Files
unsupervised-scheduler/assets/css/frontend.css
T
thatguygriffandClaude Opus 5 cb347ffca0
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
Demo follow-ups: editable policy name, one-page signup, group classes in upcoming lessons, deletion cleanup
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]>
2026-07-30 11:45:04 -03:00

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;
}