CI / Tests (PHP 8.1) (pull_request) Successful in 1m0s
CI / Tests (PHP 8.2) (pull_request) Successful in 1m0s
CI / No Debug Code (pull_request) Successful in 3s
CI / Coding Standards (pull_request) Successful in 3m8s
CI / Build Plugin Zip (pull_request) Skipped
CI / PHPStan (pull_request) Successful in 2m49s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m44s
Five items from the latest demo pass: - A policy's title can be edited from the Policies screen. Only the title moves; the slug is what the gates resolve policies by, so a rename can never detach a policy from acceptances already recorded against it. - Signup is one page again. The studio's registration questions move from a second step behind "Next" onto the main form, in an "About you" panel above the students being added, and that panel also asks an adult student for their birth year (the same us_birth_year meta a child's uses). register.js disables and hides the whole panel for a pure guardian, since the questions describe a student. - The password is re-scored on submit, not only as it is typed. zxcvbn's dictionary arrives after page load, so a password typed straight away was never scored at all and the first the student heard of it was the server rejecting the whole form. - Group-class sessions appear alongside lessons wherever upcoming lessons are listed: the [us_scheduler] panel (students and instructors) and the admin student detail page. GroupClass\SessionSchedule derives them from Offering::sessionWindows(), the same derivation the billing scan uses. They carry kind = 'group_class' and no Cancel action - a session is one date in a term, not a booked slot. - Deleting a user releases what the account was holding: each upcoming lesson is cancelled, its slot freed for rebooking, its pending payment voided, and active class enrolments cancelled. Past lessons and paid history are left alone. Tests: composer test (851), composer lint, composer cs all pass. Co-Authored-By: Claude Opus 5 <[email protected]>
649 lines
14 KiB
CSS
649 lines
14 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;
|
|
}
|
|
|
|
/*
|
|
* Marks a row in the upcoming panel as a group-class session. The list mixes
|
|
* one-to-one lessons and classes, and only the class rows have no Cancel button
|
|
* — without a label that reads as a missing button rather than a different kind
|
|
* of thing.
|
|
*/
|
|
#us-booking-app .us-my-lesson-kind {
|
|
display: inline-block;
|
|
margin-left: 6px;
|
|
padding: 1px 6px;
|
|
border-radius: 10px;
|
|
background: #eef1f5;
|
|
color: #3c434a;
|
|
font-size: 0.75em;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.03em;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
/*
|
|
* The signup form's grouped sections: who you are registering, your own
|
|
* details, and the students you are adding. One box style for all three so the
|
|
* form reads as a short list of decisions rather than an undifferentiated
|
|
* column of fields.
|
|
*/
|
|
.us-reg-group {
|
|
margin: 16px 0;
|
|
padding: 12px 14px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.us-reg-group 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;
|
|
}
|
|
|
|
/*
|
|
* The account panel: who is signed in, and the way out. Sized to sit in a
|
|
* header or sidebar, so the rules stay minimal and inherit the theme's type —
|
|
* a block that lands in a site header should look like it belongs there.
|
|
*/
|
|
.us-account p {
|
|
margin: 0 0 4px;
|
|
}
|
|
|
|
.us-account-name {
|
|
font-weight: 600;
|
|
}
|
|
|
|
.us-account-email {
|
|
display: block;
|
|
font-size: 0.9em;
|
|
opacity: 0.75;
|
|
}
|
|
|
|
.us-account-actions {
|
|
margin-top: 8px;
|
|
}
|
|
|
|
/*
|
|
* `[hidden]` is a UA-stylesheet rule, so the widespread `div { display: block }`
|
|
* theme reset outranks it — the same trap the upcoming-lessons panel hit. An
|
|
* author !important is the only way to win, and it has to sit before the
|
|
* display rule it guards against.
|
|
*/
|
|
.us-notice[hidden] {
|
|
display: none !important;
|
|
}
|
|
|
|
/*
|
|
* The "you're booked" / "you're enrolled" notice. It sits above the calendar
|
|
* or class list rather than replacing it, so it needs to read as a banner
|
|
* about something that just happened — not as the page's content.
|
|
*/
|
|
.us-notice {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: 8px 16px;
|
|
margin-bottom: 16px;
|
|
padding: 12px 16px;
|
|
border: 1px solid #b7dfc0;
|
|
border-left-width: 4px;
|
|
border-radius: 4px;
|
|
background: #f2faf4;
|
|
color: #1a5c2a;
|
|
}
|
|
|
|
.us-notice p {
|
|
margin: 0;
|
|
}
|
|
|
|
.us-notice-dismiss {
|
|
background: transparent;
|
|
border: 1px solid currentColor;
|
|
border-radius: 4px;
|
|
padding: 4px 12px;
|
|
color: inherit;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* Shown only in block-editor previews (see BlockPreview). */
|
|
.us-editor-note {
|
|
font-size: 0.85em;
|
|
font-style: italic;
|
|
opacity: 0.7;
|
|
}
|