/* ═══════════════════════════════════════════════════════════════════════════
   Student Manager — page shell for the student view
   App bar, "next lesson" hero, empty state, feedback footer.

   Every value here goes through the --cbtt-* custom properties that
   backend/settings_design.php already prints inline, so a teacher's chosen
   design carries straight over. The fallbacks equal the classic Orange design,
   matching css/design-skin.css — so this file also works when the design is set
   to "Default – Theme related" and no palette is printed at all.

   Scoped under .cbtt-sm-shell throughout. The plugin runs inside somebody else's
   theme, and an unscoped rule here would leak into it — the way the existing
   ".row { display: block }" in assets/css/contract_dashboard.css already does.
   ═══════════════════════════════════════════════════════════════════════════ */

/* --cbtt-dock-h is the one number .cbtt-sm-nav__item's min-height (Zone L)
   and this shell's own padding-bottom below (Zone F) both read, so the space
   reserved for the dock cannot drift out of step with the dock itself. A dock
   item's content stack is 17px icon + 3px gap + 14.4px per label line (12px /
   1.2, UX 9.9's floor since 2026-09-11) + 6px top/bottom padding: one line
   ≈46px, two lines ≈61px. 61px is therefore the floor — every label measured
   (German and English, 320–414px, Figtree) takes at most two lines, so the
   bar is the same 61px on every phone instead of jumping between widths. A
   third line (a long name brought in through the filter) makes ≈75px, which
   the +16px in the reserve below and in the accessibility button's lift
   (Zone L) still absorb. Until then the labels were 10.5px on a 56px floor.

   --cbtt-sm-dock-reserve is that number turned into the strip the shell keeps
   free at its foot. It is its own property because a SECOND box subtracts
   exactly the same amount: the rail shaft (Zone M) is an absolutely positioned
   child of this shell, and bottom:0 on it would reach down through the
   reserved strip and let the sticky rail park underneath the action bar.

   --cbtt-sm-chrome-top is the first line of the window that the site's own
   fixed and sticky bars do not cover — the admin bar plus a sticky theme
   header. The app bar sticks there, the rail sticks a gap below it, and every
   anchor target clears it. assets/js/cbtt-sm-nav.js MEASURES it and writes it
   onto each shell, because no theme publishes its header height and the one
   that is ours changes it at runtime: CBTT-Turbo's header is about 70px at
   rest and 42 to 55px once scrolled (depending on whether its menu carries a
   button), and the customizer scales the logo on top of that. A constant is
   wrong on every site in a different way, and here it would be wrong on the
   customers' sites, whose themes we have never seen.

   Without the script it falls back to --cbtt-sticky-top, CBTT-Turbo's
   measurement of the WordPress admin bar. theme.js writes that one to <body>
   as an inline style and this shell is a descendant, so what arrives here by
   inheritance is the measured value. A theme without it contributes 0 — the
   same top:0 the app bar had before, so nothing gets worse. */
.cbtt-sm-shell {
    --cbtt-dock-h: 61px;
    --cbtt-sm-dock-reserve: calc(var(--cbtt-dock-h, 61px) + env(safe-area-inset-bottom, 0px) + 16px);
    --cbtt-sm-chrome-top: var(--cbtt-sticky-top, 0px);
    --cbtt-sm-rail-gap: 12px;
    /* 76px item + 2×6px padding + 2×1px border. The shaft's minimum width
       below reads it, so the rail and the room made for it cannot disagree. */
    --cbtt-sm-rail-w: 90px;
    font-family: var(--cbtt-font-sans, inherit);
    color: var(--cbtt-text, #1d2327);
}

/* The positioning context for the rail shaft (Zone M), and ONLY on the shell
   that has one. The calendar, reports and feedback pages set the modifier when
   they really rendered a rail, instead of this being derived with :has() — the
   trial calendar, the GDPR export and the three wp-admin screens share
   .cbtt-sm-shell and keep plain static positioning, in every browser.

   position:relative does NOT make a containing block for position:fixed, so
   .cbtt-sm-actionbar keeps hanging off the viewport. The one other absolutely
   positioned descendant without a positioned ancestor of its own is
   .cbtt-sm-skip, which now appears 8px inside the shell rather than 8px inside
   the document — where the student is looking anyway. */
.cbtt-sm-shell--rail {
    position: relative;
}

/* The target layout is a 720 px reading column, but that width is NOT set on
   .cbtt-sm-shell: the legacy card grid (.cbtt-cal-*) still renders inside this wrapper
   until the lesson list is rebuilt, and constraining the wrapper would squeeze
   the desktop grid in the meantime. So each new block carries the measure
   itself, and the width moves up to the wrapper once the old grid is gone. */
.cbtt-sm-appbar,
.cbtt-sm-hero,
.cbtt-sm-empty,
.cbtt-sm-feedback {
    max-width: 720px;
    margin-inline: auto;
    padding-inline: 16px;
}

/* The next lesson is the single most important thing on the page, and it was
   the only block that was NOT a card — so between a boxed offer above it and
   boxed rows below it, the one item that matters read as a gap. It gets the
   card treatment the rows have, one step stronger: the accent edge marks it
   as the page's subject rather than one entry among many. */
.cbtt-sm-hero__card {
    margin: 0 16px;
    padding: 14px 16px;
    border: 1px solid var(--cbtt-border, #e6e3dd);
    border-left: 3px solid var(--cbtt-accent, #0193aa);
    border-radius: var(--cbtt-radius-md, 10px);
    background: var(--cbtt-surface, #fff);
}

@media (min-width: 752px) {
    .cbtt-sm-hero__card {
        margin-inline: 0;
    }
}

/* Anything a notice or an in-page link jumps to. Without this the target
   lands under the sticky app bar — the "Top up" link scrolled the balance
   panel to exactly where the bar covers it.

   The 72px are the app bar (52px) plus air. They used to be the whole answer
   because the app bar stuck to the top of the WINDOW, underneath the site's
   own header; it now sticks below that header (Zone B), so a target has to
   clear both. The property is inherited, so it also resolves for the four ids,
   all of which sit inside .cbtt-sm-shell. */
.cbtt-sm-shell :target,
#cbtt-sm-schedule,
#cbtt-sm-contract,
#cbtt-sm-topup,
#cbtt-sm-account {
    scroll-margin-top: calc(var(--cbtt-sm-chrome-top, 0px) + 72px);
}

/* The same for keyboard focus (WCAG 2.4.11, UX rule 9.2). With the app bar
   sticking visibly below the site header instead of hidden under it, a
   Shift+Tab that scrolls a lesson's button in at the top edge used to land it
   underneath header plus app bar, and a Tab at the bottom edge underneath the
   dock. Browsers honour scroll-margin when they scroll a focused element into
   view. The navigation itself is left out: the rail scrolls inside itself, and
   the dock and the action bar are fixed — a margin there would only push their
   own scrolling around. */
.cbtt-sm-shell :is(a, button, input, select, textarea, summary, [tabindex]):not(.cbtt-sm-rail *, .cbtt-sm-dock *, .cbtt-sm-actionbar *) {
    scroll-margin-top: calc(var(--cbtt-sm-chrome-top, 0px) + 64px);
    scroll-margin-bottom: calc(var(--cbtt-dock-h, 61px) + 16px);
}

/* Skip link — zone A. The promise "the timetable without a click" only holds
   for keyboard and screen-reader users if there is a way past the chrome. */
.cbtt-sm-skip {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 100;
    padding: 10px 16px;
    background: var(--cbtt-surface, #fff);
    color: var(--cbtt-link, #086b7e);
    border-radius: var(--cbtt-radius-md, 4px);
    box-shadow: var(--cbtt-shadow-md, 0 4px 6px rgba(0, 0, 0, .1));
}

.cbtt-sm-skip:focus {
    left: 8px;
    top: 8px;
}

/* ── Zone B — app bar ──────────────────────────────────────────────────── */

/* top used to be 0: "stick to the top of the window", which on a theme with a
   sticky header means underneath it (CBTT-Turbo's is z-index 100 against this
   bar's 30). The bar stuck, but out of sight — the same geometric fault as the
   rail's in Zone M, one element over. Fixing only the rail would have left
   this bar the single thing on the page still sliding under the header, so
   both read the same edge now and cannot disagree about it. */
.cbtt-sm-appbar {
    position: sticky;
    top: var(--cbtt-sm-chrome-top, 0px);
    z-index: 30;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    min-height: 52px;
    background: var(--cbtt-surface, #fff);
    border-bottom: 1px solid var(--cbtt-border-soft, #eee);
}

/* position:sticky is silently dead if ANY ancestor has overflow other than
   visible — a very common theme rule. CBTT-Turbo deliberately uses
   overflow-x: clip instead of hidden on html and body for exactly this reason
   (theme.css, with the reasoning next to it). In a theme that uses hidden —
   html, body or any wrapper, overflow-x:hidden on #page is the classic — this
   bar simply stops following and scrolls away with the page. For the rail in
   Zone M that would take away the only desktop menu, so there the script
   detects the ancestor and hands the navigation to the dock instead
   (data-nosticky). Verify on the live site rather than assuming; if it does
   not stick, the culprit is an ancestor, not this rule. */

/* min-width:0 plus truncation: without it the title refuses to shrink below its
   content width, and on a 375 px bar carrying two chips it wrapped to a second
   line — turning a 52 px bar into an 80 px one right above the thing this whole
   layout exists to put first. */
.cbtt-sm-appbar__title {
    margin: 0;
    min-width: 0;
    flex: 0 1 auto;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-size: 15px;
    font-weight: 600;
    line-height: 1.4;
    color: var(--cbtt-text, #1d2327);
    /* The bar is a strip of UI chrome, not a piece of the theme's prose. Every
       property a theme routinely sets on a bare h1 has to be answered here or
       the same element renders differently on the two pages that use it: on the
       reports page the title is an <h1> inside .wrap, and CBTT-Turbo's heading
       block (letter-spacing, brand family, optional uppercase transform) plus
       WordPress's own .wrap h1 padding pushed it right and off the chip's
       baseline. On the schedule page, where the title is not an h1, none of that
       applied — which is exactly why the two bars did not match.
       padding-inline is reset rather than left alone because that indent was the
       visible symptom; text-indent for the pathological case of a theme that
       indents first lines. */
    padding: 0;
    text-indent: 0;
    letter-spacing: normal;
    text-transform: none;
    font-family: inherit;
}

.cbtt-sm-appbar__chips {
    flex: 0 0 auto;
}

.cbtt-sm-appbar__chips {
    display: flex;
    align-items: center;
    gap: 7px;
}

.cbtt-sm-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 11px;
    border: 1px solid var(--cbtt-border, #ddd);
    border-radius: 999px;
    background: var(--cbtt-surface-alt, #f8f9fa);
    color: var(--cbtt-text, #1d2327);
    font-size: 13px;
    font-weight: 600;
    line-height: 1.3;
    text-decoration: none;
    white-space: nowrap;
}

.cbtt-sm-chip__icon {
    width: 13px;
    height: 13px;
    flex: none;
}

.cbtt-sm-money {
    font-variant-numeric: tabular-nums;
}

/* Escalation carries colour AND an icon AND a word — never colour alone. */
.cbtt-sm-appbar[data-level="low"] .cbtt-sm-chip--money {
    background: var(--cbtt-warning-tint, #fff3cd);
    border-color: var(--cbtt-warning, #f0ad4e);
    color: var(--cbtt-warning-ink, #856404);
}

.cbtt-sm-appbar[data-level="critical"] .cbtt-sm-chip--money {
    background: var(--cbtt-error-tint, #f8d7da);
    border-color: var(--cbtt-error, #d9534f);
    color: var(--cbtt-error-ink, #721c24);
}

/* The cancellation-credit mark. Sized in em so it tracks whatever text it
   sits in, from the 19 px panel figure down to an 11 px footnote. */
.cbtt-sm-mana {
    display: inline-flex;
    align-items: center;
    gap: .28em;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.cbtt-sm-mana__mark {
    width: 1em;
    height: 1em;
    flex: none;
    color: var(--cbtt-accent, #0193aa);
}

.cbtt-sm-btn--cost .cbtt-sm-mana__mark {
    color: currentColor;
}

/* ── Zone C — system row ───────────────────────────────────────────────── */

/* Every applicable notice, stacked, no fold.
   Deliberately NOT sticky any more. A single row could be pinned under the app
   bar without costing much; a stack of three cannot — it would follow the
   student down the page eating a third of a 667px screen. What has to stay in
   view is the balance, and the app bar's chip already does that. These rows sit
   at the top, which is where they are read. */
/* padding-inline matches the other 720 px blocks (.cbtt-sm-appbar, .cbtt-sm-hero, …).
   Without it this stack was the only block at the full 720 px while everything
   around it was inset by 16 px on each side — so the notice hung 32 px wider
   than the hero card directly beneath it and than every lesson row below that.
   Against a tinted background with a coloured left edge, that overhang is the
   most visible edge on the page and read as the layout coming apart. */
.cbtt-sm-sysrows {
    max-width: 720px;
    margin-inline: auto;
    padding-inline: 16px;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.cbtt-sm-sysrow {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 40px;
    padding: 6px 12px 6px 16px;
    font-size: 13px;
    line-height: 1.25;
    background: var(--cbtt-surface-alt, #f8f9fa);
    color: var(--cbtt-text, #1d2327);
    border-left: 3px solid var(--cbtt-accent, #0193aa);
}

.cbtt-sm-sysrow[data-level="warning"] {
    background: var(--cbtt-warning-tint, #fff3cd);
    color: var(--cbtt-warning-ink, #856404);
    border-left-color: var(--cbtt-warning, #f0ad4e);
}

/* Wraps rather than truncates. The one-line ellipsis was safe while every
   notice was a short status; the expiry warning names an amount and a date and
   would lose both to "…" on a 375px screen — a warning about money that cannot
   be read is worse than a taller row. */
.cbtt-sm-sysrow__text {
    flex: 1;
    min-width: 0;
    overflow-wrap: anywhere;
}

/* 44 px of hit area without 44 px of row: the padding overlaps the line above
   and below. Touch target beats tidy box here. */
.cbtt-sm-sysrow__action {
    flex: none;
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    margin-block: -2px;
    padding-inline: 4px;
    color: var(--cbtt-primary, #003f86);
    font-weight: 600;
    white-space: nowrap;
    text-decoration: none;
}

/* ── Zone D — next lesson ──────────────────────────────────────────────── */

.cbtt-sm-eyebrow {
    margin: 22px 0 6px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--cbtt-accent-ink, #086b7e);
}

.cbtt-sm-hero__when {
    margin: 0;
    font-size: 21px;
    font-weight: 600;
    line-height: 1.22;
    letter-spacing: -.01em;
    color: var(--cbtt-text, #1d2327);
    text-wrap: balance;
}

.cbtt-sm-hero__rel {
    margin: 5px 0 0;
    font-size: 14px;
    color: var(--cbtt-text-muted, #777);
}

.cbtt-sm-hero__note {
    margin: 8px 0 0;
    font-size: 12.5px;
    line-height: 1.45;
    color: var(--cbtt-text-muted, #777);
}

.cbtt-sm-btn {
    display: block;
    width: 100%;
    min-height: 46px;
    margin-top: 14px;
    padding: 11px 14px;
    border: 1px solid var(--cbtt-border, #ddd);
    border-radius: var(--cbtt-radius-md, 4px);
    background: var(--cbtt-surface, #fff);
    color: var(--cbtt-text, #1d2327);
    font: inherit;
    font-size: 14px;
    font-weight: 600;
    text-align: center;
    cursor: pointer;
}

/* A .cbtt-sm-btn that is an <a> has to defend its own colour and lose the
   underline. Themes style links with selectors like `.entry-content a`, which
   is two classes to .cbtt-sm-btn's one — so on a themed page the link colour won
   and a primary button rendered as theme-blue text on the primary blue fill.
   Unreadable, and exactly where it hurts most: the button is the only way out
   of that block. Written as a.cbtt-sm-btn so it outranks the theme on element +
   class rather than relying on source order. */
a.cbtt-sm-btn,
a.cbtt-sm-btn:hover,
a.cbtt-sm-btn:focus,
a.cbtt-sm-btn:visited,
a.cbtt-sm-btn:active {
    color: var(--cbtt-text, #1d2327);
    text-decoration: none;
}

.cbtt-sm-btn--primary,
a.cbtt-sm-btn--primary,
a.cbtt-sm-btn--primary:hover,
a.cbtt-sm-btn--primary:focus,
a.cbtt-sm-btn--primary:visited,
a.cbtt-sm-btn--primary:active {
    background: var(--cbtt-primary, #003f86);
    border-color: var(--cbtt-primary, #003f86);
    color: var(--cbtt-on-primary, #fff);
}

.cbtt-sm-btn--primary:hover,
.cbtt-sm-btn--primary:focus {
    background: var(--cbtt-primary-hover, #002b57);
    border-color: var(--cbtt-primary-hover, #002b57);
}

/* Chargeable actions are marked by colour, by a border AND by the amount in
   the label itself — the old UI distinguished them by an icon alone, and that
   icon is rendered aria-hidden. */
.cbtt-sm-btn--cost {
    background: var(--cbtt-warning-tint, #fff3cd);
    border-color: var(--cbtt-warning, #f0ad4e);
    color: var(--cbtt-warning-ink, #856404);
}

/* ── Empty state ───────────────────────────────────────────────────────── */

.cbtt-sm-empty {
    padding: 24px 0;
}

.cbtt-sm-empty__title {
    margin: 0 0 4px;
    font-size: 17px;
    font-weight: 600;
    color: var(--cbtt-text, #1d2327);
}

.cbtt-sm-empty__hint {
    margin: 0 0 14px;
    font-size: 14px;
    color: var(--cbtt-text-muted, #777);
}

/* ── Zone F — the lesson list ──────────────────────────────────────────── */

.cbtt-sm-list {
    max-width: 720px;
    margin-inline: auto;
    padding-inline: 16px;
}

/* Room for the action bar, so the row a student just selected is not the one
   hidden underneath it — and, at the foot of the page, so the feedback links
   in Zone K are not the last thing it hides. The old bar was roughly 109 px
   tall with no compensation at all, which is precisely the failure it
   produced. The formula lives in --cbtt-sm-dock-reserve at the top of this
   file, built on --cbtt-dock-h, because the rail shaft (Zone M) has to take
   exactly the same strip off its own bottom edge; written out twice, the
   second copy is the one the next dock change forgets. Deliberately NOT
   scoped to the ≤1023px dock breakpoint: the action-state face of the bar
   shows at every width (see Zone L, "the action state shows a bar at every
   width"), so the space reserved for it has to as well, or a desktop
   selection would reintroduce the exact hidden-row bug this rule exists to
   prevent. The extra 16px is headroom beyond the bar's own height, not part
   of it. */
.cbtt-sm-shell {
    padding-bottom: var(--cbtt-sm-dock-reserve);
}

.cbtt-sm-list__group {
    margin: 26px 0 2px;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--cbtt-border, #ddd);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--cbtt-text-muted, #777);
}

/* The point in the list where the projected cancellation credit stops covering
   a cancellation. Emitted once, immediately above the first row it applies to.
   Borrowed vocabulary rather than a new one: the micro-caps of .cbtt-sm-list__group
   because it is a boundary in the same list, the dashed rule of
   .cbtt-sm-fold__summary because it separates rather than warns, and the accent ink
   of .cbtt-sm-eyebrow because it does have to be findable. No tint and no icon —
   nothing is wrong here, the student is simply past the point where cancelling
   is free of money. */
.cbtt-sm-list__spent {
    margin: 14px 0 2px;
    padding-top: 9px;
    border-top: 1px dashed var(--cbtt-border, #ddd);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--cbtt-accent-ink, #086b7e);
}

/* The rows carry .cbtt-cal-card (the 2026-08-22 rename of Bootstrap's
   .card.h-100 DOM contract) because the selection script addresses them by
   exactly that selector. Undo what the card base brings visually — four rows
   now fit in the height one card used to take. */
.cbtt-sm-list .cbtt-sm-row,
.cbtt-sm-listfoot .lesson-card {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 10px;
    align-items: center;
    padding: 11px 14px;
    /* Every row is a card of the same width. Before, only the offers were
       boxed and the student's own lessons were bare rows, which produced a
       ragged edge down the list and made the lessons that matter look like
       leftovers between the ones that do not.

       The border is always 1px and always present — selection changes only
       COLOUR below, never geometry. A 2px selected state (what the legacy
       stylesheet did) grows the row and shoves the rest of the list down on
       every click. */
    border: 1px solid var(--cbtt-border, #e6e3dd);
    border-radius: var(--cbtt-radius-md, 10px);
    background: var(--cbtt-surface, #fff);
    box-shadow: none;
    margin-bottom: 8px;
}

/* An offer is an invitation, not a commitment: it recedes. No fill, a softer
   edge, quieter ink. Everything here comes from the same --cbtt-* tokens the
   teacher themes, so a studio that reskins the plugin reskins this too. */
.cbtt-sm-list .cbtt-sm-row--offer {
    border-style: dashed;
    border-color: var(--cbtt-border-soft, #eee);
    background: none;
}

.cbtt-sm-list .cbtt-sm-row--offer .cbtt-sm-row__when {
    font-weight: 500;
    color: var(--cbtt-text-muted, #777);
}

.cbtt-sm-row__when {
    margin: 0;
    font-size: 14.5px;
    font-weight: 600;
    color: var(--cbtt-text, #1d2327);
}

.cbtt-sm-row__meta {
    margin: 2px 0 0;
    font-size: 12.5px;
    color: var(--cbtt-text-muted, #777);
}

.cbtt-sm-row__btn {
    min-height: 40px;
    padding: 8px 12px;
    border: 1px solid var(--cbtt-border, #ddd);
    border-radius: var(--cbtt-radius-sm, 4px);
    background: var(--cbtt-surface, #fff);
    color: var(--cbtt-text, #1d2327);
    font: inherit;
    font-size: 12.5px;
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
}

/* Chargeable is stated three ways — border, tint and the amount in the label —
   because the previous UI distinguished it by an icon alone, and that icon is
   rendered aria-hidden. */
.cbtt-sm-row__btn--cost {
    border-color: var(--cbtt-warning, #f0ad4e);
    background: var(--cbtt-warning-tint, #fff3cd);
    color: var(--cbtt-warning-ink, #856404);
}

/* Offers are the QUIET half of the list.
   They were solid primary-filled buttons, which made every free slot the
   loudest thing on the page — so the eye landed on lessons the student does
   not have before it found the ones they do. An offer is an invitation; a
   booked lesson is a commitment, and the commitment has to win. Outline
   instead of fill, and the row itself steps back a shade. */
.cbtt-sm-row__btn--book,
.cbtt-sm-row__btn--call {
    border-color: var(--cbtt-primary, #003f86);
    background: var(--cbtt-surface, #fff);
    color: var(--cbtt-primary, #003f86);
    text-decoration: none;
    display: inline-flex;
    align-items: center;
}

.cbtt-sm-row__btn--book:hover,
.cbtt-sm-row__btn--call:hover,
.cbtt-sm-row__btn--book:focus-visible,
.cbtt-sm-row__btn--call:focus-visible {
    background: var(--cbtt-primary, #003f86);
    color: var(--cbtt-on-primary, #fff);
}

/* The offer row recedes: its date is the same size as everyone's, but not the
   same weight or ink. Nothing is hidden — it just stops competing. */
.cbtt-sm-list .cbtt-sm-row--offer .cbtt-sm-row__when {
    font-weight: 500;
    color: var(--cbtt-text-muted, #777);
}

/* Selection is a DIFFERENT axis from booked-vs-offer, so it must not reuse the
   same visual language. It gets the accent rail and a tint that neither of the
   two row kinds uses, which is what keeps "I picked this" readable on top of
   either. */
/* Selection is COLOUR ONLY. No border-width change, no padding change, no
   margin change — the row keeps its exact box, so clicking through a list does
   not make it jump. (The earlier version shifted the row 3px left and the
   legacy sheet grew the border to 2px; both moved everything below.) */
.cbtt-sm-list .cbtt-sm-row.marked {
    background: var(--cbtt-accent-tint, #e6f4f3);
    border-style: solid;
    border-color: var(--cbtt-accent, #0193aa);
}

.cbtt-sm-list .cbtt-sm-row--offer.marked .cbtt-sm-row__when {
    font-weight: 600;
    color: var(--cbtt-text, #1d2327);
}

/* Everything beyond the fold. Presentation only: the rows stay in the document
   and stay cancellable, so a lesson months out is always reachable. */
.cbtt-sm-fold__summary {
    display: block;
    margin-top: 14px;
    padding: 11px 12px;
    border: 1px dashed var(--cbtt-border, #ddd);
    border-radius: var(--cbtt-radius-md, 4px);
    color: var(--cbtt-accent-ink, #086b7e);
    font-size: 13px;
    font-weight: 600;
    text-align: center;
    cursor: pointer;
}

.cbtt-sm-fold[open] > .cbtt-sm-fold__summary {
    margin-bottom: 4px;
}

/* ── Zone G — list foot ────────────────────────────────────────────────── */

.cbtt-sm-listfoot {
    max-width: 720px;
    margin: 24px auto 0;
    padding-inline: 16px;
}

.cbtt-sm-listfoot__link {
    margin: 0;
}

.cbtt-sm-listfoot__link a,
.cbtt-sm-listfoot__item > summary {
    min-height: 44px;
    display: flex;
    align-items: center;
    font-size: 14px;
    color: var(--cbtt-link, #086b7e);
    cursor: pointer;
}

.cbtt-sm-listfoot__hint {
    margin: 0 0 8px;
    font-size: 12.5px;
    color: var(--cbtt-text-muted, #777);
}

.cbtt-sm-listfoot__row {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-items: center;
}

/* ── Swap pairing indicator ────────────────────────────────────────────── */

/* Drawn by js/mark_available_swaps.js when a marked lesson and a marked free
   slot fall inside the swap window. It replaces three layers of animated
   chevrons on rotated base64 backgrounds: that read as "these two belong
   together" across a grid of cards, but the list is one column now, so the
   beam would have lain across the rows between the pair.

   Anchored in the shell, not the viewport, so it scrolls with the rows. */
/* z-index 3 was not enough once the rows became cards: .cbtt-cal-card is
   position:relative, so every row is a positioned element painting in document
   order — and the overlay, sitting at a low index inside the same stacking
   context, ended up under the row backgrounds it is meant to connect. 25 keeps
   it above the list and still below the system row (29) and the action bar
   (1030), neither of which it may cover. */
/* Both rows of a swap pair carry the same accent edge and the same badge, so
   the pairing is stated on the elements themselves — no overlay, no geometry,
   nothing to re-measure when the fold opens or the list reflows. */
.cbtt-sm-list .cbtt-sm-row--swap {
    border-color: var(--cbtt-success, #46946a);
}

.cbtt-sm-row__swapmark {
    display: inline-block;
    margin-top: 4px;
    padding: 1px 7px;
    border-radius: 999px;
    background: var(--cbtt-success, #46946a);
    color: var(--cbtt-on-primary, #fff);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    white-space: nowrap;
}

/* Only number the badges once a second pair exists — with one pair the digit
   answers a question nobody asked. */
.cbtt-sm-list:has(.cbtt-sm-row[data-swap-pair="2"]) .cbtt-sm-row__swapmark::after {
    content: " " attr(data-pair);
}

/* ── Zone L — action bar ───────────────────────────────────────────────── */

/* Only ever ONE fixed element at the bottom: two stacked bars would be roughly
   a quarter of a 667 px screen. */
/* Hidden by DEFAULT, in CSS, and revealed by script — not the other way round.
   Previously only JavaScript ever hid it (hideBottomBar() on load), so a script
   error left the bar sitting there: visible, inviting, and dead. Failing to
   "invisible" is the safe direction.

   The safe-area inset keeps the button clear of the iOS home indicator. Note it
   only resolves to a non-zero value when the document carries
   <meta name="viewport" content="... viewport-fit=cover">; without that it is
   0 and this is simply a no-op. */
.cbtt-sm-actionbar {
    /* fixed/background/border used to come from Bootstrap's
       .bg-light.fixed-bottom.border-top utilities on the same element. */
    position: fixed;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 1030;
    background: var(--cbtt-surface-alt, #f8f9fa);
    border-top: 1px solid var(--cbtt-border, #e6e1d7);
    transform: translateY(100%);
    transition: transform .3s ease-in-out;
    padding-bottom: env(safe-area-inset-bottom, 0);
}

/* Two mutually exclusive faces of ONE fixed element. Which one is showing is a
   data-state attribute, so the breakpoint decides what "nav" means rather than
   the script: on a phone the dock is the only navigation there is, on a wide
   screen the rail has already taken that job and a permanently parked bar
   would only cost --cbtt-dock-h (61 px). */
.cbtt-sm-actionbar[data-state="action"] {
    transform: none;
}

.cbtt-sm-actionbar .cbtt-sm-dock,
.cbtt-sm-actionbar[data-state="action"] .cbtt-sm-dock {
    display: none;
}

/* Up to 1023px, not 767: the rail below needs room in the margin beside the
   720px content column, and between those two widths there is none. Without
   this the tablet range would have no section navigation at all.

   1023.98, not 1023: with display scaling the layout width can be fractional
   (Windows at 125% turns a 1279px window into 1023.2 CSS px), and a width
   between 1023 and 1024 matched neither this query nor the rail's
   (min-width: 1024px) — no dock, no rail, no Log out. The same value stands
   at the :root:has(.cbtt-sm-actionbar) rule for the floating button. */
@media (max-width: 1023.98px) {
    .cbtt-sm-actionbar[data-state="nav"] {
        transform: none;
    }

    .cbtt-sm-actionbar[data-state="nav"] .cbtt-sm-dock {
        display: flex;
    }

    .cbtt-sm-actionbar[data-state="nav"] .cbtt-sm-actionbar__inner {
        display: none;
    }
}

/* The dock carries PAGES, at most five of them (Availability while it is due,
   the way back, Billing & records, My account, Feedback, Log out — the page
   being viewed is never in its own menu; see cbtt_sm_nav_entries()). Until
   2026-09-11 it carried eleven entries including the seven reports tabs, and
   folded the tail behind a named, counted door. With pages instead of tabs the
   list fits a 320px phone, and the door, its panel and the fold are gone.

   Tiles grow to fill the bar and may shrink down to their longest word (the
   flex minimum, min-content) — with five entries at most, that squeezes
   nothing that was not already short. Should a site's cbtt_sm_nav_entries filter
   add more than fit, the row scrolls sideways as the fallback, announced by
   scroll shadows: two cover layers attached `local` scroll with the entries,
   two shadow layers stay with the box, so a shadow shows only at an edge that
   really has entries behind it and sits under the labels, never over them.
   (The mask-image fade this replaced was painted on the scroll port and lay
   across the last entry even at the end of the scroll.)

   The scrollbar is hidden rather than styled: it would sit on top of the
   labels in a 61px-high bar. scrollbar-width is Firefox, -ms-overflow-style is
   legacy Edge, the ::-webkit-scrollbar rule below covers Chrome and Safari. */
.cbtt-sm-dock {
    display: flex;
    max-width: 720px;
    margin-inline: auto;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
    scrollbar-width: none;
    -ms-overflow-style: none;
    background:
        linear-gradient(to right, var(--cbtt-surface-alt, #f8f9fa) 30%, rgba(255, 255, 255, 0)) local left / 18px 100% no-repeat,
        linear-gradient(to left, var(--cbtt-surface-alt, #f8f9fa) 30%, rgba(255, 255, 255, 0)) local right / 18px 100% no-repeat,
        radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, .14), rgba(0, 0, 0, 0)) scroll left / 10px 100% no-repeat,
        radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, .14), rgba(0, 0, 0, 0)) scroll right / 10px 100% no-repeat,
        var(--cbtt-surface-alt, #f8f9fa);
    /* The same, with the shadow taken from the text colour instead of black:
       on a dark custom palette a black shadow on a dark bar is invisible. A
       browser without color-mix() drops this declaration and keeps the one
       above. */
    background:
        linear-gradient(to right, var(--cbtt-surface-alt, #f8f9fa) 30%, rgba(255, 255, 255, 0)) local left / 18px 100% no-repeat,
        linear-gradient(to left, var(--cbtt-surface-alt, #f8f9fa) 30%, rgba(255, 255, 255, 0)) local right / 18px 100% no-repeat,
        radial-gradient(farthest-side at 0 50%, color-mix(in srgb, var(--cbtt-text, #1d2327) 18%, transparent), transparent) scroll left / 10px 100% no-repeat,
        radial-gradient(farthest-side at 100% 50%, color-mix(in srgb, var(--cbtt-text, #1d2327) 18%, transparent), transparent) scroll right / 10px 100% no-repeat,
        var(--cbtt-surface-alt, #f8f9fa);
}

.cbtt-sm-dock::-webkit-scrollbar {
    display: none;
}

.cbtt-sm-dock .cbtt-sm-nav__item {
    flex: 1 1 72px;
    scroll-snap-align: start;
}

/* "Billing & records" is the one name of two words and an ampersand. In German
   ("Abrechnung & Unterlagen") an equal share of a 360px phone breaks it onto
   three lines, and the whole bar grows by a line. Its tile starts wider: at
   the 12px labels (since 2026-09-11) 100px keeps it at two lines everywhere
   measured — German and English, calendar and feedback page, due or not,
   320–414px, Figtree — with the narrowest other tile at 57px on a 320px
   screen, above the 44px target. The other long German word,
   "Verfügbarkeiten", cannot break at all; its menu label carries a soft
   hyphen ("Verfüg-barkeiten", msgctxt "student menu entry") — without it
   the five German tiles did not fit 320px and the dock scrolled. */
.cbtt-sm-dock .cbtt-sm-nav__item--billing {
    flex-basis: 100px;
}

/* Five German labels on a 320px phone ("Verfügbarkeiten", "Abrechnung &
   Unterlagen", "Abmelden" are the long ones) need the last few pixels: every
   tile gives up 2px of side padding there. The height does not change.
   (0,3,0), because the shell-scoped tile rule further down is (0,2,0) and comes
   later. */
@media (max-width: 359.98px) {
    .cbtt-sm-shell .cbtt-sm-dock .cbtt-sm-nav__item {
        padding-inline: 2px;
    }
}

/* A scroll container clips on BOTH axes, so the shared focus ring — drawn 2px
   outside the item — would lose its top and bottom edge in here. Inset the
   outline instead: same ring, drawn inside the item, nothing clipped. The rail
   is in it because it scrolls inside itself when a window is too short for it
   (Zone M). */
.cbtt-sm-shell .cbtt-sm-dock .cbtt-sm-nav__item:focus-visible,
.cbtt-sm-shell .cbtt-sm-rail .cbtt-sm-nav__item:focus-visible {
    outline-offset: -3px;
    box-shadow: none;
}

/* ── Zone M — the rail, and the shaft it stands in ─────────────────────── */

/* The same entries, stood on end, in the margin the content column does not
   use. Hidden below 1024px, where that margin does not exist. It exists
   because the page runs past 2400px on a laptop, so the dock's destinations
   sat a long scroll away while the margin beside the text stayed empty.

   Until 2026-09-11 it was position:fixed; top:50%; left:max(12px, calc(50vw -
   360px - 100px)); z-index:28 — and the students' bug report comes straight
   out of that one declaration:

     • fixed is measured against the WINDOW and has no relationship to the
       page. The rail knew nothing about the theme's sticky header, so it slid
       underneath it, and nothing about the footer, so it floated over that at
       the end of a long page.
     • Raising the z-index only swaps which of the two is wrong — the plugin's
       navigation would then cover the site's own menu. The fix has to be
       geometric: give the rail a box it cannot leave. That is why the z-index
       below is still 28.
     • 50vw includes the classic Windows scrollbar; the theme's centring does
       not. The 10px of air the formula promised beside the text were really
       about 2.5px.

   The shaft is the fix. It is absolutely positioned over the whole shell, so
   it takes NO space in the flow — app bar, hero, the 720px list and every
   panel lay out exactly as before — and the rail inside it is position:sticky,
   which can only ever travel within its containing block. The top of the
   shaft is the top of the shell and its bottom is the top of the strip
   reserved for the action bar; those two edges are the header and the footer
   the rail can no longer pass. Between them it follows the scroll, which is
   what it was always meant to do.

   The insets are percentages of the SHELL, not of the window, which also
   retires the scrollbar error: 50% of the shell is the centre line of every
   720px block inside it, because it IS the box those blocks are centred in.
   (The 12px padding-inline of .student-manager-container cancels against its
   own centring, so the list starts at exactly 50% - 360px as well:
   12 + (W - 24 - 720) / 2 = W / 2 - 360.)

   NEVER put overflow (other than visible), transform, filter, contain,
   content-visibility or container-type on the shaft or on the shell. Any other
   overflow makes that box the nearest scrollport, and the sticky rail then
   sticks to a box that never scrolls — it freezes at the top of the page and
   looks as though position:sticky were ignored. The rest turn the element into
   a containing block for position:fixed descendants, and the action bar is one
   of those. */
.cbtt-sm-railshaft {
    display: none;
}

@media (min-width: 1024px) {
    .cbtt-sm-railshaft {
        display: flex;
        position: absolute;
        top: 0;
        bottom: var(--cbtt-sm-dock-reserve);
        inset-inline-start: 0;
        /* Two terms, the smaller wins. The first stands the rail beside the
           720px reading column. The second is the floor that replaces the old
           max(12px, …): however narrow a host theme's content column is, the
           shaft never gets narrower than the rail. Without it the calc goes
           negative, the width clamps to 0, and above 1023px — where the dock
           is display:none — the student is left with NO navigation at all. A
           rail that visibly overlaps the text beats a menu that silently
           disappears. */
        inset-inline-end: min(
            calc(50% + 360px + var(--cbtt-sm-rail-gap, 12px)),
            calc(100% - var(--cbtt-sm-rail-w, 90px))
        );
        /* flex-end stands the rail against the reading column and decides
           which way an oversized one overflows: outwards into the page gutter,
           never across the text. align-items:flex-start because the flex
           default (stretch) would make the rail a page-tall white card with
           the entries at its top. */
        justify-content: flex-end;
        align-items: flex-start;
        /* A transparent, page-tall column painted over everything after it (a
           positioned box paints above in-flow content) eats clicks: without
           this, every click and every text selection left of the reading
           column would hit the shaft instead of the page. The rail switches
           events back on for itself, and only for itself. */
        pointer-events: none;
    }
}

.cbtt-sm-rail {
    display: none;
    box-sizing: border-box;
    flex-direction: column;
    /* 0 0 auto, not the flex default: with shrink:1 a narrow shaft would
       squeeze the rail instead of letting it hang out into the gutter. */
    flex: 0 0 auto;
    gap: 2px;
    padding: 6px;
    pointer-events: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
    border: 1px solid var(--cbtt-border, #e6e3dd);
    border-radius: var(--cbtt-radius-lg, 16px);
    box-shadow: 0 6px 24px rgba(0, 0, 0, .08);
    /* Scroll shadows instead of a visible scrollbar, vertical twin of the
       dock's. A visible scrollbar would eat 11 to 17px of a content box that
       is exactly 76px wide and cut every label short. With at most five
       entries the rail only ever scrolls on a very short window or with a
       filter-grown list. */
    background:
        linear-gradient(var(--cbtt-surface, #fff) 30%, rgba(255, 255, 255, 0)) local top / 100% 18px no-repeat,
        linear-gradient(rgba(255, 255, 255, 0), var(--cbtt-surface, #fff) 70%) local bottom / 100% 18px no-repeat,
        radial-gradient(farthest-side at 50% 0, rgba(0, 0, 0, .13), rgba(0, 0, 0, 0)) scroll top / 100% 9px no-repeat,
        radial-gradient(farthest-side at 50% 100%, rgba(0, 0, 0, .13), rgba(0, 0, 0, 0)) scroll bottom / 100% 9px no-repeat,
        var(--cbtt-surface, #fff);
    /* Shadow from the text colour where color-mix() exists — see the dock. */
    background:
        linear-gradient(var(--cbtt-surface, #fff) 30%, rgba(255, 255, 255, 0)) local top / 100% 18px no-repeat,
        linear-gradient(rgba(255, 255, 255, 0), var(--cbtt-surface, #fff) 70%) local bottom / 100% 18px no-repeat,
        radial-gradient(farthest-side at 50% 0, color-mix(in srgb, var(--cbtt-text, #1d2327) 16%, transparent), transparent) scroll top / 100% 9px no-repeat,
        radial-gradient(farthest-side at 50% 100%, color-mix(in srgb, var(--cbtt-text, #1d2327) 16%, transparent), transparent) scroll bottom / 100% 9px no-repeat,
        var(--cbtt-surface, #fff);
}

.cbtt-sm-rail::-webkit-scrollbar {
    display: none;
}

@media (min-width: 1024px) {
    .cbtt-sm-rail {
        display: flex;
        position: sticky;
        top: calc(var(--cbtt-sm-chrome-top, 0px) + var(--cbtt-sm-rail-gap, 12px));
        /* Kept LOW on purpose. The rail no longer needs to win a stacking
           contest, because it never reaches the header; and should the
           measured edge ever come out too small, 28 against a theme header's
           usual 100 means the rail disappears BEHIND the site navigation
           rather than covering it. Raising this number is the fix that was
           rejected. */
        z-index: 28;
        /* Two ceilings, the lower one wins: 100% is the shaft, so the rail
           cannot grow past the end of the shell and onto the footer; the calc
           is the band between the site chrome and the action bar's strip,
           which comes off at every width because the action face of the bar
           shows on a desktop too. */
        max-height: min(
            100%,
            calc(100vh - var(--cbtt-sm-chrome-top, 0px) - var(--cbtt-sm-rail-gap, 12px) - var(--cbtt-sm-dock-reserve))
        );
        overflow-y: auto;
        /* Explicit, not left to the y-axis: overflow-y:auto next to
           overflow-x:visible computes to auto on BOTH axes (CSS Overflow 3),
           and a horizontal scrollbar in a 76px content box cuts the labels. */
        overflow-x: hidden;
        overscroll-behavior-y: contain;
    }

    /* position:sticky did not survive the host theme. Any ancestor with
       overflow other than visible or clip — overflow-x:hidden on #page or
       .site is a very common WordPress theme rule — becomes the rail's
       scrollport; it never scrolls, so the rail stays at the top of the page
       and, above 1023px where the dock is off, the student scrolls away from
       the only menu there is. assets/js/cbtt-sm-nav.js detects the ancestor
       and sets data-nosticky; the dock, which is position:fixed and does not
       care about ancestor overflow, then takes over at every width. The rail
       hides rather than half-works. */
    .cbtt-sm-shell[data-nosticky] .cbtt-sm-railshaft {
        display: none;
    }

    .cbtt-sm-shell[data-nosticky] .cbtt-sm-actionbar[data-state="nav"] {
        transform: none;
    }

    .cbtt-sm-shell[data-nosticky] .cbtt-sm-actionbar[data-state="nav"] .cbtt-sm-dock {
        display: flex;
    }

    .cbtt-sm-shell[data-nosticky] .cbtt-sm-actionbar[data-state="nav"] .cbtt-sm-actionbar__inner {
        display: none;
    }
}

/* Fixed cell size, so a rail on a short window scrolls instead of squeezing
   every entry a little shorter until the icons touch the labels. min-width:0
   stops a long label from pushing the cell past 76px — a flex item's automatic
   minimum is min-content; "Verfügbarkeiten" breaks at its soft hyphen instead.
   At 12px the German "Abrechnung & Unterlagen" takes three lines here, with
   the "&" on a line of its own — accepted: the rail has the height, and a
   wider rail would cost the reading column its gutter. */
.cbtt-sm-rail .cbtt-sm-nav__item {
    flex: 0 0 auto;
    width: 76px;
    min-width: 0;
    border-radius: var(--cbtt-radius-md, 11px);
}

/* Icon over label, shared by dock and rail — one item style, two containers.
   The icon inherits the item's colour and carries the hover distinction in its
   opacity, so one colour rule tints both halves.

   Body ink, not the muted grey: at the old 10.5px the muted token landed at 3.8:1
   (Solver Blue) / 4.5:1 (Orange) — under AA for small text, and the handoff
   reserves that grey for hints, never for a control's label. */
.cbtt-sm-nav__item {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    min-height: var(--cbtt-dock-h, 61px);
    padding: 6px 4px;
    color: var(--cbtt-text, #1d2327);
    font-size: 12px;
    font-weight: 600;
    line-height: 1.2;
    text-align: center;
    text-decoration: none;
}

/* The rule above sits at (0,1,0), and the host theme's prose-link rule outranks
   it: CBTT-Turbo's `.cbtt-content a` (0,1,1) painted every entry in link colour
   and underlined it, like a link in running text. Customer themes do the same
   through `.entry-content a`. The properties a host rule is likely to set are
   repeated here at (0,2,0), scoped to the shell; Log out's hover rule is
   (0,2,0) as well and comes later, so it still wins. */
.cbtt-sm-shell .cbtt-sm-nav__item {
    padding: 6px 4px;
    color: var(--cbtt-text, #1d2327);
    font-size: 12px;
    font-weight: 600;
    line-height: 1.2;
    text-decoration: none;
}

.cbtt-sm-dock__icon {
    display: block;
    width: 17px;
    height: 17px;
    opacity: .5;
    transition: opacity .15s ease;
}

.cbtt-sm-nav__label {
    display: block;
}

.cbtt-sm-nav__item:hover .cbtt-sm-dock__icon,
.cbtt-sm-nav__item:focus-visible .cbtt-sm-dock__icon,
.cbtt-sm-nav__item--due .cbtt-sm-dock__icon {
    opacity: .95;
}

/* ── The red dot: Availability is due ───────────────────────────────────────

   While a new timetable is being planned and the student has not entered
   their availability (cbtt_availability_due(), the same condition the
   reminder mails go out under), Availability stands first in the menu with
   this dot — and on the reports page, where that entry is left out, on the
   Availability tab. The dot stays until the student has answered (entered
   availability, or chosen a pause) or the new timetable starts. It PULSES for
   ten seconds after every page load (five 2s rounds), then stands still:
   operator decision 2026-09-11, replacing an endless pulse, and the one
   documented exception to UX rule 9.5 ("nothing runs by itself"). WCAG 2.2.2
   (Pause, Stop, Hide) allows motion without a stop control for up to five
   seconds; the ten are a deliberate, recorded deviation (UX-GUIDELINES 9.5).
   It does not pulse at all for anyone who asked for reduced motion (WCAG
   2.3.3); the dot itself stays.

   The dot hangs off the icon (.cbtt-sm-nav__iconwrap), not off the tile: the
   tile centres icon and label vertically, so a dot positioned against the
   tile would sit higher or lower depending on whether the label runs to one
   line or two. Error red rather than the accent: it asks for something, which
   is what the status colours are reserved for (DESIGN-SYSTEM, Low-Distraction
   1). The ring in the surface colour keeps it legible where it overlaps the
   glyph. The pulse is a second, fading copy scaled up behind it — transform
   and opacity, nothing that moves layout. */
.cbtt-sm-nav__iconwrap {
    position: relative;
    display: block;
    line-height: 0;
}

.cbtt-sm-nav__dot {
    position: relative;
    display: inline-block;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--cbtt-error, #d9534f);
    box-shadow: 0 0 0 2px var(--cbtt-surface, #fff);
}

.cbtt-sm-nav__iconwrap .cbtt-sm-nav__dot {
    position: absolute;
    top: -3px;
    inset-inline-end: -5px;
}

.cbtt-sm-dock .cbtt-sm-nav__dot {
    box-shadow: 0 0 0 2px var(--cbtt-surface-alt, #f8f9fa);
}

.cbtt-sm-tab .cbtt-sm-nav__dot {
    margin-inline-start: 6px;
    vertical-align: 1px;
}

.cbtt-sm-nav__dot::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: inherit;
    /* Hidden at rest, so that when the five rounds are over (no fill-mode:
       the copy falls back to this) nothing is left over the dot. */
    opacity: 0;
    animation: cbtt-sm-due-pulse 2s ease-out 5;
}

/* Ten seconds per PAGE VIEW, not per appearance: a CSS animation restarts
   whenever its box comes back from display:none — the dock does that each
   time a lesson is selected and deselected, and on every resize across
   1024px. cbtt-sm-nav.js sets this attribute ten seconds after load; from
   then on the dot stays still whatever reappears. Without the script the
   five rounds still end on their own. */
:root[data-cbtt-sm-pulsed] .cbtt-sm-nav__dot::after {
    animation: none;
}

@keyframes cbtt-sm-due-pulse {
    0%   { transform: scale(1);   opacity: .7; }
    70%  { transform: scale(2.4); opacity: 0; }
    100% { transform: scale(2.4); opacity: 0; }
}

/* Windows High Contrast and other forced-colour modes replace every
   background with the page colour and drop box-shadow: the dot — a background
   and a ring, nothing else — would simply vanish. CanvasText is the one colour
   such a theme guarantees to stand out from Canvas; ::after inherits it, so
   the pulse follows (and still stops under reduced motion). */
@media (forced-colors: active) {
    .cbtt-sm-nav__dot {
        forced-color-adjust: none;
        background: CanvasText;
        box-shadow: 0 0 0 2px Canvas;
    }
}

/* Words for what the dot says, for a screen reader. The shell carries its own
   copy of the pattern because WordPress' .screen-reader-text is a theme's to
   provide, and not every theme does.

   Absolutely positioned, so it needs a positioned ancestor INSIDE whatever
   scrolls it: an overflow container only clips what it contains. The reports
   tab is made one (.cbtt-sm-tab, position:relative) — without that the span's
   box sat at its static place behind the fifth tab, outside the tab bar's
   scroll clip, and widened the whole page on a phone under any theme that
   does not clip html/body itself. The dock and the rail are fixed and sticky,
   so their copies are contained already. */
.cbtt-sm-shell .cbtt-sm-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Signing out is not one more section. It leaves the product, so it is set off
   by a rule — the same reason a browser puts "Quit" behind a separator — but
   NOT dimmed: opacity:.72 over the muted ink at 10.5px landed around 2.6:1,
   and on a shared or borrowed device this is the control that matters most. */
.cbtt-sm-dock .cbtt-sm-nav__item--logout {
    margin-left: 4px;
    border-left: 1px solid var(--cbtt-border-soft, #eee);
}

.cbtt-sm-rail .cbtt-sm-nav__item--logout {
    margin-top: 6px;
    padding-top: 10px;
    border-top: 1px solid var(--cbtt-border-soft, #eee);
}

.cbtt-sm-shell .cbtt-sm-nav__item--logout:hover,
.cbtt-sm-shell .cbtt-sm-nav__item--logout:focus-visible {
    color: var(--cbtt-accent-ink, #086b7e);
}

@media (prefers-reduced-motion: reduce) {
    .cbtt-sm-dock__icon {
        transition: none;
    }

    .cbtt-sm-nav__dot::after {
        animation: none;
        opacity: 0;
    }
}

/* The theme's floating accessibility button.
   CBTT-Turbo pins .cbtt-a11y-btn to bottom:1.5rem right:1.5rem at z-index 9999,
   while the dock sits 0–61px up at z-index 1030 — so the button landed on the
   last dock entry and swallowed its taps. The theme now resolves its position
   from --cbtt-a11y-bottom / --cbtt-a11y-right / --cbtt-a11y-left (defaults
   1.5rem / 1.5rem / auto); move it clear of whatever bar is on screen. :has()
   scopes this to pages that really carry the bar, so the variables never leak
   onto a page without one.

   Lifting alone is not enough: every lesson row puts its Book/Cancel button
   flush right, so a bottom-RIGHT button lands on one of them at almost any
   scroll position. The left column carries only the date and duration, so the
   button moves there for the width where the dock exists — which is the same
   1023px the dock itself uses, not 767. Leaving these two breakpoints out of
   step is how the collision would quietly come back on tablets. (1023.98 for
   the fractional-width reason given at the dock's own query.)

   The lift is the shell's reserved strip, --cbtt-dock-h + 16px = 61 + 16px,
   as a literal because --cbtt-dock-h lives on .cbtt-sm-shell, not on :root —
   change the three literals below together with it. Sized to the strip, not
   to the bar: a three-line label (≈75px) still fits the strip, and any bar
   that fits the strip clears the button too. */
@media (max-width: 1023.98px) {
    :root:has(.cbtt-sm-actionbar) {
        --cbtt-a11y-bottom: calc(61px + env(safe-area-inset-bottom, 0px) + 16px);
        --cbtt-a11y-right: auto;
        --cbtt-a11y-left: 1rem;
    }
}

/* The action state shows a bar at every width, not only on mobile — so this one
   is not inside the media query. */
:root:has(.cbtt-sm-actionbar[data-state="action"]) {
    --cbtt-a11y-bottom: calc(61px + env(safe-area-inset-bottom, 0px) + 16px);
}

/* The dock standing in for a rail that could not stick (data-nosticky, Zone M)
   is the same bar at a desktop width, and the button would land on its last
   entry just as it does on a phone. */
:root:has(.cbtt-sm-shell[data-nosticky] .cbtt-sm-actionbar[data-state="nav"]) {
    --cbtt-a11y-bottom: calc(61px + env(safe-area-inset-bottom, 0px) + 16px);
    --cbtt-a11y-right: auto;
    --cbtt-a11y-left: 1rem;
}

@media (prefers-reduced-motion: reduce) {
    .cbtt-sm-actionbar {
        transition: none;
    }
}

.cbtt-sm-actionbar__inner {
    max-width: 720px;
    margin-inline: auto;
}

.cbtt-sm-actionbar__sum {
    margin: 0;
    padding: 6px 16px 0;
    font-size: 12.5px;
    line-height: 1.35;
    color: var(--cbtt-text-muted, #777);
    text-align: center;
}

.cbtt-sm-actionbar__sum:empty {
    display: none;
}

.cbtt-sm-wrapup-invite {
    max-width: 720px;
    margin: 24px auto 0;
    padding-inline: 16px;
}

.cbtt-sm-actionbar__btn {
    display: block;
    width: 100%;
    min-height: 44px;
    padding: 10px 14px;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    font-weight: 600;
    cursor: pointer;
}

/* Zone H — quick access — is gone. The four tiles under the schedule pointed
   at availability, the reports page, the contract panel and the account panel.
   The reports page and its tabs (availability and account among them) are
   reached through the page entries in the dock and the rail, permanently on
   screen instead of at one particular scroll position; the contract panel is
   on the calendar page itself. */

.cbtt-sm-dashboard,
.cbtt-sm-panel {
    max-width: 720px;
    margin-inline: auto;
    padding-inline: 16px;
}

/* ── Zone I — contract panel ───────────────────────────────────────────── */

.cbtt-sm-panel {
    margin-top: 12px;
}

/* A panel has to READ as a control. Left bare it looked like a stray heading
   sitting under the tiles, and nothing suggested it could be opened — which is
   the whole point of moving the contract and account details in here. */
.cbtt-sm-panel__summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    min-height: 48px;
    padding: 0 14px;
    border: 1px solid var(--cbtt-border, #ddd);
    border-radius: var(--cbtt-radius-md, 4px);
    background: var(--cbtt-surface, #fff);
    font-size: 15px;
    font-weight: 600;
    color: var(--cbtt-text, #1d2327);
    cursor: pointer;
    list-style: none;
}

/* Hide the native marker and draw our own on the right, so it stays put in
   both reading directions and does not shift the label. */
.cbtt-sm-panel__summary::-webkit-details-marker {
    display: none;
}

.cbtt-sm-panel__summary::after {
    content: '';
    flex: none;
    width: 8px;
    height: 8px;
    border-right: 2px solid var(--cbtt-text-muted, #777);
    border-bottom: 2px solid var(--cbtt-text-muted, #777);
    transform: rotate(45deg) translate(-2px, -2px);
}

.cbtt-sm-panel[open] > .cbtt-sm-panel__summary::after {
    transform: rotate(225deg) translate(-2px, -2px);
}

/* Open: the summary keeps its box, the content sits underneath with a matching
   indent. No second border around the whole panel — .cbtt-sm-panel already carries
   the 16 px reading inset, so a border there would sit outside the summary's
   and read as two nested frames.

   The content used to add another 14 px on top of the panel's own 16, so an
   open panel's text sat at 30 px while its summary and every lesson row above
   it sat at 16. With two panels open that is the ragged left edge. The panel's
   inset is the only one; the content aligns with the summary. */
.cbtt-sm-panel[open] > .cbtt-sm-panel__summary {
    margin-bottom: 4px;
}

.cbtt-sm-panel__empty {
    margin: 0 0 12px;
    font-size: 14px;
    color: var(--cbtt-text-muted, #777);
}

.cbtt-sm-facts {
    margin: 0 0 18px;
}

.cbtt-sm-fact {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 2px;
    padding: 10px 0;
    border-bottom: 1px solid var(--cbtt-border-soft, #eee);
}

@media (min-width: 560px) {
    .cbtt-sm-fact {
        grid-template-columns: 220px minmax(0, 1fr);
        gap: 16px;
        align-items: baseline;
    }
}

.cbtt-sm-fact dt {
    font-size: 13px;
    font-weight: 600;
    color: var(--cbtt-text-muted, #777);
}

.cbtt-sm-fact dd {
    margin: 0;
    font-size: 14.5px;
    color: var(--cbtt-text, #1d2327);
}

.cbtt-sm-fact__label {
    margin: 18px 0 6px;
    font-size: 13px;
    font-weight: 600;
    color: var(--cbtt-text-muted, #777);
}

/* The cancellation-credit meter. The previous dashboard shipped an empty
   .mana-liquid-fill div with no CSS rule anywhere — the right idea, never
   built. This is that idea, as a plain bar. */
.cbtt-sm-meter {
    height: 6px;
    margin-top: 6px;
    border-radius: 999px;
    background: var(--cbtt-border-soft, #eee);
    overflow: hidden;
}

.cbtt-sm-meter span {
    display: block;
    height: 100%;
    background: var(--cbtt-accent, #0193aa);
}

.cbtt-sm-subpanel {
    margin: 14px 0;
    padding-top: 12px;
    border-top: 1px solid var(--cbtt-border-soft, #eee);
}

.cbtt-sm-subpanel > summary {
    display: flex;
    align-items: center;
    min-height: 44px;
    font-size: 14px;
    font-weight: 600;
    color: var(--cbtt-link, #086b7e);
    cursor: pointer;
}

/* Price tables are wide. Let them scroll inside their own box rather than
   pushing the page sideways. */
.cbtt-sm-subpanel table {
    display: block;
    max-width: 100%;
    overflow-x: auto;
}

.cbtt-sm-panel__bank {
    margin: 0 0 10px;
    font-size: 14px;
    line-height: 1.5;
}

.cbtt-sm-panel__pay .cbtt-sm-btn {
    max-width: 320px;
}

.cbtt-sm-qr-missing {
    margin: 12px 0;
    font-size: 13.5px;
    color: var(--cbtt-text-muted, #777);
}

/* ── Zone J — account forms ────────────────────────────────────────────── */

/* Five independent groups, each saved on its own. One giant form would make a
   postcode change re-submit the password fields, and one validation error
   would reject the lot. */
.cbtt-sm-form {
    padding: 16px 0;
    border-bottom: 1px solid var(--cbtt-border-soft, #eee);
}

/* Inside the Account tab the panel IS the page, so the five groups become five
   cards on a grid instead of one long ribbon of hairline-separated blocks. As a
   <details> on the schedule page they stay a plain stack — there the panel is
   one section among many and must not compete with the lessons.

   The grid is what fixes the tab: at 960 px the forms were a single column of
   short fields with an 8 cm gutter of empty space beside them, and the eye had
   to travel the full height of the page to find the field it wanted. */
#cbtt-sm-account[open] {
    border: 0;
    padding: 0;
}

.cbtt-sm-reports #cbtt-sm-account[open] > summary {
    /* The tab click already was the disclosure. A second control saying
       "My account" directly under a tab saying "Account" is noise. */
    display: none;
}

/* [open] guard: display:grid on a CLOSED <details> would lay out the collapsed
   element as a grid container and defeat the browser's own hiding of the
   contents. The only caller inside .cbtt-sm-reports renders it open, but the rule
   must not depend on that staying true. */
.cbtt-sm-reports #cbtt-sm-account[open] {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    align-items: start;
    gap: 16px;
}

.cbtt-sm-reports #cbtt-sm-account[open] > .cbtt-sm-form {
    margin: 0;
    padding: 16px 18px;
    border: 1px solid var(--cbtt-border, #ddd);
    border-radius: var(--cbtt-radius-md, 10px);
    background: var(--cbtt-surface, #fff);
}

/* Full-width items: the confirmation notice and the closing link are about the
   panel as a whole, not about one card in it. */
.cbtt-sm-reports #cbtt-sm-account[open] > .cbtt-sm-form__ok,
.cbtt-sm-reports #cbtt-sm-account[open] > .cbtt-sm-form__hint {
    grid-column: 1 / -1;
    margin: 0;
}

/* The field cap exists so a text input does not run to 900 px on a desktop.
   Inside a card the card already caps it, and the leftover max-width just left
   a ragged right edge in every box. */
.cbtt-sm-reports #cbtt-sm-account[open] .cbtt-sm-form :is(input, select) {
    max-width: none;
}

.cbtt-sm-reports #cbtt-sm-account[open] .cbtt-sm-form .cbtt-sm-btn {
    max-width: none;
    width: 100%;
}

.cbtt-sm-form__title {
    margin: 0 0 10px;
    font-size: 15px;
    font-weight: 600;
    color: var(--cbtt-text, #1d2327);
}

.cbtt-sm-form label {
    display: block;
    margin-bottom: 10px;
    font-size: 13px;
    font-weight: 600;
    color: var(--cbtt-text-muted, #777);
}

.cbtt-sm-form input[type="text"],
.cbtt-sm-form input[type="email"],
.cbtt-sm-form input[type="password"],
.cbtt-sm-form input[type="number"],
.cbtt-sm-form select {
    display: block;
    width: 100%;
    max-width: 420px;
    min-height: 44px;
    margin-top: 4px;
    padding: 9px 11px;
    border: 1px solid var(--cbtt-border, #ddd);
    border-radius: var(--cbtt-radius-sm, 4px);
    background: var(--cbtt-surface, #fff);
    color: var(--cbtt-text, #1d2327);
    font: inherit;
    font-size: 15px; /* below 16px iOS zooms the whole page on focus */
    font-weight: 400;
}

.cbtt-sm-form__check {
    display: flex;
    align-items: center;
    gap: 9px;
    font-weight: 400;
    color: var(--cbtt-text, #1d2327);
}

.cbtt-sm-form__check input {
    width: 20px;
    height: 20px;
    margin: 0;
}

.cbtt-sm-form__hint {
    margin: 0 0 10px;
    max-width: var(--cbtt-measure);
    font-size: 12.5px;
    line-height: 1.45;
    color: var(--cbtt-text-muted, #777);
}

.cbtt-sm-form .cbtt-sm-btn {
    max-width: 260px;
    margin-top: 4px;
}

.cbtt-sm-form__status {
    margin: 8px 0 0;
    min-height: 1.4em;
    font-size: 13px;
}

.cbtt-sm-form__ok {
    color: var(--cbtt-success-ink, #075008);
}

.cbtt-sm-form__err {
    color: var(--cbtt-error-ink, #721c24);
}

/* ── Reports page ──────────────────────────────────────────────────────── */

/* Same shell as the lesson page, so a student does not cross a visual border
   halfway through their own account. border-box declared here rather than
   left to the theme's reset: the rail strip below widens max-width and the
   padding by the same amount, which keeps the content at 928px only when
   max-width measures the border box. */
.cbtt-sm-reports {
    box-sizing: border-box;
    max-width: 960px;
    margin-inline: auto;
    padding-inline: 16px;
}

.cbtt-sm-reports .cbtt-sm-appbar {
    margin-bottom: 8px;
}

/* The rail on the reports page (Zone M). This page is not a 720px reading
   column like the calendar and the feedback page: its tab bar and its tables
   use the whole 960px shell, so the shaft's "50% − 360px" would put the rail
   straight across the tabs. Here the shell makes room for it instead — a
   left padding exactly as wide as rail plus gap — and grows by the same
   amount where the theme's container allows, so the content keeps its 928px
   on a wide window and gives up the difference only on a narrow one. The
   shaft then fills that strip, and the rail stands against the content with
   the usual 12px. */
@media (min-width: 1024px) {
    .cbtt-sm-reports.cbtt-sm-shell--rail {
        max-width: calc(960px + var(--cbtt-sm-rail-w, 90px) + var(--cbtt-sm-rail-gap, 12px));
        padding-inline-start: calc(16px + var(--cbtt-sm-rail-w, 90px) + var(--cbtt-sm-rail-gap, 12px));
    }

    .cbtt-sm-reports .cbtt-sm-railshaft {
        inset-inline-end: auto;
        width: calc(16px + var(--cbtt-sm-rail-w, 90px));
    }

    /* The dock standing in for a rail that could not stick needs no strip. */
    .cbtt-sm-reports.cbtt-sm-shell--rail[data-nosticky] {
        max-width: 960px;
        padding-inline-start: 16px;
    }
}

.cbtt-sm-tabs {
    display: flex;
    flex-wrap: nowrap;
    gap: 4px;
    margin: 0 0 20px;
    padding: 0 0 2px;
    list-style: none;
    border-bottom: 1px solid var(--cbtt-border, #ddd);
    /* Seven tabs do not fit on a phone. Scrolling them beats wrapping into
       three ragged rows that push the content off the first screen. */
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: thin;
    -webkit-overflow-scrolling: touch;
}

.cbtt-sm-tabs > li {
    flex: none;
    margin: 0;
}

/* ── Availability tab — when it was last submitted ─────────────────────── */

/* Sits directly above the solver's own form, which brings its own look. This
   line is deliberately quiet: it is an answer to "did I already do this?", not
   a call to action, and it must not compete with the grid underneath it. */
.cbtt-sm-lastupdate {
    margin: 0 0 14px;
    padding: 9px 13px;
    border-left: 3px solid var(--cbtt-border, #ddd);
    background: var(--cbtt-surface-alt, #f8f9fa);
    color: var(--cbtt-text-muted, #777);
    font-size: 13px;
    line-height: 1.45;
}

.cbtt-sm-lastupdate time {
    font-weight: 600;
    color: var(--cbtt-text, #1d2327);
}

/* ── "My data" tab — Art. 15 self-service ──────────────────────────────── */

/* The panel is a legal disclosure, so it opens by saying so. Styled as a quiet
   note rather than a warning: nothing is wrong, and a tinted alert box would
   imply otherwise. */
.cbtt-sm-legalnote {
    margin: 0 0 20px;
    padding: 14px 16px;
    border: 1px solid var(--cbtt-border, #ddd);
    border-radius: var(--cbtt-radius-md, 10px);
    background: var(--cbtt-surface-alt, #f8f9fa);
}

.cbtt-sm-legalnote__title {
    margin: 0 0 8px;
    font-size: 14px;
    font-weight: 600;
    color: var(--cbtt-text, #1d2327);
}

.cbtt-sm-legalnote p {
    margin: 0 0 8px;
    font-size: 13.5px;
    line-height: 1.55;
    color: var(--cbtt-text, #1d2327);
}

.cbtt-sm-legalnote p:last-child {
    margin-bottom: 0;
}

.cbtt-sm-legalnote__more {
    color: var(--cbtt-text-muted, #777) !important;
    font-size: 12.5px !important;
}

.cbtt-sm-my-data__actions {
    margin: 0 0 22px;
}

.cbtt-sm-my-data__group {
    margin: 26px 0 10px;
    font-size: 15px;
    font-weight: 600;
    color: var(--cbtt-text, #1d2327);
}

.cbtt-sm-my-data__note {
    margin: -4px 0 12px;
    font-size: 13px;
    line-height: 1.5;
    color: var(--cbtt-text-muted, #777);
}

.cbtt-sm-my-data .cbtt-sm-tablewrap + .cbtt-sm-tablewrap {
    margin-top: 10px;
}

/* A label/value table, not a data grid: the label column is a row header, so it
   gets the muted treatment .cbtt-sm-table reserves for thead — which this table
   does not have. Sized in ch so the column tracks the text rather than a guess
   in pixels, and allowed to wrap: the old markup pinned it with
   white-space:nowrap, and a long field name then pushed the value itself off a
   phone screen. */
.cbtt-sm-table--fields th[scope="row"] {
    width: 34%;
    min-width: 11ch;
    color: var(--cbtt-text-muted, #777);
    font-size: 12.5px;
    font-weight: 600;
    vertical-align: top;
    overflow-wrap: anywhere;
}

.cbtt-sm-table--fields td {
    vertical-align: top;
    overflow-wrap: anywhere;
}

.cbtt-sm-table--fields tr:last-child > * {
    border-bottom: 0;
}

.cbtt-sm-my-data__img {
    max-width: 260px;
    max-height: 120px;
    height: auto;
    border: 1px solid var(--cbtt-border, #ddd);
    border-radius: var(--cbtt-radius-sm, 4px);
}

/* An absent value is stated, not left as blank space — otherwise a student
   cannot tell "nothing is stored" from "the page failed to print it". */
.cbtt-sm-my-data__empty {
    color: var(--cbtt-text-muted, #777);
    font-style: italic;
}

/* ── Zone K — feedback ─────────────────────────────────────────────────── */

.cbtt-sm-feedback {
    margin-top: 32px;
    padding-top: 20px;
    border-top: 1px solid var(--cbtt-border-soft, #eee);
}

.cbtt-sm-feedback__intro {
    margin: 0 0 8px;
    font-size: 14px;
    color: var(--cbtt-text-muted, #777);
}

.cbtt-sm-feedback__links {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 20px;
    margin: 0;
}

.cbtt-sm-feedback__links a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    color: var(--cbtt-link, #086b7e);
    font-size: 14px;
    font-weight: 600;
}

.cbtt-sm-feedback__logout {
    margin: 12px 0 0;
    padding-top: 12px;
    border-top: 1px solid var(--cbtt-border-soft, #eee);
}

.cbtt-sm-feedback__logout a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    color: var(--cbtt-text-muted, #777);
    font-size: 14px;
    font-weight: 600;
}

/* ── Theme link styling must not leak into components ──────────────────── */

/* The plugin renders inside somebody else's theme, and CBTT-Turbo underlines
   content links and paints them in its link colour. That is right for prose and
   wrong for a chip or a button: they came out looking like a list of
   hyperlinks rather than controls, which is the single largest visual
   difference between the design and the built page.
   Scoped to the components, so real text links in zones G and K keep the
   theme's treatment — there they SHOULD look like links. */
.cbtt-sm-shell :is(.cbtt-sm-chip, .cbtt-sm-btn, .cbtt-sm-row__btn, .cbtt-sm-fold__summary),
.cbtt-sm-shell :is(.cbtt-sm-chip, .cbtt-sm-btn, .cbtt-sm-row__btn):hover,
.cbtt-sm-shell :is(.cbtt-sm-chip, .cbtt-sm-btn, .cbtt-sm-row__btn):focus {
    text-decoration: none;
    box-shadow: none;
}

/* Panel and disclosure summaries are controls too. */
.cbtt-sm-shell summary {
    list-style-position: outside;
}

.cbtt-sm-shell summary::-webkit-details-marker {
    color: var(--cbtt-text-muted, #777);
}

/* ── Focus ─────────────────────────────────────────────────────────────── */

/* The student front end had no visible focus style at all — the only
   focus-ring rule in the plugin applied to wp-admin inputs. */
.cbtt-sm-shell :is(a, button, [tabindex]):focus-visible {
    outline: 2px solid var(--cbtt-accent, #0193aa);
    outline-offset: 2px;
    box-shadow: var(--cbtt-focus-ring, 0 0 0 2px rgba(0, 115, 170, .25));
    border-radius: var(--cbtt-radius-sm, 4px);
}

/* ── Motion ────────────────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
    .cbtt-sm-shell *,
    .cbtt-sm-shell *::before,
    .cbtt-sm-shell *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
    }
}

/* ── Small screens ─────────────────────────────────────────────────────── */

@media (max-width: 480px) {
    .cbtt-sm-appbar__title {
        font-size: 14px;
    }

    .cbtt-sm-chip {
        padding: 6px 9px;
        font-size: 12.5px;
    }

    /* Two chips plus a title do not fit on a 375 px bar once the balance chip
       grows a call to action. The word goes, the warning icon and the negative
       figure stay — and the chip's aria-label carries the full sentence, so
       nothing is lost for anyone who cannot see the colour or the triangle.
       Truncating the page title instead produced "Meine Stun…". */
    .cbtt-sm-chip__cta {
        display: none;
    }
}

/* ═══════════════════════════════════════════════════════════════════════════
   REPORTS PAGE — tabs, filters, transaction list, annual report, calculator

   The navigation's Billing & records and My account entries (and Availability
   while it is due) land on /student-reports/, which until now still looked
   like the Bootstrap page it started as: card/card-header,
   btn btn-primary, table, progress-bar, plus three <style> blocks echoed from
   reporting/reporting.php carrying hardcoded #e45c00, #f8f9fa and #c77dff.
   That markup now carries cbtt-sm-* class names and everything it needs is here,
   in the tokens, so a teacher who reskins the plugin reskins this page too.

   Nothing above this line was changed or removed.

   Two rules for the whole section, both from the page they were learnt on:
   • State is COLOUR. No selected tab, filter or row changes a border width, a
     padding or a font weight — that moves everything below it on every click.
   • Anything wider than the screen scrolls inside its own box. The tab bar and
     the tables have their own overflow container; the document never scrolls
     sideways.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Scoped by class name rather than by a .cbtt-sm-shell ancestor, deliberately:
   cbtt_sm_show_accounting_details() also renders inside wp-admin, where there
   is no shell wrapper. The cbtt-sm- prefix is the scope. */

/* ── Shared bits ───────────────────────────────────────────────────────── */

.cbtt-sm-section__title {
    margin: 0 0 14px;
    font-size: 17px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--cbtt-text, #1d2327);
}

/* Replaces Bootstrap's .alert. One quiet box; the level is a modifier, and the
   default carries no colour at all — most of these are statements of fact
   ("no bookings found"), not warnings. */
.cbtt-sm-notice {
    margin: 0 0 14px;
    padding: 10px 14px;
    border: 1px solid var(--cbtt-border, #ddd);
    border-left: 3px solid var(--cbtt-border, #ddd);
    border-radius: var(--cbtt-radius-md, 10px);
    background: var(--cbtt-surface-alt, #f8f9fa);
    color: var(--cbtt-text, #1d2327);
    font-size: 13.5px;
    line-height: 1.45;
}

.cbtt-sm-notice--warning {
    border-color: var(--cbtt-warning, #f0ad4e);
    background: var(--cbtt-warning-tint, #fff3cd);
    color: var(--cbtt-warning-ink, #856404);
}

.cbtt-sm-notice--error {
    border-color: var(--cbtt-error, #d9534f);
    background: var(--cbtt-error-tint, #f8d7da);
    color: var(--cbtt-error-ink, #721c24);
}

.cbtt-sm-note {
    font-size: 12.5px;
    color: var(--cbtt-text-muted, #777);
}

/* Location-icon legend (util/business_info.php) — replaces the Bootstrap
   .alert-info/.row/.col trio that no stylesheet defined any more. It rides on
   .cbtt-sm-notice for the box; inside a list-foot disclosure the box is taken
   back, because the <details> already frames it. */
.cbtt-sm-legend {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 24px;
}

.cbtt-sm-legend__item {
    display: flex;
    align-items: center;
    gap: 10px;
}

.cbtt-sm-legend__item img {
    width: 24px;
    height: 24px;
    flex-shrink: 0;
}

.cbtt-sm-listfoot__item .cbtt-sm-legend {
    margin: 8px 0 0;
    padding: 0;
    border: 0;
    background: transparent;
}

/* Every column of figures on this page. Digits of equal width are what let the
   eye compare two amounts by their length instead of reading both. */
.cbtt-sm-num {
    font-variant-numeric: tabular-nums;
}

/* .cbtt-sm-btn is a full-width block button (zone D). Reports needs the same
   button inside a table cell and next to a paragraph, so this modifier only
   takes back the width and the top margin — no second button style. */
.cbtt-sm-btn--inline {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: auto;
    min-height: 40px;
    margin-top: 0;
    padding: 8px 14px;
    font-size: 13px;
}

/* A pressed toggle (the admin "Edit comments" switch). Tint, not fill: white
   on the accent lands near 3.6:1, which is under AA for a 13px label. */
.cbtt-sm-btn.is-active {
    background: var(--cbtt-accent-tint, #e6f4f3);
    border-color: var(--cbtt-accent, #0193aa);
    color: var(--cbtt-accent-ink, #086b7e);
}

/* ── Tabs ──────────────────────────────────────────────────────────────── */

/* The bar keeps the underline shape declared further up in this file. What
   changes here: it is addressed by .cbtt-sm-tab instead of Bootstrap's .nav-link,
   so the framework is out of the picture entirely — the rules that used to
   out-specify it have been deleted along with the markup they fought over.

   The scrollbar is hidden for the same reason as in .cbtt-sm-dock — it would sit
   on the bar's own bottom rule — and a half-cut tab at the right edge is the
   affordance that says there is more this way. */
.cbtt-sm-tabs {
    scrollbar-width: none;
    -ms-overflow-style: none;
    scroll-snap-type: x proximity;
}

.cbtt-sm-tabs::-webkit-scrollbar {
    display: none;
}

.cbtt-sm-tabs > li {
    scroll-snap-align: start;
}

/* position:relative holds the due tab's screen-reader text inside the tab
   bar's scroll clip (see .cbtt-sm-sr-only). */
.cbtt-sm-tab {
    position: relative;
    display: block;
    padding: 11px 14px;
    /* The bottom rule is always 2px and always there. Only its colour changes
       with the state, so selecting a tab cannot shift the bar by a pixel. */
    border: 0;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    background: none;
    color: var(--cbtt-text-muted, #777);
    font-size: 14px;
    font-weight: 600;
    line-height: 1.3;
    white-space: nowrap;
    text-decoration: none;
    cursor: pointer;
}

.cbtt-sm-tab:hover {
    color: var(--cbtt-text, #1d2327);
    border-bottom-color: var(--cbtt-border, #ddd);
    text-decoration: none;
}

.cbtt-sm-tab.is-active {
    color: var(--cbtt-accent-ink, #086b7e);
    border-bottom-color: var(--cbtt-accent, #0193aa);
    background: none;
}

/* The theme underlines and recolours content links; a tab is a control. */
.cbtt-sm-shell .cbtt-sm-tab,
.cbtt-sm-shell .cbtt-sm-tab:hover,
.cbtt-sm-shell .cbtt-sm-tab:focus {
    text-decoration: none;
    box-shadow: none;
}

/* A horizontally scrolling container clips on both axes, so a ring drawn 2px
   outside the tab loses its top and bottom edge. Same fix as the dock: draw it
   inside. */
.cbtt-sm-shell .cbtt-sm-tabs .cbtt-sm-tab:focus-visible,
.cbtt-sm-tabs .cbtt-sm-tab:focus-visible {
    outline: 2px solid var(--cbtt-accent, #0193aa);
    outline-offset: -3px;
    box-shadow: none;
}

.cbtt-sm-tabpanels {
    margin-top: 16px;
}

/* Hidden panels carry the `hidden` attribute (set by PHP, toggled by the tab
   script). Stated explicitly because a theme rule such as "[hidden]{display:
   block}" is common enough, and because any element given a display value of
   its own would otherwise ignore the attribute completely. */
.cbtt-sm-tabpanel[hidden] {
    display: none;
}

/* ── Transaction filter ────────────────────────────────────────────────── */

/* The filter is a tool for the list below it, not a section of its own: no
   fill, no box, just a label and a row of controls over a hairline. */
.cbtt-sm-filters {
    margin: 0 0 18px;
    padding: 0 0 14px;
    border-bottom: 1px solid var(--cbtt-border-soft, #eee);
}

.cbtt-sm-filters__title {
    margin: 0 0 8px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--cbtt-text-muted, #777);
}

/* min-width:0 lets the row shrink inside its grid/flex parent, and the chips
   may wrap. Without both, one long chip — "Time of cancellation by <teacher>"
   carries the studio name verbatim — pushes past the viewport and scrolls the
   whole document sideways, which this section's own rule forbids. */
.cbtt-sm-filters__row {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    min-width: 0;
}

.cbtt-sm-filter {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: 40px;
    padding: 8px 12px;
    border: 1px solid var(--cbtt-border, #ddd);
    border-radius: 999px;
    background: var(--cbtt-surface, #fff);
    color: var(--cbtt-text-muted, #777);
    font: inherit;
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1.3;
    /* Was nowrap. One chip carries the teacher's name verbatim, so a studio
       name of any length made a single chip wider than the phone and took the
       document sideways with it. Wrapping inside the chip keeps the pill shape
       and the row responsive; max-width stops any one chip from owning the row. */
    max-width: 100%;
    overflow-wrap: anywhere;
    text-align: left;
    cursor: pointer;
}

.cbtt-sm-filter:hover {
    color: var(--cbtt-text, #1d2327);
    border-color: var(--cbtt-text-muted, #777);
}

/* Selected: tint plus accent ink, ≈5:1 against the tint. The old rule filled
   the button with #e45c00 and wrote on it in white, which is 3.6:1. Border
   width, padding and weight are untouched, so the row never reflows. */
.cbtt-sm-filter.is-active {
    background: var(--cbtt-accent-tint, #e6f4f3);
    border-color: var(--cbtt-accent, #0193aa);
    color: var(--cbtt-accent-ink, #086b7e);
}

/* The icons are outlines drawn in currentColor, so they take the button's own
   ink in every state — including the selected one — and are never filled.
   fill/stroke are restated here rather than trusted to the file: a theme rule
   as ordinary as "svg { fill: currentColor }" would otherwise turn every one
   of them into a solid blob, since the shapes inherit fill from the root.

   !important is not decoration: cbtt_sm_show_svg() writes width/height/transform
   into a style attribute on the element, and nothing but !important outranks
   that. css/admin_backend.css resizes the same icons the same way. */
.cbtt-sm-filter__icon {
    display: inline-flex;
    flex: none;
}

.cbtt-sm-filter__icon .svg-icon,
.cbtt-sm-txrow__icon .svg-icon {
    fill: none;
    stroke: currentColor;
    color: inherit;
    width: 1.15em !important;
    height: 1.15em !important;
    transform: none !important;
    vertical-align: middle;
}

.cbtt-sm-filter__label {
    min-width: 0;
}

/* ── Transaction list ──────────────────────────────────────────────────── */

.cbtt-sm-txlist {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.cbtt-sm-txlist__admin {
    margin: 0 0 12px;
}

/* One card per point in time, same card as every other row in the product:
   1px border, one radius, the surface colour. The accent edge marks it as an
   entry in the student's own ledger rather than a notice about it. */
.cbtt-sm-txcard {
    border: 1px solid var(--cbtt-border, #e6e3dd);
    border-left: 3px solid var(--cbtt-accent, #0193aa);
    border-radius: var(--cbtt-radius-md, 10px);
    background: var(--cbtt-surface, #fff);
    overflow: hidden;
}

/* The filter hides cards with the hidden attribute; it used to set
   display:none / display:block, which flattened the card the moment a filter
   was cleared. */
.cbtt-sm-txcard[hidden] {
    display: none;
}

.cbtt-sm-txcard__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 9px 14px;
    border-bottom: 1px solid var(--cbtt-border-soft, #eee);
    background: var(--cbtt-surface-alt, #f8f9fa);
}

.cbtt-sm-txcard__date {
    margin: 0;
    font-size: 13px;
    font-weight: 700;
    line-height: 1.3;
    color: var(--cbtt-text, #1d2327);
    font-variant-numeric: tabular-nums;
}

.cbtt-sm-badge {
    display: inline-block;
    flex: none;
    padding: 2px 9px;
    border-radius: 999px;
    background: var(--cbtt-accent-tint, #e6f4f3);
    color: var(--cbtt-accent-ink, #086b7e);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .02em;
    white-space: nowrap;
}

.cbtt-sm-badge--live {
    background: var(--cbtt-warning-tint, #fff3cd);
    color: var(--cbtt-warning-ink, #856404);
}

.cbtt-sm-badge--done {
    background: var(--cbtt-success-tint, #d9f0e1);
    color: var(--cbtt-success-ink, #075008);
}

.cbtt-sm-txcard__body {
    padding: 2px 14px;
}

.cbtt-sm-txentry {
    padding: 10px 0;
}

/* The rule between two entries of one group. It replaces an <hr>, which was an
   element in the flow that the filter could not hide with its card. */
.cbtt-sm-txentry + .cbtt-sm-txentry {
    border-top: 1px solid var(--cbtt-border-soft, #eee);
}

/* icon · what happened · what it cost. The icon has a column of its own so
   every label starts on the same vertical line however long the line above it
   ran — the "HACK: use grid layout here to align text" note in reporting.php. */
.cbtt-sm-txrow {
    display: grid;
    grid-template-columns: 22px minmax(0, 1fr) auto;
    gap: 10px;
    align-items: start;
}

.cbtt-sm-txrow__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    padding-top: 1px;
    color: var(--cbtt-text-muted, #777);
}

.cbtt-sm-txrow__main {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.cbtt-sm-txrow__label {
    font-size: 13.5px;
    line-height: 1.4;
    color: var(--cbtt-text, #1d2327);
    /* Some of these labels are a full sentence ("Remaining amount with real
       money because of insufficient cancellation credit"). They wrap; the page
       does not scroll. */
    overflow-wrap: anywhere;
}

.cbtt-sm-txrow__sub {
    font-size: 12.5px;
    line-height: 1.4;
    color: var(--cbtt-text-muted, #777);
    overflow-wrap: anywhere;
}

.cbtt-sm-txrow__amount {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 1px;
    text-align: right;
}

.cbtt-sm-txrow__figure {
    font-size: 14.5px;
    font-weight: 700;
    line-height: 1.3;
    white-space: nowrap;
    color: var(--cbtt-text, #1d2327);
    font-variant-numeric: tabular-nums;
}

/* Cancellation credit is not money, and the shell already marks it in the
   accent (see .cbtt-sm-mana__mark). Ink rather than the accent itself: this is
   text, and it has to clear 4.5:1 on the card. */
.cbtt-sm-txrow__figure--mana {
    color: var(--cbtt-accent-ink, #086b7e);
}

.cbtt-sm-txrow__note {
    font-size: 11.5px;
    line-height: 1.35;
    white-space: nowrap;
    color: var(--cbtt-text-muted, #777);
    font-variant-numeric: tabular-nums;
}

.cbtt-sm-txrow__note--expired {
    color: var(--cbtt-error-ink, #721c24);
    font-weight: 600;
}

/* Everything that belongs to an entry but is not part of its headline: the
   lesson date, the place, an invoice link. Indented to the label column
   (22px icon + 10px gap) so it reads as subordinate to the line above. */
.cbtt-sm-txentry__aside {
    margin: 6px 0 0;
    padding-left: 32px;
    font-size: 12.5px;
    line-height: 1.45;
    color: var(--cbtt-text-muted, #777);
    overflow-wrap: anywhere;
}

.cbtt-sm-txnotes {
    margin-top: 6px;
    padding-left: 32px;
}

.cbtt-sm-txnote {
    display: flex;
    align-items: baseline;
    gap: 6px;
    font-size: 12.5px;
    line-height: 1.45;
    color: var(--cbtt-text-muted, #777);
}

.cbtt-sm-txnote__del {
    flex: none;
    min-height: 24px;
    padding: 0 4px;
    border: 0;
    background: none;
    color: var(--cbtt-error-ink, #721c24);
    font: inherit;
    font-size: 15px;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
}

.cbtt-sm-txnote__form {
    display: flex;
    gap: 6px;
    margin-top: 8px;
    padding-left: 32px;
}

/* Both of these have a display of their own, which beats what the hidden
   attribute does on its own — so it has to be said. */
.cbtt-sm-txnote__form[hidden],
.cbtt-sm-txnote__del[hidden] {
    display: none;
}

.cbtt-sm-txnote__input {
    flex: 1 1 auto;
    max-width: 260px;
    min-height: 40px;
    padding: 8px 10px;
    border: 1px solid var(--cbtt-border, #ddd);
    border-radius: var(--cbtt-radius-sm, 4px);
    background: var(--cbtt-surface, #fff);
    color: var(--cbtt-text, #1d2327);
    font: inherit;
    font-size: 13px;
}

/* ── Tables (contract archive) ─────────────────────────────────────────── */

/* The scroll box is the table's, never the page's. */
.cbtt-sm-tablewrap {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    border: 1px solid var(--cbtt-border, #ddd);
    border-radius: var(--cbtt-radius-md, 10px);
    background: var(--cbtt-surface, #fff);
}

.cbtt-sm-table {
    width: 100%;
    margin: 0;
    border-collapse: collapse;
    font-size: 13.5px;
}

.cbtt-sm-table th,
.cbtt-sm-table td {
    padding: 10px 14px;
    text-align: left;
    vertical-align: middle;
    border-bottom: 1px solid var(--cbtt-border-soft, #eee);
}

.cbtt-sm-table thead th {
    background: var(--cbtt-surface-alt, #f8f9fa);
    color: var(--cbtt-text-muted, #777);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    white-space: nowrap;
}

.cbtt-sm-table tbody tr:last-child td {
    border-bottom: 0;
}

.cbtt-sm-table .cbtt-sm-num {
    white-space: nowrap;
}

/* ── Annual report ─────────────────────────────────────────────────────── */

.cbtt-sm-years {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.cbtt-sm-yearcard {
    border: 1px solid var(--cbtt-border, #e6e3dd);
    border-radius: var(--cbtt-radius-md, 10px);
    background: var(--cbtt-surface, #fff);
    overflow: hidden;
}

.cbtt-sm-yearcard__head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 8px 12px;
    padding: 11px 16px;
    border-bottom: 1px solid var(--cbtt-border-soft, #eee);
    background: var(--cbtt-surface-alt, #f8f9fa);
}

.cbtt-sm-yearcard__title {
    margin: 0;
    font-size: 15px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--cbtt-text, #1d2327);
}

/* The headline row: the three figures a student opens this tab for, stated
   large before the breakdown explains them. The card used to open straight into
   three narrow stat blocks of equal weight, which is why it read as thin — a
   summary with no summary. Everything here is derived from figures the report
   already had; nothing new is queried.

   auto-fit rather than a fixed count: the row carries one, two or three items
   depending on what the year actually contains, and each of those has to look
   deliberate rather than like a three-column grid with gaps in it. */
.cbtt-sm-yearhead {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: 4px 16px;
    padding: 18px 16px 16px;
    border-bottom: 1px solid var(--cbtt-border-soft, #eee);
}

.cbtt-sm-yearhead__item {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.cbtt-sm-yearhead__value {
    font-size: 30px;
    font-weight: 650;
    line-height: 1.05;
    letter-spacing: -.02em;
    color: var(--cbtt-accent-ink, #086b7e);
}

.cbtt-sm-yearhead__label {
    font-size: 12.5px;
    line-height: 1.35;
    color: var(--cbtt-text-muted, #777);
    overflow-wrap: anywhere;
}

/* One column on a phone, three side by side from 640px. The old markup used
   col-md-4, which meant three columns down to 768px and one below it — the
   bars were 90px wide on a tablet. */
.cbtt-sm-yearstats {
    display: grid;
    grid-template-columns: 1fr;
    gap: 20px;
    padding: 16px;
}

@media (min-width: 640px) {
    .cbtt-sm-yearstats {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

.cbtt-sm-stat {
    min-width: 0;
}

.cbtt-sm-stat__title {
    margin: 0 0 8px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--cbtt-text-muted, #777);
}

.cbtt-sm-stat__figure {
    margin: 0;
    font-size: 28px;
    font-weight: 600;
    line-height: 1.1;
    color: var(--cbtt-text, #1d2327);
    font-variant-numeric: tabular-nums;
}

.cbtt-sm-stat__note {
    margin: 3px 0 0;
    font-size: 12.5px;
    color: var(--cbtt-text-muted, #777);
}

/* The proportion only. The figures moved out of the segments and into the
   legend below: inside a short segment they were clipped, and white on the
   green missed AA at that size. */
.cbtt-sm-bar {
    display: flex;
    height: 10px;
    border: 1px solid var(--cbtt-border-soft, #eee);
    border-radius: 999px;
    background: var(--cbtt-surface-alt, #f8f9fa);
    overflow: hidden;
}

.cbtt-sm-bar__seg {
    display: block;
    height: 100%;
}

.cbtt-sm-bar__seg--credit,
.cbtt-sm-bar__seg--free {
    background: var(--cbtt-success, #46946a);
}

.cbtt-sm-bar__seg--fees {
    background: var(--cbtt-accent, #0193aa);
}

.cbtt-sm-bar__seg--paid {
    background: var(--cbtt-error, #d9534f);
}

.cbtt-sm-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 3px 14px;
    margin: 9px 0 0;
    padding: 0;
    list-style: none;
    font-size: 12.5px;
    line-height: 1.4;
    color: var(--cbtt-text-muted, #777);
}

/* gap:0 on purpose. A flex container turns each run of text into an anonymous
   item, so "Credit: €" and the <span> holding the figure are two items — a gap
   here would open a space in the middle of the amount. The swatch carries its
   own margin instead. */
.cbtt-sm-legend__item {
    display: inline-flex;
    align-items: center;
    gap: 0;
    font-variant-numeric: tabular-nums;
}

/* The swatch is what ties a figure to its slice of the bar. Only the items
   that HAVE a slice get one — "Total" is a sum, not a segment. */
.cbtt-sm-legend__item--credit::before,
.cbtt-sm-legend__item--fees::before,
.cbtt-sm-legend__item--free::before,
.cbtt-sm-legend__item--paid::before,
.cbtt-sm-legend__item--expired::before {
    content: "";
    flex: none;
    width: 8px;
    height: 8px;
    margin-right: 6px;
    border-radius: 2px;
}

.cbtt-sm-legend__item--credit,
.cbtt-sm-legend__item--free {
    color: var(--cbtt-success-ink, #075008);
}

.cbtt-sm-legend__item--credit::before,
.cbtt-sm-legend__item--free::before {
    background: var(--cbtt-success, #46946a);
}

.cbtt-sm-legend__item--fees {
    color: var(--cbtt-accent-ink, #086b7e);
}

.cbtt-sm-legend__item--fees::before {
    background: var(--cbtt-accent, #0193aa);
}

.cbtt-sm-legend__item--paid {
    color: var(--cbtt-error-ink, #721c24);
}

.cbtt-sm-legend__item--paid::before {
    background: var(--cbtt-error, #d9534f);
}

.cbtt-sm-legend__item--expired {
    color: var(--cbtt-warning-ink, #856404);
}

.cbtt-sm-legend__item--expired::before {
    background: var(--cbtt-warning, #f0ad4e);
}

/* ── Price calculator ──────────────────────────────────────────────────── */

/* [price-calculator] is also embeddable on a public page, so this block is
   self-contained: it sets its own type and its own focus ring instead of
   relying on the .cbtt-sm-shell rules further up, which are not there when the
   shortcode stands on its own. */
.cbtt-sm-calc {
    max-width: 600px;
    margin: 24px auto;
    padding: 20px;
    border: 1px solid var(--cbtt-border, #ddd);
    border-radius: var(--cbtt-radius-md, 10px);
    background: var(--cbtt-surface, #fff);
    color: var(--cbtt-text, #1d2327);
    font-family: var(--cbtt-font-sans, inherit);
}

.cbtt-sm-calc :is(a, button, input, [tabindex]):focus-visible {
    outline: 2px solid var(--cbtt-accent, #0193aa);
    outline-offset: 2px;
}

.cbtt-sm-calc__title {
    margin: 0 0 18px;
    font-size: 19px;
    font-weight: 600;
    line-height: 1.3;
    text-align: center;
    color: var(--cbtt-text, #1d2327);
}

.cbtt-sm-calc__options {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-bottom: 18px;
}

.cbtt-sm-calc__option {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    padding: 12px 14px;
    border: 1px solid var(--cbtt-border-soft, #eee);
    border-radius: var(--cbtt-radius-md, 10px);
    font-size: 14px;
    font-weight: 600;
    line-height: 1.35;
    cursor: pointer;
}

/* A logged-in student's discount is a fact, not a choice: the switch is
   disabled, so it must not look clickable either. */
.cbtt-sm-calc__option:has(input:disabled) {
    color: var(--cbtt-text-muted, #777);
    cursor: default;
}

.cbtt-sm-calc__option-label {
    min-width: 0;
}

.cbtt-sm-switch {
    position: relative;
    display: inline-block;
    flex: none;
    width: 46px;
    height: 24px;
}

/* The input covers the whole switch and stays in the tab order; the track is
   only paint. The old markup nested a second <label> inside the first to get
   this, which is invalid and left the control unlabelled. */
.cbtt-sm-switch input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    opacity: 0;
    cursor: pointer;
}

.cbtt-sm-switch input:disabled {
    cursor: default;
}

.cbtt-sm-switch__track {
    position: absolute;
    inset: 0;
    border-radius: 999px;
    background: var(--cbtt-border, #ccc);
    transition: background-color .2s ease;
    pointer-events: none;
}

.cbtt-sm-switch__track::before {
    content: "";
    position: absolute;
    top: 3px;
    left: 3px;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: var(--cbtt-surface, #fff);
    /* Plain black at 30%, on purpose. A shadow is a depth cue, not a brand
       value — there is nothing here for a theme to override. */
    box-shadow: 0 1px 2px rgba(0, 0, 0, .3);
    transition: transform .2s ease;
}

.cbtt-sm-switch input:checked + .cbtt-sm-switch__track {
    background: var(--cbtt-accent, #0193aa);
}

.cbtt-sm-switch input:checked + .cbtt-sm-switch__track::before {
    transform: translateX(22px);
}

.cbtt-sm-switch input:disabled + .cbtt-sm-switch__track {
    opacity: .55;
}

.cbtt-sm-switch input:focus-visible + .cbtt-sm-switch__track {
    outline: 2px solid var(--cbtt-accent, #0193aa);
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    .cbtt-sm-switch__track,
    .cbtt-sm-switch__track::before {
        transition: none;
    }
}

.cbtt-sm-calc__results {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin: 0 0 18px;
    padding: 0;
    list-style: none;
}

/* Same card as everywhere else, and the same border in every state: the old
   rule lifted the row 2px and grew a shadow on hover, on a row that is not
   even clickable. */
.cbtt-sm-calc__item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    margin: 0;
    padding: 12px 14px;
    border: 1px solid var(--cbtt-border, #ddd);
    border-radius: var(--cbtt-radius-md, 10px);
    background: var(--cbtt-surface, #fff);
}

.cbtt-sm-calc__label {
    min-width: 0;
    font-size: 14px;
    font-weight: 600;
}

.cbtt-sm-calc__price {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 1px;
    text-align: right;
}

.cbtt-sm-calc__total {
    font-size: 17px;
    font-weight: 700;
    line-height: 1.25;
    white-space: nowrap;
    color: var(--cbtt-accent-ink, #086b7e);
}

.cbtt-sm-calc__effective {
    font-size: 12.5px;
    white-space: nowrap;
    color: var(--cbtt-text-muted, #777);
}

/* The small print under the prices. It explains, it does not warn — so it gets
   the dashed edge and the muted ink the rest of the product gives to secondary
   material, not the yellow warning panel it used to be. */
.cbtt-sm-calc__info {
    margin-top: 18px;
    padding: 12px 14px;
    border: 1px dashed var(--cbtt-border, #ddd);
    border-radius: var(--cbtt-radius-md, 10px);
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--cbtt-text-muted, #777);
}

.cbtt-sm-calc__info p {
    margin: 0 0 8px;
}

.cbtt-sm-calc__subtitle {
    margin: 12px 0 6px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--cbtt-text-muted, #777);
}

/* A description list, because that is what it is: four terms and what each
   one resolves to. It replaces a Bootstrap row/col-md-4 grid that stacked at
   768px and squeezed everywhere above it. */
.cbtt-sm-formula {
    display: flex;
    flex-direction: column;
    gap: 7px;
    margin: 0;
}

.cbtt-sm-formula__row {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 1px;
}

@media (min-width: 520px) {
    .cbtt-sm-formula__row {
        grid-template-columns: minmax(0, 11em) minmax(0, 1fr);
        gap: 4px 12px;
    }
}

.cbtt-sm-formula__term {
    margin: 0;
    font-weight: 700;
    color: var(--cbtt-text, #1d2327);
}

.cbtt-sm-formula__def {
    margin: 0;
    overflow-wrap: anywhere;
}

/* ── Reports page, small screens ───────────────────────────────────────── */

@media (max-width: 480px) {
    .cbtt-sm-tab {
        padding: 10px 11px;
        font-size: 13.5px;
    }

    .cbtt-sm-txcard__body {
        padding: 2px 11px;
    }

    .cbtt-sm-txcard__head {
        padding: 8px 11px;
    }

    .cbtt-sm-calc {
        padding: 16px;
    }
}

/* ── Trial booking (token / guest view) ────────────────────────────────── */

/* The visitor's first sight of the product. Same building blocks as the
   student view; the differences are that nothing here is cancellable and that
   the invoice form is permanently on screen rather than revealed by a
   selection. */

.cbtt-sm-shell--trial .cbtt-sm-appbar {
    position: static;
}

.cbtt-sm-trial__lead {
    max-width: 720px;
    margin: 12px auto;
    padding-inline: 16px;
    font-size: 15px;
    line-height: 1.5;
    color: var(--cbtt-text, #1d2327);
}

.cbtt-sm-shell--trial .cbtt-sm-listfoot__item {
    max-width: 720px;
    margin-inline: auto;
    padding-inline: 16px;
}

/* Room for the fixed form below, which is taller than the student action bar
   because it carries three inputs — and taller still on a small phone, where
   they stack (315px at 320px wide). cbtt-sm-nav.js measures the form and
   writes --cbtt-sm-trial-h; 204 + 16 = the old fixed 220px without it. */
.cbtt-sm-shell--trial .cbtt-sm-list {
    padding-bottom: calc(var(--cbtt-sm-trial-h, 204px) + 16px);
}

/* This bar is a FORM, not a selection bar: it must be visible from the first
   paint. .cbtt-sm-actionbar starts translated off-screen and is revealed by
   script when something is selected — correct there, wrong here, so the
   transform is undone and no data-state is set. Overriding rather than using a
   separate class keeps the safe-area inset and the transition. */
#cbtt-sm-trial-actionbar {
    transform: none;
    z-index: 40;
    border-top: 1px solid var(--cbtt-border, #ddd);
    background: var(--cbtt-surface, #fff);
    box-shadow: 0 -2px 12px rgb(0 0 0 / 8%);
}

.cbtt-sm-trial__formhead {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 8px;
    max-width: 900px;
    margin-inline: auto;
    padding: 10px 16px 0;
    font-size: 14px;
}

.cbtt-sm-trial__formhead span {
    font-size: 12px;
    color: var(--cbtt-text-muted, #777);
}

.cbtt-sm-trial__fields {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: 10px;
    align-items: end;
    max-width: 900px;
    margin-inline: auto;
    padding: 8px 16px 12px;
}

.cbtt-sm-trial__field {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
    font-size: 12px;
    font-weight: 600;
    color: var(--cbtt-text-muted, #777);
}

.cbtt-sm-trial__field .cbtt-sm-input {
    width: 100%;
    padding: 8px 10px;
    border: 1px solid var(--cbtt-border, #ddd);
    border-radius: var(--cbtt-radius-sm, 6px);
    font-size: 15px; /* 16px-ish: below that iOS zooms the page on focus */
    font-weight: 400;
    color: var(--cbtt-text, #1d2327);
}

.cbtt-sm-trial__fields #save-changes {
    display: flex;
    align-items: flex-end;
}

/* margin-top:0 matters as much as the height: .cbtt-sm-btn carries a 14px top
   margin for the student's stacked layout, and in a row of fields that margin
   pushes the button below the inputs it is supposed to line up with. Same
   defect the teacher's action bar had. */
.cbtt-sm-trial__fields .apply-changes {
    width: 100%;
    min-height: 40px;
    height: 40px;
    margin-top: 0;
    padding-block: 0;
}

/* ── The teacher's pointer on the front-end lessons page ───────────────── */

.cbtt-sm-adminhint {
    max-width: 720px;
    margin: 24px auto;
    padding: 16px;
    border: 1px solid var(--cbtt-border, #ddd);
    border-left: 4px solid var(--cbtt-accent, #0193aa);
    border-radius: var(--cbtt-radius-md, 10px);
    background: var(--cbtt-surface, #fff);
}

.cbtt-sm-adminhint p {
    margin: 0 0 12px;
    font-size: 15px;
    line-height: 1.5;
}

.cbtt-sm-adminhint p:last-child {
    margin-bottom: 0;
}

/* The "found something? tell us" banner (modules/students/calendar/shell.php,
   cbtt_sm_render_earn_notice()) reuses .cbtt-sm-adminhint's box and only adds
   its own dismiss control — a plain-text link-button, deliberately quiet so
   it never competes with the primary "Report it" button above it. */
.cbtt-sm-earn-notice__dismiss {
    margin: 8px 0 0;
}

.cbtt-sm-earn-notice__dismiss-btn {
    /* Padding, not font-size, carries the tap target to the frontend's 44px
       floor (UX-GUIDELINES 9.6) — the visible text stays small and quiet,
       the hit area does not. */
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 10px 4px;
    margin: -10px -4px;
    background: none;
    border: none;
    font-size: 13px;
    color: var(--cbtt-muted, #6b7280);
    text-decoration: underline;
    cursor: pointer;
}

.cbtt-sm-earn-notice__dismiss-btn:hover,
.cbtt-sm-earn-notice__dismiss-btn:focus-visible {
    color: var(--cbtt-text, #1d2327);
}

/* ==========================================================================
   .cbtt-cal-* — the calendar grid, rebuilt on suite classes (2026-08-22).
   These replace the retired Bootstrap contract (.row/.col-md-4/.card.h-100/
   .d-none/.d-flex.gap-3/.bg-light.fixed-bottom.border-top/.container) that
   core/assets/css/cbtt-legacy-grid.css used to emulate. The class names are a
   DOM CONTRACT shared with js/lesson_handler.js, calendar_interactions.js,
   bottom_bar_functions.js and bulk_cancel.js — rename only in lockstep.
   ========================================================================== */

/* The page container the shortcode renders (former Bootstrap .container). */
.student-manager-container {
    width: 100%;
    max-width: 1140px;
    margin-inline: auto;
    padding-inline: 12px;
    box-sizing: border-box;
}

.cbtt-cal-grid {
    display: flex;
    flex-wrap: wrap;
    margin-inline: -12px;
}

.cbtt-cal-grid--start {
    align-items: flex-start;
}

.cbtt-cal-grid > * {
    flex-shrink: 0;
    width: 100%;
    max-width: 100%;
    padding-inline: 12px;
    box-sizing: border-box;
}

.cbtt-cal-cell--full {
    flex: 0 0 auto;
    width: 100%;
}

@media (min-width: 768px) {
    .cbtt-cal-cell {
        flex: 0 0 auto;
        width: 33.3333%;
    }
}

/* Card base (former .card.h-100): flat per the design standard. The
   .cbtt-sm-row face of the student list overrides most of this — see the
   grid rules further up — but the teacher/admin variants render bare. */
.cbtt-cal-card {
    position: relative;
    display: flex;
    flex-direction: column;
    min-width: 0;
    height: 100%;
    background: var(--cbtt-surface, #ffffff);
    border: 1px solid var(--cbtt-border, #e6e1d7);
    border-radius: var(--cbtt-radius-md, 11px);
    margin-bottom: 1rem;
}

.cbtt-cal-card__body {
    flex: 1 1 auto;
    padding: 1rem;
}

.cbtt-cal-card__title {
    margin: 0 0 0.5rem;
    font-size: 1.1em;
    font-weight: 600;
}

.cbtt-cal-card__text {
    margin: 0 0 0.5rem;
}

.cbtt-cal-card__actions {
    display: flex;
    align-items: center;
    gap: 1rem;
}

/* "Has been arranged personally" switch on a short-notice card
   (calendar/calendar_html.php). The class is a DOM contract with
   js/calendar_interactions.js — rename only in lockstep. The full reset lives
   here (it used to be an inline style attribute plus a <style> block printed
   once per card); the three-part selector outranks theme and WP checkbox
   rules on its own, so the !important the inline version carried is gone. */
.cbtt-cal-card__actions input[type="checkbox"].personal-clarification-toggle {
    appearance: none;
    -webkit-appearance: none;
    flex-shrink: 0;
    position: relative;
    width: 3.5rem;
    height: 2rem;
    margin: 0;
    padding: 0;
    cursor: pointer;
    border: none;
    border-radius: 2rem;
    background-color: var(--cbtt-muted, #7c8494);
    background-image: none;
    transition: background-color 0.3s ease;
}

.cbtt-cal-card__actions input[type="checkbox"].personal-clarification-toggle:checked {
    background-color: var(--cbtt-success, #1f8f5f);
}

.cbtt-cal-card__actions input[type="checkbox"].personal-clarification-toggle:focus {
    border: none;
    box-shadow: none;
    outline: none;
}

.cbtt-cal-card__actions input[type="checkbox"].personal-clarification-toggle:focus-visible {
    box-shadow: var(--cbtt-focus-ring, 0 0 0 3px rgba(1, 147, 170, 0.15));
}

.cbtt-cal-card__actions input[type="checkbox"].personal-clarification-toggle::after {
    content: '';
    position: absolute;
    top: 0.15rem;
    left: 0.15rem;
    width: 1.7rem;
    height: 1.7rem;
    border-radius: 50%;
    background-color: var(--cbtt-white, #ffffff);
    border: 1px solid rgba(0, 0, 0, 0.1);
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
    transition: transform 0.3s ease;
}

.cbtt-cal-card__actions input[type="checkbox"].personal-clarification-toggle:checked::after {
    transform: translateX(1.5rem);
}

/* Machine-readable JSON payload inside every card (former .d-none). */
.cbtt-cal-payload {
    display: none !important;
}

/* Bottom bars that are NOT the actionbar/dock element (admin/token lists) —
   former .bg-light.fixed-bottom.border-top utility trio. */
.cbtt-cal-fixedbar {
    position: fixed;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 1030;
    background: var(--cbtt-surface-alt, #f8f9fa);
    border-top: 1px solid var(--cbtt-border, #e6e1d7);
}
