CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.2) (pull_request) Successful in 42s
CI / Tests (PHP 8.1) (pull_request) Successful in 53s
CI / PHPStan (pull_request) Successful in 2m53s
CI / Coding Standards (pull_request) Successful in 2m57s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m44s
CI / Build Plugin Zip (pull_request) Skipped
The password was only ever checked for length. It is now checked on both sides, with each side doing the job it can actually do. The browser scores it with zxcvbn, through WordPress's own password-strength-meter script rather than a second opinion of our own, and refuses to submit below "medium". That is the nuanced test — it knows Tr0ub4dor&3 is weaker than it looks — but it is advice a client can decline to take. Auth\PasswordPolicy runs on the server and is the rule that holds. It does not try to reproduce a strength score in PHP; it rejects the categorically bad, which is what a server can check without shipping a dictionary: too short, a well-known leaked password, fewer than four distinct characters, or the user's own name or email inside it. No composition rules — NIST advises against them, and they mostly produce predictable substitutions. Both thresholds come from the same two constants, handed to JavaScript by wp_localize_script, so the sides cannot drift into disagreeing about what was accepted. The verdict is attached to the field with setCustomValidity() rather than by disabling a button. The form has up to three submits plus a "Next" that already gates on checkValidity(), and an invalid field stops all of them without any of them needing to know why. Email validation moved ahead of the password check, since the password is now checked against the email. A blank form therefore reports the email first, which also matches the order the fields appear in. Verified the browser half against a controllable scorer: each score band blocks or allows as intended, the identity list reaches the meter, and the gate stays open while zxcvbn's dictionary is still loading — the server covers that window. Closes #150 Co-Authored-By: Claude Opus 5 <[email protected]>
558 lines
12 KiB
CSS
558 lines
12 KiB
CSS
.us-login-form label {
|
|
display: block;
|
|
margin-bottom: 4px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.us-login-form input[type="text"],
|
|
.us-login-form input[type="password"] {
|
|
width: 100%;
|
|
max-width: 340px;
|
|
padding: 8px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.us-error {
|
|
color: #c00;
|
|
border-left: 4px solid #c00;
|
|
padding-left: 8px;
|
|
}
|
|
|
|
#us-booking-app .us-slot {
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
padding: 12px 16px;
|
|
margin-bottom: 8px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
#us-booking-error {
|
|
color: #c00;
|
|
margin-top: 8px;
|
|
}
|
|
|
|
/*
|
|
* The upcoming-lessons panel. Every rule here is scoped under #us-booking-app —
|
|
* the same id-level specificity .us-slot above uses — because these rows sit in
|
|
* whatever layout the theme provides and carry more content than a calendar
|
|
* cell. Bare class selectors lost to theme rules on div/span/strong, which
|
|
* collapsed the flex layout and piled the details on top of the actions.
|
|
*/
|
|
#us-booking-app .us-my-lessons {
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
#us-booking-app .us-my-lesson {
|
|
box-sizing: border-box;
|
|
max-width: 100%;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
padding: 12px 16px;
|
|
margin-bottom: 8px;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: 8px 12px;
|
|
}
|
|
|
|
/*
|
|
* `min-width: 0` lets the title column shrink below its content width — without
|
|
* it a long offering title cannot compress and shoves the status pill and
|
|
* Cancel button out of the row. The flex-basis keeps the details and the
|
|
* actions on one line while there is room, and wraps them once there is not.
|
|
*/
|
|
#us-booking-app .us-my-lesson-info {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
flex: 1 1 14em;
|
|
min-width: 0;
|
|
}
|
|
|
|
#us-booking-app .us-my-lesson-title,
|
|
#us-booking-app .us-my-lesson-when {
|
|
overflow-wrap: break-word;
|
|
word-break: break-word;
|
|
}
|
|
|
|
#us-booking-app .us-my-lesson-title {
|
|
font-size: 1.05em;
|
|
}
|
|
|
|
#us-booking-app .us-my-lesson-duration {
|
|
font-weight: normal;
|
|
color: #666;
|
|
}
|
|
|
|
#us-booking-app .us-my-lesson-when {
|
|
color: #555;
|
|
}
|
|
|
|
#us-booking-app .us-my-lesson-actions {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px 12px;
|
|
align-items: center;
|
|
}
|
|
|
|
/*
|
|
* Theme-proofing for the leaf text. The row and its two columns are divs with
|
|
* explicit flex rules above, but the text itself still sits in inline elements
|
|
* a theme is free to take out of normal flow — an absolutely positioned,
|
|
* floated or negatively offset span drops the date/time on top of the title and
|
|
* the status pill on top of the Cancel button. Pinning the three properties
|
|
* that would have to change keeps the leaves in flow, at the same id-level
|
|
* specificity the rules above rely on.
|
|
*/
|
|
#us-booking-app .us-my-lesson-title,
|
|
#us-booking-app .us-my-lesson-when,
|
|
#us-booking-app .us-my-lesson-duration,
|
|
#us-booking-app .us-my-lesson-who,
|
|
#us-booking-app .us-lesson-status {
|
|
position: static;
|
|
float: none;
|
|
margin: 0;
|
|
}
|
|
|
|
/*
|
|
* The rows the "Show all" button reveals. `[hidden]` is only a UA-stylesheet
|
|
* rule, so any author rule setting a display on div beats it — the html5-reset
|
|
* `div { display: block }` is still widespread in themes — and the rows the
|
|
* button is meant to gate render anyway. An author !important is the only way
|
|
* to win that cascade.
|
|
*/
|
|
#us-booking-app [hidden] {
|
|
display: none !important;
|
|
}
|
|
|
|
#us-booking-app .us-show-all-lessons {
|
|
background: transparent;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
padding: 6px 14px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
#us-booking-app .us-show-all-lessons:hover {
|
|
border-color: #888;
|
|
}
|
|
|
|
#us-booking-app .us-cancel-lesson {
|
|
background: transparent;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
padding: 4px 12px;
|
|
cursor: pointer;
|
|
color: #c00;
|
|
}
|
|
|
|
#us-booking-app .us-cancel-lesson:hover {
|
|
border-color: #c00;
|
|
}
|
|
|
|
#us-booking-app .us-lesson-status {
|
|
display: inline-block;
|
|
font-size: 0.85em;
|
|
font-weight: 600;
|
|
padding: 2px 10px;
|
|
border-radius: 10px;
|
|
background: #eee;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
#us-booking-app .us-lesson-status-confirmed {
|
|
background: #e2f5e5;
|
|
color: #1a7d2e;
|
|
}
|
|
|
|
#us-booking-app .us-lesson-status-pending {
|
|
background: #fdf3d7;
|
|
color: #8a6d1a;
|
|
}
|
|
|
|
.us-calendar-controls {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.us-filter-toggle {
|
|
padding: 6px 16px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
background: transparent;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.us-filter-toggle.us-active {
|
|
background: #333;
|
|
border-color: #333;
|
|
color: #fff;
|
|
}
|
|
|
|
.us-type-filter {
|
|
margin-bottom: 12px;
|
|
padding: 8px 12px;
|
|
border: 1px solid #eee;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.us-type-filter-heading {
|
|
display: block;
|
|
margin-bottom: 6px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.us-type-filter-choices {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: 8px 16px;
|
|
}
|
|
|
|
.us-type-filter-choice {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.us-type-filter-clear {
|
|
margin-left: auto;
|
|
padding: 4px 12px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
background: transparent;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.us-view-toggle {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.us-view-toggle button {
|
|
padding: 6px 16px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
background: transparent;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.us-view-toggle button.us-active {
|
|
background: #333;
|
|
border-color: #333;
|
|
color: #fff;
|
|
}
|
|
|
|
.us-week-nav {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.us-week-nav button {
|
|
padding: 6px 12px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
background: transparent;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.us-week-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(7, 1fr);
|
|
gap: 8px;
|
|
}
|
|
|
|
.us-week-day {
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
padding: 8px;
|
|
min-height: 90px;
|
|
}
|
|
|
|
.us-week-day-heading {
|
|
margin: 0 0 8px;
|
|
font-size: 0.85em;
|
|
text-align: center;
|
|
}
|
|
|
|
.us-week-slot {
|
|
display: block;
|
|
width: 100%;
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.us-week-empty {
|
|
display: block;
|
|
text-align: center;
|
|
opacity: 0.4;
|
|
}
|
|
|
|
/* The price and pay agreement on a booking / enrolment form. */
|
|
.us-price {
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
padding: 12px 16px;
|
|
margin: 16px 0;
|
|
}
|
|
|
|
.us-price h4 {
|
|
margin: 0 0 8px;
|
|
}
|
|
|
|
.us-price p {
|
|
margin: 0 0 4px;
|
|
}
|
|
|
|
.us-price-amount strong {
|
|
font-size: 1.15em;
|
|
}
|
|
|
|
.us-price-cadence {
|
|
margin-left: 4px;
|
|
}
|
|
|
|
.us-price-tax,
|
|
.us-price-note {
|
|
font-size: 0.9em;
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.us-price-agree {
|
|
display: block;
|
|
margin-top: 12px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* The cadence-carrying price on a group-class card. */
|
|
.us-class-price {
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Policy acceptance — booking, enrolment, and signup all render this markup. */
|
|
.us-policy {
|
|
margin: 16px 0;
|
|
}
|
|
|
|
.us-policy h4 {
|
|
margin: 0 0 6px;
|
|
}
|
|
|
|
/*
|
|
* The body is admin-authored HTML sitting inside whatever layout the theme
|
|
* provides, so it gets an explicit reading box rather than inheriting one.
|
|
* `overflow-wrap` breaks pasted URLs instead of letting one long token force
|
|
* the horizontal scrollbar, and the bounded height keeps a long policy from
|
|
* pushing the accept checkbox off the screen.
|
|
*/
|
|
.us-policy-body {
|
|
box-sizing: border-box;
|
|
max-width: 100%;
|
|
max-height: 260px;
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
padding: 12px 14px;
|
|
margin-bottom: 8px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
background: #fafafa;
|
|
white-space: normal;
|
|
overflow-wrap: break-word;
|
|
word-break: break-word;
|
|
line-height: 1.5;
|
|
text-align: left;
|
|
}
|
|
|
|
.us-policy-body p,
|
|
.us-policy-body ul,
|
|
.us-policy-body ol {
|
|
margin: 0 0 0.75em;
|
|
max-width: 100%;
|
|
}
|
|
|
|
.us-policy-body ul,
|
|
.us-policy-body ol {
|
|
padding-left: 1.5em;
|
|
}
|
|
|
|
.us-policy-body > :last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.us-policy-accept,
|
|
.us-policies input[type="checkbox"] {
|
|
margin-right: 6px;
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.us-week-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.us-week-day {
|
|
min-height: 0;
|
|
}
|
|
|
|
/*
|
|
* A lesson row carries a title, a date/time, a status pill and a button —
|
|
* more than fits one narrow line, so stack the details above the actions
|
|
* rather than letting them wrap into each other.
|
|
*/
|
|
#us-booking-app .us-my-lesson {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
}
|
|
|
|
#us-booking-app .us-my-lesson-info {
|
|
flex: 0 0 auto;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* "Who is this for?" picker — booking and enrolment. Present only on an account
|
|
* that books for more than one person, so it is styled as a normal field rather
|
|
* than a callout.
|
|
*/
|
|
.us-student-picker select {
|
|
max-width: 100%;
|
|
}
|
|
|
|
/*
|
|
* Whose lesson a row in the upcoming panel is — only shown on an account that
|
|
* books for more than one person. Scoped under #us-booking-app like the rest of
|
|
* the panel; as a bare class it was the one rule in the group a theme could
|
|
* outrank on a plain span.
|
|
*/
|
|
#us-booking-app .us-my-lesson-who {
|
|
font-weight: normal;
|
|
opacity: 0.75;
|
|
}
|
|
|
|
/* Parent/guardian signup: the child blocks revealed by the checkbox. */
|
|
.us-guardian {
|
|
margin: 16px 0;
|
|
padding: 12px 14px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.us-guardian legend {
|
|
padding: 0 6px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.us-children-intro {
|
|
margin-top: 0;
|
|
font-size: 0.9em;
|
|
opacity: 0.8;
|
|
}
|
|
|
|
/*
|
|
* Each child is a bordered group so a family of three does not read as one long
|
|
* undifferentiated column of fields.
|
|
*/
|
|
.us-child {
|
|
margin-bottom: 12px;
|
|
padding: 10px 12px;
|
|
border-left: 3px solid #ddd;
|
|
background: #fafafa;
|
|
}
|
|
|
|
.us-child > p:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
/* The guardian's manage-children screen ([us_family]). */
|
|
.us-family-list {
|
|
margin: 0 0 20px;
|
|
padding: 0;
|
|
list-style: none;
|
|
}
|
|
|
|
.us-family-child {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px 12px;
|
|
align-items: baseline;
|
|
padding: 10px 0;
|
|
border-bottom: 1px solid #eee;
|
|
}
|
|
|
|
.us-family-child-name {
|
|
font-weight: 600;
|
|
}
|
|
|
|
.us-family-child-birth-year {
|
|
font-size: 0.9em;
|
|
opacity: 0.75;
|
|
}
|
|
|
|
/*
|
|
* The actions sit at the far end of the row. Remove is its own form (it posts),
|
|
* so it is forced inline rather than taking a block of its own.
|
|
*/
|
|
.us-family-child-actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
align-items: baseline;
|
|
margin-left: auto;
|
|
}
|
|
|
|
.us-family-remove {
|
|
display: inline;
|
|
}
|
|
|
|
/* The editing row replaces the child's line, so it spans the whole width. */
|
|
.us-family-edit {
|
|
width: 100%;
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
/* A name, a date and two actions do not fit one narrow line. */
|
|
.us-family-child-actions {
|
|
margin-left: 0;
|
|
width: 100%;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The live password verdict under the signup field. Colour is a reinforcement,
|
|
* not the message — the text says what is wrong on its own, so this still reads
|
|
* correctly to anyone who cannot separate the hues.
|
|
*/
|
|
.us-password-strength {
|
|
display: block;
|
|
margin-top: 4px;
|
|
font-size: 0.85em;
|
|
}
|
|
|
|
.us-password-strength.is-short,
|
|
.us-password-strength.is-weak {
|
|
color: #c00;
|
|
}
|
|
|
|
.us-password-strength.is-medium {
|
|
color: #7a5c00;
|
|
}
|
|
|
|
.us-password-strength.is-strong {
|
|
color: #1a7d2e;
|
|
}
|
|
|
|
/* Shown only in block-editor previews (see BlockPreview). */
|
|
.us-editor-note {
|
|
font-size: 0.85em;
|
|
font-style: italic;
|
|
opacity: 0.7;
|
|
}
|