/* ═══════════════════════════════════════════════════════════════════════════
   CRAZY DUCK — COMPONENTS
   Requires tokens.css. Plain CSS, no framework, no build step.
   ───────────────────────────────────────────────────────────────────────────
   Everything here is scoped to `.cd`. That is deliberate: this stylesheet gets
   dropped onto a stranger's Squarespace site next to their theme, their
   plugins and whatever a previous web person left behind. We prefix every
   class, we never style a bare element outside our scope, and we reset our own
   subtree rather than trusting theirs.

   Cascade layers are NOT used, on purpose: unlayered host styles beat layered
   ones, so a layer would hand the host page a free win over our own buttons.

   RESPONSIVENESS is done with container queries against `.cd-shell`, not
   viewport media queries. The widget is 320px wide inside a 1440px page, so
   the viewport tells us nothing useful. Narrow is the default; wider layouts
   are opt-in enhancements, which means a browser without container query
   support degrades to the 320px layout — cramped but entirely functional.

   CONTENTS
   ────────
    1  Scope + reset           8c  Choice tiles
    2  Typography              8d  People picker
    3  Layout primitives        9  Slot picker
    4  The waterline + paddle  10  Date navigator
    5  Logo + attribution     10b  Month calendar
    6  Focus                   11  Stepper
    7  Buttons                 12  Summary / receipt rows
    8  Forms                   13  Badges + status pills
   8a  Option list             14  Skeletons
   8b  Segmented choice        15  Toasts
                               16  Empty state
                               17  Forced colours + print

   THE THREE PICKERS ARE THREE SHAPES, ON PURPOSE
   ──────────────────────────────────────────────
   A booking flow asks the same grammatical question four times running — "which
   one?" — and the fastest way to make four questions feel like one endless form
   is to answer them all with the same control. So the shape carries the rank:

     .cd-tile     a DESTINATION.  Square, iconic, 2–4 across. "Which part of the
                  practice is this?" There are five of these, ever.
     .cd-option   a LINE ITEM.    Full-width row, price at the right edge. Read
                  as a menu: name, description, what it costs.
     .cd-segment  a MODIFIER.     Pill. Changes the thing already chosen rather
                  than choosing it. Phone or office, Tuesday's 9am or 10am.
     .cd-person   a PERSON.       Face first. The one choice that is about who
                  rather than what.

   Get this wrong and the step reads as a wall. Categories and services were
   both .cd-option once, and the first screen of the booking page was seventeen
   identical rectangles in two indistinguishable groups.
   ═══════════════════════════════════════════════════════════════════════════ */


/* ═══ 1 · SCOPE + RESET ════════════════════════════════════════════════════ */

.cd {
  font-family: var(--cd-font-body);
  font-size: var(--cd-text-base);
  line-height: var(--cd-leading-normal);
  color: var(--cd-ink);
  background-color: var(--cd-canvas);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  font-synthesis-weight: none;
}

/* Any element that pins a scheme mid-page MUST re-assert the ink, and this is
   the rule that does it. The trap is subtle and it bites hard.
   ────────────────────────────────────────────────────────────────────────────
   `color` is an inherited PROPERTY, and it inherits the value already computed
   on the ancestor — not the var() expression that produced it. So a page whose
   body resolved `color: var(--cd-ink)` to bone, containing a panel that flips to
   the light scheme with data-cd-scheme="light", gets a panel with a light
   BACKGROUND (resolved fresh, on the panel) and bone TEXT (inherited, already
   computed). Bone on bone paper: 1.1:1. Invisible.

   It only shows up on elements that do not set a colour of their own, which is
   why it hides so well — every `cd-` component sets one, so the booking card
   inside such a panel looks perfect while the plain divs around it vanish.

   Colour only, not background: a pinned subtree is often deliberately
   transparent over something else, and painting a canvas rectangle under every
   one of them would be a worse surprise than this rule fixes. Whoever pins a
   scheme still supplies their own surface.

   Declared here rather than in tokens.css so every component rule below still
   out-ranks it on source order at equal specificity — .cd-hint must keep its
   muted ink inside a pinned panel. */
[data-cd-scheme] { color: var(--cd-ink); }

.cd *,
.cd *::before,
.cd *::after { box-sizing: border-box; }

/* :where(), not :is(), and this is load-bearing rather than stylistic.
   `.cd :is(button, …)` computes to (0,1,1), which quietly OUT-RANKS every one of
   our own single-class component rules — `.cd-btn { color: var(--cd-btn-fg) }`
   at (0,1,0) loses to it, and every primary button silently inherits the page
   ink instead of its derived label colour. :where() contributes nothing, so the
   reset lands at (0,1,0): still strong enough to beat a host page's bare
   `button { color: … }`, never strong enough to beat us. */
.cd :where(h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd, ul, ol, fieldset) {
  margin: 0;
  padding: 0;
}
.cd :where(ul, ol) { list-style: none; }
.cd :where(fieldset) { border: 0; min-inline-size: 0; }
.cd :where(legend) { padding: 0; }
.cd :where(img, svg) { display: block; max-inline-size: 100%; }
.cd :where(button, input, select, textarea, optgroup) { font: inherit; color: inherit; }
.cd ::selection { background: var(--cd-selection-bg); color: var(--cd-selection-fg); }

/* Numbers a client has to compare — times, dates, dollars — always line up. */
.cd :where(time, .cd-slot, .cd-day, .cd-money, .cd-summary__value) {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.cd-visually-hidden {
  position: absolute !important;
  inline-size: 1px; block-size: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%);
  white-space: nowrap; border: 0;
}


/* ═══ 2 · TYPOGRAPHY ═══════════════════════════════════════════════════════
   The serif is a guest, not a resident. It appears on page titles and on
   Crazy Duck's own marketing surfaces. Everything a client has to read and
   act on is set in the platform's UI face, because that is the face their
   accountant's own website is most likely already using. */

.cd-display {
  font-family: var(--cd-font-display);
  font-size: clamp(var(--cd-text-2xl), 5.2cqi + 1rem, var(--cd-text-4xl));
  line-height: var(--cd-leading-tight);
  letter-spacing: var(--cd-track-display);
  font-weight: var(--cd-weight-regular);
  text-wrap: balance;
}

.cd-title {
  font-family: var(--cd-font-display);
  font-size: var(--cd-text-xl);
  line-height: var(--cd-leading-snug);
  letter-spacing: var(--cd-track-tight);
  font-weight: var(--cd-weight-regular);
  text-wrap: balance;
}

.cd-subtitle {
  font-size: var(--cd-text-md);
  line-height: var(--cd-leading-snug);
  font-weight: var(--cd-weight-medium);
  letter-spacing: var(--cd-track-tight);
}

/* The ledger column head. Small, tracked, uppercase. Used everywhere a group
   of things needs naming without shouting: AVAILABLE TIMES, STEP 2 OF 3. */
.cd-eyebrow {
  font-size: var(--cd-text-2xs);
  line-height: var(--cd-leading-flat);
  font-weight: var(--cd-weight-semi);
  letter-spacing: var(--cd-track-eyebrow);
  text-transform: uppercase;
  color: var(--cd-ink-3);
}

/* A fact with its glyph — "we'll allow 60 minutes for this", "the database is in
   Australia". The mark is what stops a single quiet line being read as filler. */
.cd-noteline {
  display: flex;
  align-items: center;
  gap: var(--cd-space-2);
  margin: 0;
  font-size: var(--cd-text-sm);
  line-height: var(--cd-leading-snug);
  color: var(--cd-ink-2);
}
.cd-noteline svg { flex: none; inline-size: 18px; block-size: 18px; color: var(--cd-ink-3); }
.cd-noteline strong { font-weight: var(--cd-weight-semi); color: var(--cd-ink); }

.cd-lead  { font-size: var(--cd-text-md); color: var(--cd-ink-2); max-inline-size: var(--cd-measure); }
.cd-hint  { font-size: var(--cd-text-sm); color: var(--cd-ink-3); line-height: var(--cd-leading-snug); }
.cd-meta  { font-size: var(--cd-text-xs); color: var(--cd-ink-3); }
.cd-mono  { font-family: var(--cd-font-mono); font-size: 0.94em; letter-spacing: 0.02em; }
.cd-strong { font-weight: var(--cd-weight-semi); color: var(--cd-ink); }

.cd-link {
  --cd-ring: 0 0 #0000;
  color: var(--cd-accent-ink);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  border-radius: var(--cd-radius-xs);
  transition: text-decoration-thickness var(--cd-dur-1) var(--cd-ease);
}
.cd-link:hover { text-decoration-thickness: 2px; }
.cd-link:focus-visible { box-shadow: var(--cd-ring); }
/* A <button> that reads as a link — "Different hours on some days", "+ Add a break".
   The browser's button chrome would otherwise show through as a pale box. */
button.cd-link { display: inline; padding: 0; font: inherit; background: none; border: 0; cursor: pointer; }


/* ═══ 3 · LAYOUT PRIMITIVES ════════════════════════════════════════════════ */

.cd-shell {
  container-type: inline-size;
  container-name: cd;
  inline-size: 100%;
  max-inline-size: var(--cd-width-page);
  margin-inline: auto;
}

.cd-stack   { display: flex; flex-direction: column; gap: var(--cd-space-4); }
.cd-stack-2 { display: flex; flex-direction: column; gap: var(--cd-space-2); }
.cd-stack-6 { display: flex; flex-direction: column; gap: var(--cd-space-6); }
.cd-stack-8 { display: flex; flex-direction: column; gap: var(--cd-space-8); }

.cd-cluster {
  display: flex; flex-wrap: wrap;
  align-items: center;
  gap: var(--cd-space-3);
}
.cd-cluster--between { justify-content: space-between; }

/* Actions stack full-width on a narrow widget and sit inline once there is
   room. Primary action last in the DOM, first visually on wide — no: keep DOM
   order, because screen-reader order and tab order must match the visual one
   at 320px too. So the primary is simply first, always. */
.cd-actions { display: flex; flex-direction: column-reverse; gap: var(--cd-space-2); }
/* Wraps rather than overflows. Buttons are `white-space: nowrap` and their labels name
   their destination rather than saying "Back" — "Choose another time" beside "Confirm
   booking" is wider than the 420px that turned this row horizontal in the first place,
   and without the wrap that band of narrow phones gets a horizontal scrollbar. */
@container cd (min-width: 420px) {
  .cd-actions { flex-direction: row; flex-wrap: wrap; justify-content: flex-end; }
}


/* ═══ 4 · THE WATERLINE ════════════════════════════════════════════════════
   The identity's one structural device: a single hairline that separates what
   the client sees from the machinery underneath. Used as a section rule, as
   the spine of the stepper and as the underline on the active date. It is
   never decorated and never coloured with the accent — that would make it
   compete with the thing it is meant to organise. */

.cd-waterline {
  border: 0;
  block-size: var(--cd-hairline);
  background: var(--cd-line);
  margin-block: var(--cd-space-6);
}
.cd-waterline--tight { margin-block: var(--cd-space-4); }

/* Labelled variant: rule — LABEL — rule. */
.cd-waterline-label {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--cd-space-3);
  margin-block: var(--cd-space-5);
}
.cd-waterline-label::before,
.cd-waterline-label::after {
  content: "";
  block-size: var(--cd-hairline);
  background: var(--cd-line);
}

/* ── THE PADDLE — the busy state ────────────────────────────────────────────
   The waterline device, animated, and the only place in the system where the
   chaos below it is ever shown to a user.

   A still rule. Beneath it, two short strokes going flat out. The rule does
   not move — that is the entire joke, and it stops working the moment the
   surface twitches. Nothing is drawn above the line: there is NO DUCK here.
   BRAND.md §9 forbids animating the mark's feet, and it still does; this is
   the waterline on its own, which is a different object with a different job.

   Use it for anything that is genuinely working: a slot map resolving, a
   calendar reconnecting, a reschedule writing to two providers. Do not use it
   as decoration, and never above the waterline in a client-facing flow.

     <span class="cd-paddle" role="status" aria-label="Checking availability"></span>

   Functional motion, so §6 lets it survive reduced-motion. It slows down
   rather than stopping, because a busy indicator that does not move is a
   frozen page. */

.cd-paddle {
  display: inline-block;
  position: relative;
  inline-size: 2.6em;
  block-size: 0.9em;
  vertical-align: -0.18em;
  border-block-start: 2px solid currentColor;   /* the surface. never moves. */
}
.cd-paddle::before,
.cd-paddle::after {
  content: "";
  position: absolute;
  inset-block-start: 0.14em;
  inline-size: 2px;
  block-size: 0.52em;
  border-radius: var(--cd-radius-pill);
  background: currentColor;
  opacity: 0.5;                                  /* it is under water */
  transform-origin: 50% 0;
  animation: cd-paddle 640ms var(--cd-ease-in-out) infinite;
}
/* The two strokes are placed about the midpoint of the rule and swing out from
   it, so the moving mass stays optically centred under the surface rather than
   drifting to one side of it. The negative delay puts the second stroke half a
   cycle behind the first: they alternate, the way something actually paddling
   does, instead of scissoring in unison. */
.cd-paddle::before { inset-inline-start: 0.85em; }
.cd-paddle::after  { inset-inline-start: 1.65em; animation-delay: -320ms; }

@keyframes cd-paddle {
  0%, 100% { rotate: -30deg; }
  50%      { rotate:  26deg; }
}

/* Sized variants. The small one is for inline use next to a label; the large
   one is for an empty panel that is waiting on the resolver. Both restate the
   stroke positions, because they are absolute offsets rather than percentages
   — an em offset that is centred at 2.6em is nowhere near centred at 1.9em. */
.cd-paddle--sm { inline-size: 1.9em; block-size: 0.72em; }
.cd-paddle--sm::before { inset-inline-start: 0.55em; }
.cd-paddle--sm::after  { inset-inline-start: 1.15em; }
.cd-paddle--sm::before,
.cd-paddle--sm::after  { block-size: 0.42em; }

.cd-paddle--lg { inline-size: 4.2em; block-size: 1.4em; border-block-start-width: 3px; }
.cd-paddle--lg::before { inset-inline-start: 1.4em; }
.cd-paddle--lg::after  { inset-inline-start: 2.7em; }
.cd-paddle--lg::before,
.cd-paddle--lg::after  { inline-size: 3px; block-size: 0.8em; }

@media (prefers-reduced-motion: reduce) {
  .cd-paddle::before,
  .cd-paddle::after { animation-duration: 1.8s; }
}
@media (forced-colors: active) {
  .cd-paddle { border-block-start-color: CanvasText; }
  .cd-paddle::before, .cd-paddle::after { background: CanvasText; opacity: 1; }
}


/* ═══ 5 · LOGO + ATTRIBUTION ═══════════════════════════════════════════════ */

.cd-logo { display: inline-flex; align-items: center; gap: var(--cd-space-2); color: var(--cd-ink); }
.cd-logo svg { inline-size: 1.65em; block-size: 1.65em; flex: none; }

.cd-wordmark {
  font-family: var(--cd-font-display);
  font-size: var(--cd-text-lg);
  letter-spacing: var(--cd-track-tight);
  line-height: var(--cd-leading-flat);
  font-weight: var(--cd-weight-regular);
  white-space: nowrap;
}
.cd-wordmark b { font-weight: var(--cd-weight-regular); border-block-end: 2px solid currentColor; }

/* The only place Crazy Duck is allowed to appear in a client-facing flow.
   11px, tertiary ink, no colour, no duck in the sentence. */
.cd-poweredby {
  --cd-ring: 0 0 #0000;
  display: inline-flex; align-items: center; gap: var(--cd-space-1);
  font-size: var(--cd-text-2xs);
  letter-spacing: 0.04em;
  color: var(--cd-ink-3);
  text-decoration: none;
  border-radius: var(--cd-radius-xs);
  padding: var(--cd-space-1) var(--cd-space-1);
}
.cd-poweredby svg { inline-size: 1.05em; block-size: 1.05em; opacity: 0.8; }
.cd-poweredby:hover { color: var(--cd-ink-2); }
.cd-poweredby:focus-visible { box-shadow: var(--cd-ring); }


/* ═══ 5b · MASTHEAD ════════════════════════════════════════════════════════
   The firm's own name across the top of their booking page.

   WHOSE PAGE THIS IS. A client arriving from a link on their accountant's
   website has to recognise, in the first half-second, that they are still
   dealing with their accountant. That is the entire job: the name set large in
   the display face, the mark beside it, and the number to ring if they would
   rather not do any of this online.

   The phone lives HERE and not in the footer, which is where it used to be.
   Roughly seven in ten of the firms this is sold to take their bookings by
   telephone today, and a client who would rather ring should not have to fail
   at the form first to find the number. It costs us nothing: a call the firm
   was always going to take is not a booking we lost.

   A rule under it, not the waterline (§4) — that device is spent once a page,
   and this is chrome. */

.cd-masthead {
  display: flex;
  align-items: center;
  gap: var(--cd-space-4);
  padding-block-end: var(--cd-space-5);
  border-block-end: var(--cd-hairline) solid var(--cd-line);
}

/* The firm's logo if they gave us one, their monogram if they did not — and the
   monogram is not a placeholder. Most small practices have a logo in a format
   nobody can use and no appetite for finding it during a five-minute signup, so
   a considered two letters in the display face IS the shipping state for a good
   share of them. It has to look chosen: the tile wears the firm's own accent
   (graphite for a firm with none), so the page's first square inch is already
   in their colour.

   ONE MASTHEAD. This is the same header the homepage device shows and the setup
   wizard previews — both frame this page and dress it by message rather than
   drawing a masthead of their own — so this is the only place its look is
   decided. For a while the marketing site kept a second copy (.mk-firm, dressed
   by js/customise.js), and the two drifted: a wide wordmark sat on a white plate
   at its own width on the homepage and was squashed into a beige square here. */
.cd-masthead__mark {
  flex: none;
  display: grid; place-items: center;
  inline-size: 44px; block-size: 44px;
  overflow: hidden;
  border-radius: var(--cd-radius-sm);
  background: var(--cd-accent-solid);
  color: var(--cd-on-accent);
  font-family: var(--cd-font-display);
  font-size: var(--cd-text-md);
  line-height: 1;
  letter-spacing: var(--cd-track-tight);
}
/* A real logo is usually a wordmark, so the tile gives way to the logo's own
   shape: full height, width as drawn, capped so a wide mark cannot shoulder the
   heading off the line. It sits on a white plate whatever the page's surface,
   because a white-on-transparent logo — common for accounting firms — vanishes
   into a cream page, and a firm's logo was drawn for daylight. */
.cd-masthead__mark--logo {
  inline-size: auto; block-size: auto;
  max-inline-size: 12rem;
  padding: 3px 8px;
  border-radius: 6px;
  background: #fff;
  box-shadow: 0 0 0 1px rgb(0 0 0 / 0.06);
}
.cd-masthead__mark--logo img {
  display: block;
  block-size: 38px; inline-size: auto;
  max-inline-size: 100%;
  object-fit: contain;
}
/* With a real logo up, the firm's name would only repeat what the logo says, so
   it steps out of sight (not out of the document — it is still the page's h1)
   and the quiet line beneath it steps up to be the heading, in the same face
   and size the name wore. */
.cd-masthead--logo .cd-masthead__name {
  position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}
.cd-masthead--logo .cd-masthead__note {
  font-family: var(--cd-font-display);
  font-size: var(--cd-text-lg);
  font-weight: var(--cd-weight-regular);
  line-height: var(--cd-leading-tight);
  letter-spacing: var(--cd-track-display);
  color: var(--cd-ink);
}

.cd-masthead__text { flex: 1 1 auto; min-inline-size: 0; }
.cd-masthead__name {
  margin: 0;
  font-family: var(--cd-font-display);
  font-size: var(--cd-text-lg);
  font-weight: var(--cd-weight-regular);
  line-height: var(--cd-leading-tight);
  letter-spacing: var(--cd-track-display);
  color: var(--cd-ink);
}
.cd-masthead__note {
  margin: 0;
  font-size: var(--cd-text-sm);
  color: var(--cd-ink-3);
}

.cd-masthead__contact {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: var(--cd-space-2);
  font-size: var(--cd-text-base);
  color: var(--cd-ink);
  text-decoration: none;
  white-space: nowrap;
}
.cd-masthead__contact svg { inline-size: 18px; block-size: 18px; color: var(--cd-ink-3); }
.cd-masthead__contact:hover { color: var(--cd-accent-ink); }
.cd-masthead__contact:focus-visible { box-shadow: var(--cd-ring); border-radius: var(--cd-radius-sm); }

/* On a phone the number drops under the name rather than squeezing it. It is
   still above everything else on the page, which is the point. */
@container cd (max-width: 30rem) {
  .cd-masthead { flex-wrap: wrap; gap: var(--cd-space-3); }
  .cd-masthead__contact { inline-size: 100%; }
}


/* ═══ 6 · FOCUS ════════════════════════════════════════════════════════════
   One ring, everywhere, composed into each component's box-shadow via
   --cd-ring. Two tones: the inner ring is the surface colour, the outer is
   maximum contrast against it. On a lime button, on a navy button, on a
   photograph — one of the two boundaries always clears 3:1.

   --cd-ring is re-declared as a no-op on every component that composes it, so
   it can never leak down to a child of a focused element. */

.cd :is(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: none;
  --cd-ring:
      0 0 0 var(--cd-focus-w) var(--cd-focus-inner),
      0 0 0 var(--cd-focus-w-2) var(--cd-focus-outer);
}
/* Components compose var(--cd-ring) into their own box-shadow so they keep
   their elevation while focused. Anything else — a bare link in body copy, a
   host-page control that wandered into our subtree — is caught here. */
.cd :is(a, button, input, select, textarea, summary, [tabindex]):not(
      .cd-btn, .cd-iconbtn, .cd-slot, .cd-day, .cd-link, .cd-poweredby,
      .cd-input, .cd-textarea, .cd-select, .cd-check, .cd-radio,
      .cd-option, .cd-segment, .cd-tile, .cd-person, .cd-date
    ):focus-visible {
  box-shadow: var(--cd-ring);
}


/* ═══ 7 · BUTTONS ══════════════════════════════════════════════════════════ */

.cd-btn {
  --cd-ring: 0 0 #0000;
  --cd-btn-bg: transparent;
  --cd-btn-fg: var(--cd-ink);
  --cd-btn-line: transparent;
  --cd-btn-shadow: none;

  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--cd-space-2);

  min-block-size: var(--cd-tap);
  min-inline-size: var(--cd-tap);
  padding: var(--cd-space-2) var(--cd-space-5);

  font-size: var(--cd-text-base);
  font-weight: var(--cd-weight-semi);
  letter-spacing: var(--cd-track-tight);
  line-height: var(--cd-leading-snug);
  text-align: center;
  text-decoration: none;
  white-space: nowrap;

  color: var(--cd-btn-fg);
  background: var(--cd-btn-bg);
  border: var(--cd-border) solid var(--cd-btn-line);
  border-radius: var(--cd-radius-md);
  box-shadow: var(--cd-ring), var(--cd-btn-shadow);
  cursor: pointer;
  appearance: none;
  -webkit-tap-highlight-color: transparent;

  transition:
    background-color var(--cd-dur-1) var(--cd-ease),
    border-color     var(--cd-dur-1) var(--cd-ease),
    color            var(--cd-dur-1) var(--cd-ease),
    box-shadow       var(--cd-dur-1) var(--cd-ease),
    translate        var(--cd-dur-1) var(--cd-ease);
}

.cd-btn:active:not(:disabled, [aria-disabled="true"]) {
  translate: 0 calc(1px * var(--cd-motion));
}

.cd-btn:disabled,
.cd-btn[aria-disabled="true"] {
  cursor: not-allowed;
  --cd-btn-bg: var(--cd-disabled-bg);
  --cd-btn-fg: var(--cd-disabled-ink);
  --cd-btn-line: transparent;
  --cd-btn-shadow: none;
}

/* — primary: the firm's colour, conditioned so the label always clears AA — */
.cd-btn--primary {
  --cd-btn-bg: var(--cd-accent-solid);
  --cd-btn-fg: var(--cd-on-accent);
  --cd-btn-line: var(--cd-accent-solid);
  --cd-btn-shadow: var(--cd-shadow-1);
}
.cd-btn--primary:hover:not(:disabled, [aria-disabled="true"]) {
  --cd-btn-bg: var(--cd-accent-hover);
  --cd-btn-line: var(--cd-accent-hover);
  --cd-btn-shadow: var(--cd-shadow-2);
}
.cd-btn--primary:active:not(:disabled, [aria-disabled="true"]) {
  --cd-btn-bg: var(--cd-accent-active);
  --cd-btn-line: var(--cd-accent-active);
  --cd-btn-shadow: none;
}

/* — secondary: our structure, their colour only on the focus/hover edge — */
.cd-btn--secondary {
  --cd-btn-bg: var(--cd-surface);
  --cd-btn-fg: var(--cd-ink);
  --cd-btn-line: var(--cd-control-line);
  --cd-btn-shadow: var(--cd-shadow-1);
}
.cd-btn--secondary:hover:not(:disabled, [aria-disabled="true"]) {
  --cd-btn-bg: var(--cd-accent-subtle);
  --cd-btn-line: var(--cd-accent-line);
}

/* — ghost: navigation and dismissal. No border until it is touched. — */
.cd-btn--ghost {
  --cd-btn-bg: transparent;
  --cd-btn-fg: var(--cd-ink-2);
  padding-inline: var(--cd-space-3);
}
.cd-btn--ghost:hover:not(:disabled, [aria-disabled="true"]) {
  --cd-btn-bg: var(--cd-surface-2);
  --cd-btn-fg: var(--cd-ink);
}

/* — destructive: outlined by default. A client cancelling an appointment
     should have to mean it, but should not be frightened by a red slab. — */
.cd-btn--destructive {
  --cd-btn-bg: var(--cd-surface);
  --cd-btn-fg: var(--cd-danger);
  --cd-btn-line: var(--cd-danger);
}
.cd-btn--destructive:hover:not(:disabled, [aria-disabled="true"]) {
  --cd-btn-bg: var(--cd-danger-subtle);
}
.cd-btn--destructive-solid {
  --cd-btn-bg: var(--cd-danger);
  --cd-btn-fg: var(--cd-on-status);
  --cd-btn-line: var(--cd-danger);
  --cd-btn-shadow: var(--cd-shadow-1);
}
.cd-btn--destructive-solid:hover:not(:disabled, [aria-disabled="true"]) {
  --cd-btn-bg: color-mix(in oklab, var(--cd-danger) 88%, var(--cd-shade-toward));
}

.cd-btn--sm    { min-block-size: 36px; padding: var(--cd-space-1) var(--cd-space-3); font-size: var(--cd-text-sm); }
.cd-btn--lg    { min-block-size: 52px; padding: var(--cd-space-3) var(--cd-space-6); font-size: var(--cd-text-md); }
.cd-btn--block { inline-size: 100%; }

/* The ONE button on a screen that a client is meant to press. A pill rather
   than a rounded rectangle, because at the bottom of a page full of rounded
   rectangles — cards, rows, fields — the only shape left that reads as "this
   is different" is the one that is fully round. Reserved for that button;
   used on two, it stops meaning anything.

   Never on a destructive action. The pill says go. */
.cd-btn--pill { border-radius: var(--cd-radius-pill); }

/* The arrow leans the way the button goes. Distance is multiplied by
   --cd-motion, so reduced-motion keeps the arrow and drops the lean.

   It disappears when the button is disabled, and that is the point rather
   than tidiness: a greyed-out "Choose how to meet" still tells you what
   comes next, but an arrow on it would be promising a journey the button
   cannot yet make. */
.cd-btn__arrow {
  flex: none;
  inline-size: 1.05em; block-size: 1.05em;
  transition: translate var(--cd-dur-1) var(--cd-ease);
}
.cd-btn:hover:not(:disabled, [aria-disabled="true"]) .cd-btn__arrow,
.cd-btn:focus-visible:not(:disabled, [aria-disabled="true"]) .cd-btn__arrow {
  translate: calc(3px * var(--cd-motion)) 0;
}
.cd-btn:hover:not(:disabled, [aria-disabled="true"]) .cd-btn__arrow--back,
.cd-btn:focus-visible:not(:disabled, [aria-disabled="true"]) .cd-btn__arrow--back {
  translate: calc(-3px * var(--cd-motion)) 0;
}
.cd-btn:disabled .cd-btn__arrow,
.cd-btn[aria-disabled="true"] .cd-btn__arrow { display: none; }

/* A 36px button is under the 44px target minimum, so it only ships where a
   pointer-coarse device gets the full size back. */
@media (pointer: coarse) {
  .cd-btn--sm { min-block-size: var(--cd-tap); padding-inline: var(--cd-space-4); }
}

/* Icon-only. The 44px box is the target; the glyph inside can be small. */
.cd-iconbtn {
  --cd-ring: 0 0 #0000;
  display: inline-grid; place-items: center;
  inline-size: var(--cd-tap); block-size: var(--cd-tap);
  padding: 0;
  color: var(--cd-ink-2);
  background: transparent;
  border: var(--cd-border) solid transparent;
  border-radius: var(--cd-radius-md);
  box-shadow: var(--cd-ring);
  cursor: pointer;
  transition: background-color var(--cd-dur-1) var(--cd-ease), color var(--cd-dur-1) var(--cd-ease);
}
.cd-iconbtn:hover:not(:disabled) { background: var(--cd-surface-2); color: var(--cd-ink); }
.cd-iconbtn:disabled { color: var(--cd-ink-4); cursor: not-allowed; }
.cd-iconbtn svg { inline-size: 20px; block-size: 20px; }

/* Busy. The spinner is functional motion, so it survives reduced-motion —
   but it slows down and the label stops shifting. */
.cd-btn[aria-busy="true"] { cursor: progress; }
.cd-btn[aria-busy="true"] .cd-btn__label { opacity: 0.35; }
.cd-btn[aria-busy="true"]::after {
  content: "";
  position: absolute;
  inline-size: 1.05em; block-size: 1.05em;
  border: 2px solid currentColor;
  border-block-start-color: transparent;
  border-radius: var(--cd-radius-pill);
  animation: cd-spin 620ms linear infinite;
}
@keyframes cd-spin { to { rotate: 360deg; } }
@media (prefers-reduced-motion: reduce) {
  .cd-btn[aria-busy="true"]::after { animation-duration: 1.6s; }
}


/* ═══ 8 · FORMS ════════════════════════════════════════════════════════════ */

.cd-field { display: flex; flex-direction: column; gap: var(--cd-space-2); }

/* Two fields that are really one answer. A first name above a last name reads
   as two questions and makes the last step — the one a client is most likely
   to abandon — a column of five. Side by side it is a column of four, and the
   pair reads as "your name". Stacked below 480px, where two half-width inputs
   are each too narrow for a long surname. */
.cd-field-pair { display: grid; gap: var(--cd-space-2); }
@container cd (min-width: 480px) {
  .cd-field-pair { grid-template-columns: 1fr 1fr; gap: var(--cd-space-4); }
}

.cd-label {
  font-size: var(--cd-text-sm);
  font-weight: var(--cd-weight-semi);
  color: var(--cd-ink);
  letter-spacing: var(--cd-track-tight);
}
.cd-label .cd-optional { font-weight: var(--cd-weight-regular); color: var(--cd-ink-3); }

.cd-input,
.cd-textarea,
.cd-select {
  --cd-ring: 0 0 #0000;
  inline-size: 100%;
  min-block-size: var(--cd-tap);
  padding: var(--cd-space-2) var(--cd-space-3);
  /* max(16px, …) stops iOS Safari zooming the whole host page on focus. */
  font-size: max(16px, var(--cd-text-base));
  line-height: var(--cd-leading-snug);
  color: var(--cd-ink);
  background: var(--cd-surface);
  border: var(--cd-border) solid var(--cd-control-line);
  border-radius: var(--cd-radius-sm);
  box-shadow: var(--cd-ring);
  appearance: none;
  transition: border-color var(--cd-dur-1) var(--cd-ease), background-color var(--cd-dur-1) var(--cd-ease);
}
.cd-textarea { min-block-size: calc(var(--cd-tap) * 2.2); resize: vertical; }

.cd-input::placeholder,
.cd-textarea::placeholder { color: var(--cd-ink-3); opacity: 1; }

.cd-input:hover:not(:disabled),
.cd-textarea:hover:not(:disabled),
.cd-select:hover:not(:disabled) { border-color: var(--cd-control-line-hover); }

.cd-input:focus-visible,
.cd-textarea:focus-visible,
.cd-select:focus-visible { border-color: var(--cd-accent-line); }

.cd-input:disabled,
.cd-textarea:disabled,
.cd-select:disabled {
  background: var(--cd-disabled-bg);
  color: var(--cd-disabled-ink);
  border-color: transparent;
  cursor: not-allowed;
}

/* Select: the chevron is drawn in CSS, not fetched as an image, so it inherits
   the scheme and survives a strict CSP with no data: allowance either. */
.cd-select-wrap { position: relative; display: block; }
.cd-select { padding-inline-end: var(--cd-space-8); }
.cd-select-wrap::after {
  content: "";
  position: absolute;
  inset-inline-end: var(--cd-space-4);
  inset-block-start: calc(50% - 4px);
  inline-size: 8px; block-size: 8px;
  border-inline-end: 1.6px solid var(--cd-ink-2);
  border-block-end: 1.6px solid var(--cd-ink-2);
  rotate: 45deg;
  pointer-events: none;
}

/* — invalid ————————————————————————————————————————————————————————————
   Colour is never the only signal: the border thickens, an icon appears and
   the message is text. Wire it up with aria-invalid + aria-describedby. */
.cd-input[aria-invalid="true"],
.cd-textarea[aria-invalid="true"],
.cd-select[aria-invalid="true"] {
  border-color: var(--cd-danger);
  border-width: var(--cd-border-strong);
  background: var(--cd-danger-subtle);
}

.cd-error {
  display: flex; align-items: flex-start; gap: var(--cd-space-2);
  font-size: var(--cd-text-sm);
  line-height: var(--cd-leading-snug);
  color: var(--cd-danger);
  font-weight: var(--cd-weight-medium);
}
.cd-error::before {
  content: "!";
  flex: none;
  display: grid; place-items: center;
  inline-size: 1.15em; block-size: 1.15em;
  margin-block-start: 0.12em;
  font-size: 0.85em; font-weight: var(--cd-weight-bold); line-height: 1;
  color: var(--cd-on-status);
  background: var(--cd-danger);
  border-radius: var(--cd-radius-pill);
}

/* — checkbox + radio ——————————————————————————————————————————————————
   The whole label is the target, and the target is at least 44px tall. */
.cd-choice {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: var(--cd-space-3);
  min-block-size: var(--cd-tap);
  padding-block: var(--cd-space-2);
  cursor: pointer;
}
.cd-choice__text { font-size: var(--cd-text-base); line-height: var(--cd-leading-snug); }
.cd-choice__note { display: block; font-size: var(--cd-text-sm); color: var(--cd-ink-3); }

/* A trailing control — "how many rental properties?" — belongs at the end of the
   row it qualifies. Without the third column it lands back in the `auto` column
   under the checkbox, reading as a stray field that answers no question.

   NOT WIRED UP. Add-on QUANTITY has no markup behind it yet — book.html ticks an
   add-on on or off. Kept rather than deleted because the design is the finished
   half of a feature the catalogue already implies (four rental properties are
   not one), and because since compression landed the rule costs a few hundred
   bytes on the wire. Delete it the day quantities are ruled out, not before. */
.cd-choice--qty { grid-template-columns: auto 1fr auto; }
.cd-choice--qty > .cd-input { align-self: center; }

/* A set of tick-boxes reads as a menu, not a form. Two columns once there is room
   roughly halves the scroll on the one step a client did not come here to do —
   nobody books an appointment in order to answer eight questions about rental
   properties, and a column of eight looks like a tax form, which is precisely the
   feeling the product exists to remove.

   Row-first, not column-first: the list length varies by service (an individual
   return offers eight, a BAS review none), and column-first ordering is only
   readable when you can guarantee the row count. */
.cd-choices { display: grid; gap: 0 var(--cd-space-6); }
@container cd (min-width: 560px) {
  .cd-choices { grid-template-columns: 1fr 1fr; }
}

/* ── The same question, drawn as a menu ───────────────────────────────────
   A bare tick-box against the page is a FORM: it says somebody needs this
   information from you. The identical question drawn as a card you press is
   a MENU: it says here is something you can have. Same control, same words,
   opposite feeling — and on the one screen where a client is deciding what
   their appointment includes, the menu is the truthful one. They are not
   filling anything in; they are choosing.

   Only the presentation is a modifier. The base .cd-choice stays exactly as
   it was, because admin and the wizard use it for real forms — settings,
   permissions, opt-ins — where it IS a form and should look like one.

   One column until there is room for two GENEROUS ones. The old 560px
   threshold was set for bare tick-boxes; a card carries a price and often a
   stepper as well, and at half of 560 there is nothing left for the words. */
.cd-choices--cards { gap: var(--cd-space-2); }
@container cd (min-width: 560px) {
  .cd-choices--cards { grid-template-columns: 1fr; }
}
@container cd (min-width: 46rem) {
  .cd-choices--cards { grid-template-columns: 1fr 1fr; }
}

.cd-choice--card {
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--cd-space-3);
  padding: var(--cd-space-3);
  background: var(--cd-surface);
  border: var(--cd-border) solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  transition:
    background-color var(--cd-dur-1) var(--cd-ease),
    border-color     var(--cd-dur-1) var(--cd-ease),
    box-shadow       var(--cd-dur-1) var(--cd-ease);
}

/* The tick sits on the row's centre line here, not on the first line of text:
   the card is one object, and a box nailed to the top-left of a two-line
   label reads as a bullet point. */
.cd-choice--card > .cd-check { margin-block-start: 0; }

/* Hover and selected on different channels, as everywhere. The :not(:has())
   guard is the same one .cd-option carries: without it, pointing at an add-on
   you have already ticked repaints it as an untouched card.

   :has() is the whole state mechanism here, and it degrades safely — a
   browser without it draws a resting card around a checkbox that is plainly
   ticked and filled with the accent. Quieter than intended, never wrong. */
.cd-choice--card:hover:not(:has(> .cd-check:checked)) {
  border-color: var(--cd-accent-line);
}
.cd-choice--card:has(> .cd-check:checked) {
  background: var(--cd-accent-subtle);
  border-color: var(--cd-ink);
  box-shadow: var(--cd-selected-ring);
}

/* Whatever trails the label — a stepper, a price, both. Wraps to its own line
   rather than crushing the words when the card is narrow. */
.cd-choice__trail {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--cd-space-2) var(--cd-space-3);
}
.cd-choice__price {
  font-size: var(--cd-text-sm);
  font-weight: var(--cd-weight-semi);
  color: var(--cd-ink-2);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* ── Quantity stepper ─────────────────────────────────────────────────────
   Two buttons and a number, in place of a spinner field. It replaces a
   <input type=number> for one reason: on a phone, tapping a number field
   raises the keypad over the thing you were reading, and "how many rental
   properties" is a question answered by pressing + twice, not by typing.

   The visible circle is 36px but the TARGET is 44px, expanded by the overlay
   below — --cd-tap is a floor and it does not bend for a small control. The
   arithmetic is exact and load-bearing: 36px circles with an 8px gap put the
   centres 44px apart, so two 44px targets sit edge to edge and neither steals
   a press meant for the other. Change the size and you must change the gap.

   The count is a polite live region, so pressing + announces "3" rather than
   leaving a screen reader with two buttons and no idea what they did. The
   buttons name the add-on as well as the direction ("One more — Standard
   rental"), because a page can hold six steppers and "plus" names none of
   them. */
.cd-qty {
  display: inline-flex;
  align-items: center;
  gap: var(--cd-space-2);
}
.cd-qty__btn {
  --cd-ring: 0 0 #0000;
  position: relative;
  display: grid; place-items: center;
  inline-size: 36px; block-size: 36px;
  padding: 0;
  color: var(--cd-ink);
  background: var(--cd-surface);
  border: var(--cd-border) solid var(--cd-control-line);
  border-radius: var(--cd-radius-pill);
  box-shadow: var(--cd-ring);
  font: inherit;
  font-size: var(--cd-text-md);
  font-weight: var(--cd-weight-semi);
  line-height: 1;
  cursor: pointer;
  transition:
    background-color var(--cd-dur-1) var(--cd-ease),
    border-color     var(--cd-dur-1) var(--cd-ease),
    color            var(--cd-dur-1) var(--cd-ease);
}
.cd-qty__btn::after {
  content: '';
  position: absolute;
  inset: calc((36px - var(--cd-tap)) / 2);
}
.cd-qty__btn:hover:not(:disabled) {
  border-color: var(--cd-ink);
  background: var(--cd-surface-2);
}
.cd-qty__btn:disabled {
  color: var(--cd-disabled-ink);
  border-color: var(--cd-line);
  cursor: not-allowed;
}
.cd-qty__num {
  min-inline-size: 1.5rem;
  text-align: center;
  font-weight: var(--cd-weight-semi);
  font-variant-numeric: tabular-nums;
}

.cd-check, .cd-radio {
  --cd-ring: 0 0 #0000;
  position: relative;
  flex: none;
  inline-size: 22px; block-size: 22px;
  margin: 0; margin-block-start: 0.12em;
  background: var(--cd-surface);
  border: var(--cd-border-strong) solid var(--cd-control-line);
  box-shadow: var(--cd-ring);
  appearance: none;
  cursor: pointer;
  transition: background-color var(--cd-dur-1) var(--cd-ease), border-color var(--cd-dur-1) var(--cd-ease);
}
.cd-check { border-radius: var(--cd-radius-xs); }
.cd-radio { border-radius: var(--cd-radius-pill); }

.cd-check:checked, .cd-radio:checked {
  background: var(--cd-accent-mark);
  border-color: var(--cd-accent-mark);
}
.cd-check:checked::after {
  content: "";
  position: absolute;
  inset-block-start: 2px; inset-inline-start: 6px;
  inline-size: 6px; block-size: 11px;
  border-inline-end: 2.2px solid var(--cd-on-mark);
  border-block-end: 2.2px solid var(--cd-on-mark);
  rotate: 45deg;
}
.cd-radio:checked::after {
  content: "";
  position: absolute; inset: 4px;
  border-radius: var(--cd-radius-pill);
  background: var(--cd-on-mark);
}
.cd-check:disabled, .cd-radio:disabled { background: var(--cd-disabled-bg); border-color: var(--cd-ink-4); cursor: not-allowed; }


/* ═══ 8a · OPTION LIST ═════════════════════════════════════════════════════
   The service picker — the first screen a client sees, and the only one with
   twelve things on it.

   It is a radiogroup, not a row of buttons. Twelve .cd-btn--primary would give
   twelve choices the same shout as "Confirm booking", and a primary button
   means "this is the thing to do next" — it cannot also mean "this is the one
   you picked". So the resting state is a hairline on the surface and the chosen
   row is the only one that takes any accent at all.

   Markup contract (roving tabindex is JS; every state below is CSS):

     <div class="cd-options" role="radiogroup" aria-label="Appointment type">
       <button class="cd-option" type="button" role="radio"
               aria-checked="false" tabindex="-1">
         <span class="cd-option__mark" aria-hidden="true"></span>
         <span class="cd-option__text">
           <span class="cd-option__name">Individual Tax Return</span>
           <span class="cd-option__note">Your personal income tax return…</span>
         </span>
         <span class="cd-option__meta">30 min · $220</span>
       </button>
       …

   The mark is a drawn radio, not a tick that appears on selection: an empty
   ring tells the client this is a list they pick ONE from before they have
   picked anything. It also means the chosen state survives forced colours,
   where the wash and the hairline are both stripped — colour is never the only
   signal (BRAND.md §11). */

.cd-options { display: flex; flex-direction: column; gap: var(--cd-space-2); }

/* A ROW of choices rather than a list of them. For a short, closed set where the
   options are alternatives to weigh against each other — three ways of meeting,
   two people the return is for — side by side is how you compare; stacked is how
   you read down until one matches, which is a different and slower act.

   Only for short sets. Past four or five the row wraps into a ragged block and a
   plain list is both calmer and quicker, which is why the caller decides rather
   than the component. */
.cd-options--wrap {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
}

/* Categories are visual grouping inside ONE choice, so they are role="group"
   inside a single radiogroup rather than a radiogroup each — picking a tax
   return has to clear a pick made under Advisory. */
.cd-options__group { display: flex; flex-direction: column; gap: var(--cd-space-2); }
.cd-options__group + .cd-options__group { margin-block-start: var(--cd-space-4); }
.cd-options__group > .cd-eyebrow { margin-block-end: var(--cd-space-1); }

/* Flex, not grid, and that is a deliberate downgrade. The row has four possible
   parts — a glyph, the words, a figure, the tick — and only the words are always
   there. Grid columns for optional children means either a phantom gap wherever
   one is missing, or an explicit placement rule per child; flex simply closes up.

   The order along the row is fixed and means something: what it IS on the left,
   what it COSTS at the far end, and whether you picked it hard against the right
   edge — so a column of tick marks lines up under your thumb, and the tick sits
   where the tile's own tick sits. */
.cd-option {
  --cd-ring: 0 0 #0000;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--cd-space-1) var(--cd-space-3);
  inline-size: 100%;
  min-block-size: var(--cd-tap);
  padding: var(--cd-space-3) var(--cd-space-4);
  text-align: start;
  color: var(--cd-ink);
  background: var(--cd-surface);
  border: var(--cd-border) solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  box-shadow: var(--cd-ring);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition:
    background-color var(--cd-dur-1) var(--cd-ease),
    border-color     var(--cd-dur-1) var(--cd-ease),
    box-shadow       var(--cd-dur-1) var(--cd-ease),
    translate        var(--cd-dur-1) var(--cd-ease);
}

/* HOVER AND SELECTED USE DIFFERENT CHANNELS, EVERYWHERE.
   Hover is an accent BORDER plus a two-pixel lift. Selected is a FILL or a wash
   plus a drawn mark. Never the reverse, and never both on the same channel.

   The rule is worth stating because the obvious implementation breaks it: tint
   the background on hover, tint it harder when chosen, and the two states become
   the same gesture at two strengths — so "is this the one I picked, or the one my
   cursor is on?" stops having an answer, and on a touch screen, where hover can
   stick after a tap, it stops having an answer permanently.

   The lift distance is multiplied by --cd-motion, so reduced-motion removes the
   movement rather than merely shortening it. */
.cd-option:hover:not([aria-disabled="true"], :disabled, [aria-checked="true"]) {
  border-color: var(--cd-accent-line);
  box-shadow: var(--cd-ring), var(--cd-shadow-2);
  translate: 0 calc(-2px * var(--cd-motion));
}

.cd-option[aria-checked="true"] {
  background: var(--cd-accent-subtle);
  border-color: var(--cd-ink);
  box-shadow: var(--cd-ring), var(--cd-selected-ring);
}

/* The optional glyph. A disc, the same one the tile wears, at the size a row can
   carry — it is what turns "In our office" from a line of text into a thing. */
.cd-option__icon {
  flex: none;
  display: grid; place-items: center;
  inline-size: 38px; block-size: 38px;
  color: var(--cd-ink-2);
  background: var(--cd-surface-2);
  border-radius: var(--cd-radius-pill);
  transition:
    color            var(--cd-dur-1) var(--cd-ease),
    background-color var(--cd-dur-1) var(--cd-ease);
}
.cd-option__icon svg { inline-size: 20px; block-size: 20px; }
.cd-option[aria-checked="true"] .cd-option__icon {
  color: var(--cd-on-mark);
  background: var(--cd-accent-mark);
}

.cd-option__text { flex: 1 1 8rem; min-inline-size: 0; }

/* Last along the row, always. `order` rather than source position, so the mark
   can stay first in the DOM — it is the control's own state, and a screen reader
   reaching the row should meet "selected" before it meets the label. */
.cd-option__mark {
  flex: none;
  order: 3;
  display: grid; place-items: center;
  inline-size: 20px; block-size: 20px;
  background: var(--cd-surface);
  border: var(--cd-border-strong) solid var(--cd-control-line);
  border-radius: var(--cd-radius-pill);
  transition:
    background-color var(--cd-dur-1) var(--cd-ease),
    border-color     var(--cd-dur-1) var(--cd-ease);
}
.cd-option[aria-checked="true"] .cd-option__mark {
  background: var(--cd-accent-mark);
  border-color: var(--cd-accent-mark);
}
.cd-option[aria-checked="true"] .cd-option__mark::after {
  content: "";
  inline-size: 7px; block-size: 7px;
  border-radius: var(--cd-radius-pill);
  background: var(--cd-on-mark);
}

/* Unavailable. It stays in the list — a service that silently vanishes reads as
   a bug, and the note is where the practice explains why (BRAND.md §5: an empty
   state always offers the next move). Struck name + flattened mark, so the state
   is legible without relying on the wash. */
.cd-option[aria-disabled="true"],
.cd-option:disabled {
  background: var(--cd-disabled-bg);
  border-color: transparent;
  color: var(--cd-disabled-ink);
  box-shadow: var(--cd-ring);
  cursor: not-allowed;
}
.cd-option[aria-disabled="true"] .cd-option__name { text-decoration: line-through; text-decoration-thickness: 1px; }
.cd-option[aria-disabled="true"] :is(.cd-option__note, .cd-option__meta) { color: inherit; }
.cd-option[aria-disabled="true"] .cd-option__mark {
  background: transparent;
  border-color: var(--cd-ink-4);
}

.cd-option__name {
  display: block;
  font-weight: var(--cd-weight-semi);
  line-height: var(--cd-leading-snug);
  letter-spacing: var(--cd-track-tight);
}
.cd-option__note {
  display: block;
  margin-block-start: 2px;
  font-size: var(--cd-text-sm);
  line-height: var(--cd-leading-snug);
  color: var(--cd-ink-3);
}
/* Pushed to the far end by its own auto margin, so a scan down the list compares
   figures against one edge whether or not the row above it had a glyph. Sits at
   order 2 — after the words, before the tick. */
.cd-option__meta {
  flex: none;
  order: 2;
  margin-inline-start: auto;
  font-size: var(--cd-text-sm);
  color: var(--cd-ink-2);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}


/* ═══ 8b · SEGMENTED CHOICE ════════════════════════════════════════════════
   Format, office, adviser — the questions on the "How" step.

   Deliberately quiet. That step asks three of these at once and then offers
   Continue; if each selection rendered as .cd-btn--primary the client would
   face four solid accent slabs and no way to tell which one moves them
   forward. So exactly ONE thing on any step wears the solid accent — the
   button that advances — and a chosen segment is marked with the accent wash,
   an accent hairline, a weight change and a filled dot.

   The label stays --cd-ink rather than --cd-accent-ink: the contrast guarantee
   (tokens.css §5) covers ink on the accent wash, and it is the pair we can
   promise on an accent we have never seen.

     <div class="cd-segmented" role="radiogroup" aria-label="How to meet">
       <button class="cd-segment" type="button" role="radio"
               aria-checked="true" tabindex="0">In our office</button>
       … */

.cd-segmented { display: flex; flex-wrap: wrap; gap: var(--cd-space-2); }

.cd-segment {
  --cd-ring: 0 0 #0000;
  display: inline-flex;
  align-items: center;
  gap: var(--cd-space-2);
  min-block-size: var(--cd-tap);
  padding: var(--cd-space-2) var(--cd-space-4);
  font-size: var(--cd-text-base);
  font-weight: var(--cd-weight-medium);
  letter-spacing: var(--cd-track-tight);
  line-height: var(--cd-leading-snug);
  color: var(--cd-ink);
  background: var(--cd-surface);
  border: var(--cd-border) solid var(--cd-control-line);
  border-radius: var(--cd-radius-pill);
  box-shadow: var(--cd-ring);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition:
    background-color var(--cd-dur-1) var(--cd-ease),
    border-color     var(--cd-dur-1) var(--cd-ease),
    translate        var(--cd-dur-1) var(--cd-ease);
}

/* The dot. Not decoration — it is what still says "chosen" once forced colours
   have removed the wash and flattened the border. */
.cd-segment::before {
  content: "";
  flex: none;
  inline-size: 9px; block-size: 9px;
  background: transparent;
  border: var(--cd-border-strong) solid var(--cd-control-line);
  border-radius: var(--cd-radius-pill);
  transition:
    background-color var(--cd-dur-1) var(--cd-ease),
    border-color     var(--cd-dur-1) var(--cd-ease);
}

.cd-segment:hover:not([aria-disabled="true"], :disabled, [aria-checked="true"]) {
  border-color: var(--cd-accent-line);
  translate: 0 calc(-1px * var(--cd-motion));
}

.cd-segment[aria-checked="true"] {
  background: var(--cd-accent-subtle);
  border-color: var(--cd-accent-line);
  font-weight: var(--cd-weight-semi);
}
.cd-segment[aria-checked="true"]::before {
  background: var(--cd-accent-mark);
  border-color: var(--cd-accent-mark);
}

.cd-segment:disabled, .cd-segment[aria-disabled="true"] {
  background: var(--cd-disabled-bg);
  color: var(--cd-disabled-ink);
  border-color: transparent;
  cursor: not-allowed;
}
.cd-segment__note { font-weight: var(--cd-weight-regular); color: var(--cd-ink-3); }

/* — a segment that is a SWITCH ———————————————————————————————————————————
   role="checkbox", not role="radio", and the difference is not pedantry. A
   radio group carries its own contrast: one pill is chosen, and the rest are
   visibly not it. A checkbox group can be ALL ON — which is how the setup
   wizard's "how do you meet clients?" opens — and then every pill is in the
   same state, there is nothing on screen to compare against, and three
   switches read as three labels.

   So the marker stops being the radio's dot and becomes the box this file
   already uses for on and off (§8, .cd-check): same shape, same 2.2px drawn
   tick, same accent fill. Off is drained rather than merely unwashed — muted
   ink and an empty box — so the state survives being seen sideways.

     <button class="cd-segment cd-segment--toggle" type="button"
             role="checkbox" aria-checked="true">In our office</button>

   Rendered by the working-week editor (app/week.js, §8e below) as the seven day
   pills; the delivery modes are still plain .cd-segment radios. */
.cd-segment--toggle { position: relative; color: var(--cd-ink-2); }
.cd-segment--toggle::before {
  inline-size: 18px; block-size: 18px;
  border-width: var(--cd-border);
  border-radius: var(--cd-radius-xs);
}
.cd-segment--toggle[aria-checked="true"] { color: var(--cd-ink); }

/* The tick is a separate pseudo-element because ::before is already the box,
   and it is positioned against the pill's own padding rather than nested in
   the box — so the two cannot drift apart when the padding token moves.
   50% is the padding box's mid-line, and the pill's block padding is
   symmetrical, so it is also the box's. */
.cd-segment--toggle[aria-checked="true"]::after {
  content: "";
  position: absolute;
  inset-block-start: calc(50% - 7px);
  inset-inline-start: calc(var(--cd-space-4) + 5px);
  inline-size: 5px; block-size: 9px;
  /* --cd-on-mark, not --cd-on-accent: the box it sits in is --cd-accent-mark, and the
     two are only the same colour on the dark scheme. On the light one the tick vanished
     into its own box. */
  border-inline-end: 2.2px solid var(--cd-on-mark);
  border-block-end: 2.2px solid var(--cd-on-mark);
  rotate: 45deg;
}


/* ═══ 8e · THE WORKING WEEK ═══════════════════════════════════════════════
   Seven day pills, then the hours. Rendered by app/week.js for the setup wizard
   (the whole practice) and the admin's People cards (one person) — the markup
   below is what it writes, so the two places cannot drift.

   One line of hours when every open day shares them, a row per OPEN day when
   they differ. A closed day gets no row: it was decided in the pill row, and
   the product's rule is never to show the same decision twice. The selects are
   sized to their text rather than stretched across the card — "9am" does not
   need thirty characters of field to say it.

     <div class="cd-week">
       <div class="cd-week__days" role="group" aria-label="Days open">
         <button class="cd-segment cd-segment--toggle" role="checkbox"
                 aria-checked="true" data-day="1">Mon</button> …
       </div>
       <div class="cd-week__rows">
         <div class="cd-week__row">
           <span class="cd-week__label">Hours</span>
           <div class="cd-week__ranges">
             <span class="cd-week__range">
               <span class="cd-select-wrap"><select class="cd-select cd-week__time">…</select></span>
               <span class="cd-meta">to</span>
               <span class="cd-select-wrap"><select class="cd-select cd-week__time">…</select></span>
             </span>
             <button class="cd-link cd-week__add">+ Add a break</button>
           </div>
         </div>
       </div>
       <p class="cd-week__foot"><button class="cd-link">Different hours on some days</button></p>
     </div> */

.cd-week { display: grid; gap: var(--cd-space-4); }
.cd-week__days { display: flex; flex-wrap: wrap; gap: var(--cd-space-2); }
.cd-week__rows { display: grid; gap: var(--cd-space-3); }
.cd-week__row {
  display: grid;
  grid-template-columns: minmax(5.5rem, auto) minmax(0, 1fr);
  gap: var(--cd-space-3);
  align-items: center;
}
.cd-week__label { font-weight: var(--cd-weight-semi); letter-spacing: var(--cd-track-tight); }
.cd-week__ranges {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--cd-space-2) var(--cd-space-3);
}
.cd-week__range { display: inline-flex; align-items: center; gap: var(--cd-space-2); }
.cd-week__range .cd-select-wrap { display: inline-block; }
/* Sized to its own text. The design system's select is 100% wide for forms; a
   time in a sentence is not a form. */
.cd-week__time { inline-size: auto; min-inline-size: 0; }
.cd-week__remove { inline-size: calc(var(--cd-tap) * 0.75); font-size: var(--cd-text-lg); }
.cd-week__add { white-space: nowrap; }
.cd-week__foot { margin: 0; }
@media (max-width: 480px) {
  .cd-week__row { grid-template-columns: 1fr; gap: var(--cd-space-1); }
}


/* ═══ 8c · CHOICE TILES ════════════════════════════════════════════════════
   The FIRST question, and the only one in the flow where a client is choosing
   a destination rather than a line item.

   A glyph over a word, and that is the entire argument for the component
   existing. A client arriving to book a tax return is not reading — they are
   looking for the word "tax" — so a row of tiles is meant to be taken in at a
   glance, where four .cd-option rows are a list, and a list gets read from the
   top, slowly, or not at all.

   Which is why the tile carries NO description. It used to, and the line was
   the difference between a row of objects and four short paragraphs: whatever
   a tile says under its name, a client reads, and having read all four they
   have done exactly the work the tier existed to save them. If a name needs
   explaining, the shortlist one tap below it is where the explaining goes.

   Everything under a tile is then deliberately NOT a tile: the shortlist that
   appears is .cd-option rows, so the change of shape does the work a "step 1b"
   heading would otherwise have to do. Same grammar, different rank.

     <div class="cd-tiles" role="radiogroup" aria-label="What can we help with?">
       <button class="cd-tile" type="button" role="radio"
               aria-checked="false" tabindex="-1">
         <span class="cd-tile__icon" aria-hidden="true"><svg …></span>
         <span class="cd-tile__name">Tax Return</span>
       </button>

   The chosen tile says so twice: the disc fills with the accent, and a tick
   appears in the corner. That is not belt and braces — they answer different
   questions. The filled disc is what you see from a metre away, scanning a row;
   the corner tick is what you look for when you come back to the screen and
   need to know which one you left selected. An unchosen tile draws neither, so
   a resting row is four clean objects rather than four empty rings.

   Both survive forced colours, where the wash does not (BRAND.md §11, §17). */

/* 8.5rem, not 9 or 10, and the number is load-bearing. Two columns have to fit
   the 320px case: 288px of content minus one 8px gap leaves 140px a tile, and
   auto-fit will not place a second column unless the minimum clears it. A 10rem
   minimum drops a phone to one tile per row, which is the .cd-option layout
   again with more padding — the exact thing this component exists to avoid. */
.cd-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
  gap: var(--cd-space-2);
}
/* Past a phone, the minimum comes down so that FOUR tiles fit a narrow embed —
   the widget in a firm's sidebar, the demo device on our own homepage — in one
   row. At 8.5rem a 33rem container takes three across and strands the fourth
   on a row of its own, which is the "list read from the top" the component
   exists to avoid, wearing a square. 7rem still holds a two-line name and a
   two-line note; below this width nothing changes, so the 320px case above
   keeps its two columns. auto-fit still collapses what is not needed, so a
   wide hosted page with four categories draws four wide tiles exactly as
   before — only the wrap point moves. */
@container cd (min-width: 28rem) {
  .cd-tiles { grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr)); }
}

.cd-tile {
  --cd-ring: 0 0 #0000;
  position: relative;
  display: grid;
  grid-template-rows: auto auto;
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  /* Centred in its own box. The row stretches every tile to the tallest, so a
     two-line name somewhere along it would otherwise leave the single-line
     tiles top-heavy with a band of nothing underneath. */
  align-content: center;
  gap: var(--cd-space-2);
  /* A floor, not a shape. The tile used to force itself square off its own
     width — a zero-width strut measuring 100cqi — because it had three rows of
     content and needed room for the deepest of them without clipping. With the
     note gone there are two rows and they are both short, so the square became
     a large empty box with a word near the top of it: at four across on a wide
     page each tile was 170px tall to hold 70px of content.

     A minimum instead, and content still wins — a category named "Advice or
     Training" that wraps to two lines at 110px grows the tile rather than
     losing the second word, and a practice names its own categories, so we
     never get to see them first. */
  min-block-size: 8.5rem;
  padding: var(--cd-space-4) var(--cd-space-3);
  /* Centred, and only here. Everything else in the system reads down a left
     edge, because everything else is a LIST — you scan it. A row of category
     tiles is not scanned, it is looked at: four objects side by side, each
     one icon over one name, and the eye takes the row in at once. Centring is
     what makes them read as objects rather than as four short paragraphs.
     It survives because the note under the name is generated, never authored
     — "Individual Tax Return +1 more" — so it is always short. A tile that
     carried a sentence would have to go back to a left edge. */
  text-align: center;
  color: var(--cd-ink);
  background: var(--cd-surface);
  border: var(--cd-border) solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  box-shadow: var(--cd-ring);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition:
    background-color var(--cd-dur-1) var(--cd-ease),
    border-color     var(--cd-dur-1) var(--cd-ease),
    box-shadow       var(--cd-dur-1) var(--cd-ease),
    translate        var(--cd-dur-1) var(--cd-ease);
}
@container cd (min-width: 480px) {
  .cd-tile { padding: var(--cd-space-4); gap: var(--cd-space-3); }
}
.cd-tile__icon { grid-area: 1 / 1; }
.cd-tile__name { grid-area: 2 / 1; }

/* Hover is border + lift; selected is wash + mark. Different channels, and the
   selected state is excluded from hover for the reason written out at .cd-slot:
   `:hover:not(…)` out-ranks `[aria-checked]`, so without the guard, pointing at
   your own chosen tile repaints it as the resting surface. */
.cd-tile:hover:not([aria-disabled="true"], :disabled, [aria-checked="true"]) {
  border-color: var(--cd-accent-line);
  box-shadow: var(--cd-ring), var(--cd-shadow-2);
  translate: 0 calc(-2px * var(--cd-motion));
}

/* Three channels at once — wash, ink outline, filled disc — and the tile is
   still legible if any two of them fail. See --cd-selected-ring in tokens.css
   for why the outline is ink and not the firm's colour. */
.cd-tile[aria-checked="true"] {
  background: var(--cd-accent-subtle);
  border-color: var(--cd-ink);
  box-shadow: var(--cd-ring), var(--cd-selected-ring);
}

/* The glyph is drawn by the caller and dressed from here, so one stroke
   weight and one size govern every category whatever glyph it resolved to.

   It sits on a disc, and the disc is what fills when the tile is
   chosen. Two jobs in one shape: a bare 26px stroke icon on a bare card has
   no weight at the size a tile is actually looked at (about 120px across in a
   narrow embed), and a category with nothing but a thin outline and a word
   under it reads as a list item that has been spaced out, not as a thing to
   press. Giving it a filled ground gives the tile a centre of gravity.

   --cd-accent-mark and --cd-on-mark rather than the accent itself, because
   this disc IS the signal that the tile is selected, which is the exact job
   guarantees 6 and 7 exist for: a light-yellow practice gets an ink disc with
   yellow strokes rather than a yellow disc that vanishes into the wash behind
   it. See §5 of tokens.css. */
.cd-tile__icon {
  display: grid;
  place-items: center;
  inline-size: 58px; block-size: 58px;
  color: var(--cd-ink-2);
  background: var(--cd-surface-2);
  border-radius: var(--cd-radius-pill);
  transition:
    color            var(--cd-dur-1) var(--cd-ease),
    background-color var(--cd-dur-1) var(--cd-ease);
}
.cd-tile__icon svg { inline-size: 26px; block-size: 26px; }
.cd-tile[aria-checked="true"] .cd-tile__icon {
  color: var(--cd-on-mark);
  background: var(--cd-accent-mark);
}

/* Drawn only when chosen — see the note at the top of the section. */
.cd-tile__mark {
  position: absolute;
  inset-block-start: var(--cd-space-3);
  inset-inline-end: var(--cd-space-3);
  display: none;
  place-items: center;
  inline-size: 22px; block-size: 22px;
  background: var(--cd-accent-mark);
  border-radius: var(--cd-radius-pill);
}
.cd-tile[aria-checked="true"] .cd-tile__mark { display: grid; }
.cd-tile__mark::after {
  content: "";
  inline-size: 5px; block-size: 9px;
  border-inline-end: 2px solid var(--cd-on-mark);
  border-block-end: 2px solid var(--cd-on-mark);
  rotate: 45deg;
  translate: 0 -1px;
}

.cd-tile__name {
  font-weight: var(--cd-weight-semi);
  line-height: var(--cd-leading-snug);
  letter-spacing: var(--cd-track-tight);
  text-wrap: balance;
}


.cd-tile[aria-disabled="true"], .cd-tile:disabled {
  background: var(--cd-disabled-bg);
  border-color: transparent;
  color: var(--cd-disabled-ink);
  box-shadow: var(--cd-ring);
  cursor: not-allowed;
}
.cd-tile[aria-disabled="true"] .cd-tile__icon {
  color: var(--cd-disabled-ink);
  background: transparent;
  border: var(--cd-border) solid var(--cd-ink-4);
}


/* ═══ 8d · PEOPLE PICKER ═══════════════════════════════════════════════════
   "Who would you like to see?" — the one question in the flow that is about a
   person, answered until now by a row of pills carrying nothing but a name.

   A face is not decoration here. The product's whole claim against a call
   centre is that a client belongs to their accountant (see `pickStaff` — the
   relationship beats round-robin), and a client who recognises Priya picks
   Priya. A name in a pill gives them nothing to recognise.

   Degrades honestly: a practice that has uploaded no photographs gets initials
   on the accent wash, which reads as a considered monogram rather than as a
   missing image. There is no placeholder silhouette, because a grid of
   identical grey silhouettes is worse than a grid of pills.

     <div class="cd-people" role="radiogroup" aria-label="Who would you like to see?">
       <button class="cd-person" type="button" role="radio"
               aria-checked="true" tabindex="0">
         <span class="cd-person__avatar" aria-hidden="true">PR</span>
         <span class="cd-person__text">
           <span class="cd-person__name">Priya Raman</span>
           <span class="cd-person__title">Chartered Accountant</span>
         </span>
       </button> */

.cd-people {
  display: grid;
  /* 13rem, up from 11: the card now carries a tick at its right-hand end as well
     as a face and two lines of words, and at 11rem the name and the title were
     the two things with any give in them. */
  grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
  gap: var(--cd-space-2);
}

/* Same grammar as .cd-option, one row down in rank: a face where the glyph goes,
   the words, and the tick hard against the right edge. */
.cd-person {
  --cd-ring: 0 0 #0000;
  display: flex;
  align-items: center;
  gap: var(--cd-space-3);
  min-block-size: var(--cd-tap);
  padding: var(--cd-space-2) var(--cd-space-3);
  text-align: start;
  color: var(--cd-ink);
  background: var(--cd-surface);
  border: var(--cd-border) solid var(--cd-line);
  border-radius: var(--cd-radius-md);
  box-shadow: var(--cd-ring);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition:
    background-color var(--cd-dur-1) var(--cd-ease),
    border-color     var(--cd-dur-1) var(--cd-ease),
    box-shadow       var(--cd-dur-1) var(--cd-ease),
    translate        var(--cd-dur-1) var(--cd-ease);
}
.cd-person:hover:not([aria-disabled="true"], :disabled, [aria-checked="true"]) {
  border-color: var(--cd-accent-line);
  box-shadow: var(--cd-ring), var(--cd-shadow-1);
  translate: 0 calc(-2px * var(--cd-motion));
}
.cd-person[aria-checked="true"] {
  background: var(--cd-accent-subtle);
  border-color: var(--cd-ink);
  box-shadow: var(--cd-ring), var(--cd-selected-ring);
}

.cd-person__avatar {
  flex: none;
  position: relative;
  display: grid; place-items: center;
  inline-size: 38px; block-size: 38px;
  overflow: hidden;
  border-radius: var(--cd-radius-pill);
  background: var(--cd-surface-2);
  color: var(--cd-ink-2);
  font-size: var(--cd-text-sm);
  font-weight: var(--cd-weight-semi);
  letter-spacing: 0.02em;
  /* An uploaded photograph is not ours and may be any aspect. Cover, centred,
     and the ring is drawn INSIDE as a shadow so it lands on top of the image
     rather than fighting it for the same pixel. */
  box-shadow: inset 0 0 0 1px var(--cd-line);
}
.cd-person__avatar { flex: none; }
.cd-person__text { flex: 1 1 auto; min-inline-size: 0; }

/* The same drawn ring .cd-option uses, so "chosen" is one shape wherever a
   client meets it. The avatar ring below stays as well — at a glance down six
   faces the ring is what carries, and the tick is what confirms. */
.cd-person__mark {
  flex: none;
  order: 3;
  margin-inline-start: auto;
  display: grid; place-items: center;
  inline-size: 20px; block-size: 20px;
  background: var(--cd-surface);
  border: var(--cd-border-strong) solid var(--cd-control-line);
  border-radius: var(--cd-radius-pill);
  transition:
    background-color var(--cd-dur-1) var(--cd-ease),
    border-color     var(--cd-dur-1) var(--cd-ease);
}
.cd-person[aria-checked="true"] .cd-person__mark {
  background: var(--cd-accent-mark);
  border-color: var(--cd-accent-mark);
}
.cd-person[aria-checked="true"] .cd-person__mark::after {
  content: "";
  inline-size: 7px; block-size: 7px;
  border-radius: var(--cd-radius-pill);
  background: var(--cd-on-mark);
}

.cd-person__avatar img {
  position: absolute; inset: 0;
  inline-size: 100%; block-size: 100%;
  object-fit: cover;
}
/* The chosen person is ringed as well as washed, so the answer survives at a
   glance down a row of six faces where a pale wash does not. */
.cd-person[aria-checked="true"] .cd-person__avatar {
  /* accent-line, not solid: a ring is a border job (guarantee 3), and a light
     accent's solid ring disappeared against the wash it sits on. */
  box-shadow: inset 0 0 0 2px var(--cd-accent-line);
}

.cd-person__text { min-inline-size: 0; }
.cd-person__name {
  display: block;
  font-weight: var(--cd-weight-medium);
  line-height: var(--cd-leading-snug);
  letter-spacing: var(--cd-track-tight);
}
.cd-person[aria-checked="true"] .cd-person__name { font-weight: var(--cd-weight-semi); }
.cd-person__title {
  display: block;
  font-size: var(--cd-text-sm);
  line-height: var(--cd-leading-snug);
  color: var(--cd-ink-3);
}

/* "No preference" is a real answer, not an absent one, so it gets a card of the
   same size with the waterline in place of a face. The device doing a job:
   still water is exactly what "we'll choose for you" should look like. */
.cd-person--any .cd-person__avatar { background: var(--cd-surface-2); }
.cd-person--any .cd-person__avatar::after {
  content: "";
  inline-size: 16px; block-size: 2px;
  border-radius: 1px;
  background: var(--cd-ink-4);
}

/* Not a choice — a statement. When only one person can take the appointment the
   client is TOLD who, never asked, so the card loses its border, its hover and
   its pointer and sits on the sunken surface like the answer it is. */
.cd-person--fixed {
  background: var(--cd-surface-2);
  border-color: transparent;
  cursor: default;
}


/* ═══ 9 · SLOT PICKER ══════════════════════════════════════════════════════
   The centre of the product. A time grid the client arrows through.

   Markup contract (roving tabindex is JS; every state below is CSS):

     <div class="cd-slots" role="radiogroup" aria-label="Available times">
       <div class="cd-slots__group">
         <p class="cd-eyebrow" id="am">Morning</p>
         <div class="cd-slots__grid" role="group" aria-labelledby="am">
           <button class="cd-slot" role="radio" aria-checked="false"
                   tabindex="-1">9:00am</button>
           …

   radiogroup, not grid: exactly one time can be chosen, arrow-key traversal is
   the behaviour a screen-reader user already expects from a radiogroup, and it
   does not require us to lie about rows in a wrapping layout.

   Arrow keys move in two dimensions across the rendered grid (Up/Down step by
   the measured column count, so wrapping is handled), Home/End jump to the
   first and last available slot, Enter/Space choose. Exactly one slot carries
   tabindex="0", so the whole picker is a single tab stop.

   aria-pressed is also honoured, for hosts that render the picker as a toggle
   group instead. */

.cd-slots__group + .cd-slots__group { margin-block-start: var(--cd-space-5); }
.cd-slots__group > .cd-eyebrow { margin-block-end: var(--cd-space-3); }

.cd-slots__grid {
  display: grid;
  /* 84px holds "10:30am" at 14px with room to spare. At 320px minus padding
     that yields three columns; at 1440px it fills the card. */
  grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
  gap: var(--cd-space-2);
}

.cd-slot {
  --cd-ring: 0 0 #0000;
  position: relative;
  display: flex; align-items: center; justify-content: center;
  min-block-size: var(--cd-tap);
  padding: var(--cd-space-2) var(--cd-space-2);
  font-size: var(--cd-text-sm);
  font-weight: var(--cd-weight-medium);
  letter-spacing: var(--cd-track-normal);
  color: var(--cd-ink);
  background: var(--cd-surface);
  border: var(--cd-border) solid var(--cd-control-line);
  border-radius: var(--cd-radius-sm);
  box-shadow: var(--cd-ring);
  cursor: pointer;
  overflow: hidden;
  -webkit-tap-highlight-color: transparent;
  transition:
    background-color var(--cd-dur-1) var(--cd-ease),
    border-color     var(--cd-dur-1) var(--cd-ease),
    color            var(--cd-dur-1) var(--cd-ease),
    box-shadow       var(--cd-dur-1) var(--cd-ease),
    translate        var(--cd-dur-1) var(--cd-ease);
}

/* The chosen slot is excluded from hover, and that exclusion is load-bearing rather
   than cosmetic. `:hover:not(…)` computes to (0,3,0) and out-ranks the (0,2,0) of
   `[aria-checked="true"]`, so without it, pointing at your own chosen time repaints
   it as the pale wash while the label stays --cd-on-mark — near-white on
   near-white. The one slot a client most wants to re-read becomes the one they
   cannot. Every selected state below carries the same guard for the same reason. */
.cd-slot:hover:not([aria-disabled="true"], :disabled,
                   [aria-pressed="true"], [aria-checked="true"]) {
  border-color: var(--cd-accent-line);
  box-shadow: var(--cd-ring), var(--cd-shadow-1);
  translate: 0 calc(-2px * var(--cd-motion));
}

.cd-slot[aria-pressed="true"],
.cd-slot[aria-checked="true"] {
  background: var(--cd-accent-mark);
  border-color: var(--cd-accent-mark);
  color: var(--cd-on-mark);
  font-weight: var(--cd-weight-semi);
}

/* Selection ripple — one expanding ring, once. The only ornamental motion in
   the whole booking flow, and --cd-motion removes it entirely. */
.cd-slot[aria-pressed="true"]::before,
.cd-slot[aria-checked="true"]::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: currentColor;
  opacity: 0;
  animation: cd-ripple var(--cd-dur-3) var(--cd-ease) 1;
}
@keyframes cd-ripple {
  from { opacity: 0.28; scale: calc(1 - 0.35 * var(--cd-motion)); }
  to   { opacity: 0;    scale: 1; }
}

/* Held: another client is mid-checkout on this slot. It stays visible — a
   disappearing grid is disorienting — but it is not operable, and the reason
   is written down rather than implied by a shade of grey. */
.cd-slot[data-state="held"] {
  background: repeating-linear-gradient(
      -45deg,
      var(--cd-surface-2) 0 5px,
      var(--cd-surface) 5px 10px);
  border-style: dashed;
  border-color: var(--cd-line-strong);
  color: var(--cd-ink-3);
  cursor: not-allowed;
}
.cd-slot[aria-disabled="true"]:not([data-state="held"]),
.cd-slot:disabled {
  background: var(--cd-disabled-bg);
  border-color: transparent;
  color: var(--cd-disabled-ink);
  cursor: not-allowed;
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}

.cd-slots__more {
  margin-block-start: var(--cd-space-4);
  display: flex; justify-content: center;
}


/* ═══ 10 · DATE NAVIGATOR ══════════════════════════════════════════════════
   At 320px this is a snapping strip, because seven cells across 288px would
   put every tap target under 44px. Once the container clears 480px it becomes
   a plain seven-column week. Dates read DD/MM. */

.cd-datenav {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--cd-space-2);
}

.cd-datenav__month {
  grid-column: 1 / -1;
  order: -1;
  font-size: var(--cd-text-sm);
  font-weight: var(--cd-weight-semi);
  letter-spacing: var(--cd-track-tight);
  color: var(--cd-ink-2);
  text-align: center;
}

.cd-datenav__days {
  display: flex;
  gap: var(--cd-space-2);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: thin;
  padding-block-end: var(--cd-space-1);
  overscroll-behavior-x: contain;
}
.cd-datenav__days > * { scroll-snap-align: center; }

@container cd (min-width: 480px) {
  .cd-datenav__days {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    overflow: visible;
  }
}

.cd-day {
  --cd-ring: 0 0 #0000;
  position: relative;
  flex: 0 0 auto;
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 2px;
  min-inline-size: 58px;
  min-block-size: 60px;
  padding: var(--cd-space-2) var(--cd-space-1);
  color: var(--cd-ink);
  background: var(--cd-surface);
  border: var(--cd-border) solid var(--cd-line);
  border-radius: var(--cd-radius-sm);
  box-shadow: var(--cd-ring);
  cursor: pointer;
  transition: background-color var(--cd-dur-1) var(--cd-ease), border-color var(--cd-dur-1) var(--cd-ease),
              box-shadow var(--cd-dur-1) var(--cd-ease), translate var(--cd-dur-1) var(--cd-ease);
}
.cd-day__dow { font-size: var(--cd-text-2xs); letter-spacing: var(--cd-track-eyebrow); text-transform: uppercase; color: var(--cd-ink-3); }
.cd-day__num { font-size: var(--cd-text-sm); font-weight: var(--cd-weight-semi); line-height: var(--cd-leading-flat); }
.cd-day__free { font-size: var(--cd-text-2xs); color: var(--cd-ink-3); line-height: var(--cd-leading-flat); }

.cd-day:hover:not([aria-disabled="true"], [aria-current="date"]) {
  border-color: var(--cd-accent-line);
  box-shadow: var(--cd-ring), var(--cd-shadow-1);
  translate: 0 calc(-2px * var(--cd-motion));
}

/* Selected: filled, plus the waterline underneath it. The rule is the brand's
   own device doing a job — it marks the current position without needing the
   accent to carry any contrast load. */
.cd-day[aria-current="date"] {
  background: var(--cd-accent-mark);
  border-color: var(--cd-accent-mark);
  color: var(--cd-on-mark);
}
.cd-day[aria-current="date"] :is(.cd-day__dow, .cd-day__free) { color: inherit; opacity: 0.85; }
.cd-day[aria-current="date"]::after {
  content: "";
  position: absolute;
  inset-inline: 12%;
  inset-block-end: -6px;
  block-size: 2px;
  border-radius: 1px;
  background: var(--cd-ink);
}

/* Today, when it is not the selected day: a dot on the waterline. */
.cd-day[data-today="true"]:not([aria-current="date"])::after {
  content: "";
  position: absolute;
  inset-block-end: 6px; inset-inline-start: 50%;
  translate: -50% 0;
  inline-size: 4px; block-size: 4px;
  border-radius: var(--cd-radius-pill);
  background: var(--cd-accent-line);
}

/* Disabled, but still READ: the client has to be able to tell which date is
   unavailable, so this uses the disabled-text token (≥4.5:1) rather than
   --cd-ink-4, which is a non-text tone. */
.cd-day[aria-disabled="true"] {
  background: transparent;
  border-color: transparent;
  color: var(--cd-disabled-ink);
  cursor: not-allowed;
}
.cd-day[aria-disabled="true"] .cd-day__num { text-decoration: line-through; }


/* ═══ 10b · MONTH CALENDAR ═════════════════════════════════════════════════
   The "when" step, and the component §10 should always have been.

   §10 shows the days that HAVE something, in order, as a strip. That is a
   perfectly good control for a reschedule — the client already knows roughly
   when — and a bad one for a first booking, because it answers a question
   nobody asked ("which of these nine dates?") while hiding the one they did
   ("can I come in the week of the 20th?"). Fourteen dates with the empty ones
   silently removed reads as an arbitrary list: Tue 12, Wed 13, Fri 15, Mon 18.
   A person cannot tell whether Thursday was full or whether the firm is shut on
   Thursdays, and there is no shape to scan.

   A month grid answers both at once. Every day of the month is present; the
   ones with time in them carry a dot; the rest are visibly inert. The client
   reads the firm's WEEK — closed weekends, a Friday that is always booked —
   which is the thing they were actually trying to work out.

     <div class="cd-calendar" role="group" aria-label="Choose a date">
       <div class="cd-calendar__head">
         <button class="cd-iconbtn" aria-label="Previous month">…</button>
         <p class="cd-calendar__month">August 2026</p>
         <button class="cd-iconbtn" aria-label="Next month">…</button>
       </div>
       <div class="cd-calendar__dow" aria-hidden="true">
         <span>M</span><span>T</span>… </div>
       <div class="cd-calendar__grid">
         <span class="cd-calendar__pad"></span>
         <button class="cd-date" data-date="2026-08-01" aria-disabled="true">…
         <button class="cd-date" data-date="2026-08-03">
           <span class="cd-date__num">3</span>
           <span class="cd-date__dot" aria-hidden="true"></span></button>

   MONDAY FIRST. An Australian week starts on Monday, and a firm's working week
   starts more decisively than that — putting Sunday in column one buys a client
   nothing and costs them a beat re-counting the columns.

   TARGET SIZE, stated rather than glossed. Seven columns cannot give 44px-wide
   cells at 320px: 288px of content leaves 38px a cell, and there is no layout
   trick that changes the arithmetic. Cells are therefore ≥44px TALL always and
   ≥44px wide from 348px of container up. At the very bottom that is a 38×44
   target — four times the 24×24 that WCAG 2.2 AA actually requires (2.5.8), and
   short of the AAA 44×44 (2.5.5) this system otherwise holds itself to. §10
   remains the control that keeps 44×44 at every width, which is why it stays in
   the sheet rather than being replaced. */

.cd-calendar { display: flex; flex-direction: column; gap: var(--cd-space-3); }

.cd-calendar__head {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--cd-space-2);
}
.cd-calendar__month {
  text-align: center;
  font-size: var(--cd-text-md);
  font-weight: var(--cd-weight-semi);
  letter-spacing: var(--cd-track-tight);
}

.cd-calendar__dow,
.cd-calendar__grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: var(--cd-space-1);
}
.cd-calendar__dow > span {
  text-align: center;
  font-size: var(--cd-text-2xs);
  letter-spacing: var(--cd-track-eyebrow);
  text-transform: uppercase;
  color: var(--cd-ink-3);
}

/* Paging months must not resize the page under the client's finger, so the
   grid keeps its height while the next month loads and only its contents fade.
   pointer-events is the important half: without it a fast double-tap on the
   chevron queues two fetches and the second can land first. */
.cd-calendar[data-loading="true"] .cd-calendar__grid {
  opacity: 0.45;
  pointer-events: none;
  transition: opacity var(--cd-dur-2) var(--cd-ease);
}

.cd-date {
  --cd-ring: 0 0 #0000;
  position: relative;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 3px;
  aspect-ratio: 1;
  min-block-size: var(--cd-tap);
  max-block-size: 3.5rem;
  padding: 0;
  font-size: var(--cd-text-sm);
  font-weight: var(--cd-weight-medium);
  color: var(--cd-ink);
  background: var(--cd-surface);
  border: var(--cd-border) solid var(--cd-line);
  border-radius: var(--cd-radius-sm);
  box-shadow: var(--cd-ring);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition:
    background-color var(--cd-dur-1) var(--cd-ease),
    border-color     var(--cd-dur-1) var(--cd-ease),
    color            var(--cd-dur-1) var(--cd-ease),
    box-shadow       var(--cd-dur-1) var(--cd-ease),
    translate        var(--cd-dur-1) var(--cd-ease);
}

.cd-date__num { line-height: var(--cd-leading-flat); }

/* The dot is what makes the grid scannable — the eye finds the pattern of dots
   long before it reads a single numeral. --cd-accent-line is the token with the
   3:1 non-text guarantee, which is exactly what a meaningful graphic needs
   (WCAG 1.4.11). It is never the only signal: a day with no time in it is also
   inert, unbordered and muted. */
.cd-date__dot {
  inline-size: 5px; block-size: 5px;
  border-radius: var(--cd-radius-pill);
  background: var(--cd-accent-line);
  transition: background-color var(--cd-dur-1) var(--cd-ease);
}

.cd-date:hover:not([aria-disabled="true"], :disabled,
                   [aria-pressed="true"], [aria-checked="true"]) {
  border-color: var(--cd-accent-line);
  box-shadow: var(--cd-ring), var(--cd-shadow-1);
  translate: 0 calc(-2px * var(--cd-motion));
}

.cd-date[aria-pressed="true"],
.cd-date[aria-checked="true"] {
  background: var(--cd-accent-mark);
  border-color: var(--cd-accent-mark);
  color: var(--cd-on-mark);
  font-weight: var(--cd-weight-semi);
}
.cd-date[aria-pressed="true"] .cd-date__dot,
.cd-date[aria-checked="true"] .cd-date__dot { background: var(--cd-on-mark); }

/* Today, unchosen: a ring drawn inside the border rather than a second dot.
   The dot slot already means "has availability" in this grid, and one mark
   cannot carry two meanings on the same control. */
.cd-date[data-today="true"]:not([aria-pressed="true"], [aria-checked="true"]) {
  border-color: var(--cd-control-line);
  box-shadow: var(--cd-ring), inset 0 0 0 1px var(--cd-control-line);
  font-weight: var(--cd-weight-semi);
}

/* Nothing free. It stays in the grid and stays READABLE — the shape of the
   month is the information, and a client has to be able to see that the 14th
   exists and is full. No strikethrough: §10 can afford one across nine chips,
   a 35-cell grid cannot, and a month of struck numerals reads as an outage. */
.cd-date[aria-disabled="true"], .cd-date:disabled {
  background: transparent;
  border-color: transparent;
  color: var(--cd-disabled-ink);
  font-weight: var(--cd-weight-regular);
  box-shadow: var(--cd-ring);
  cursor: not-allowed;
}

/* Leading and trailing blanks. Not buttons, not numbered — a greyed-out "31" in
   the first cell of September is read as a date before it is read as padding. */
.cd-calendar__pad { display: block; }

.cd-calendar__legend {
  display: flex;
  align-items: center;
  gap: var(--cd-space-2);
  font-size: var(--cd-text-xs);
  color: var(--cd-ink-3);
}
.cd-calendar__legend .cd-date__dot { flex: none; }


/* — the date-and-time layout ————————————————————————————————————————————
   Wide enough, the two halves sit side by side and the client watches the times
   change as they move across the month — which is the single biggest reason
   this step now feels like one question instead of two screens. The hairline is
   an ordinary rule, NOT the waterline (§4): that device is horizontal, 2px and
   spent on one thing per page. */
.cd-picker { display: grid; gap: var(--cd-space-6); }

@container cd (min-width: 620px) {
  .cd-picker {
    grid-template-columns: minmax(16rem, 19rem) minmax(0, 1fr);
    align-items: start;
  }
  .cd-picker__times {
    border-inline-start: var(--cd-hairline) solid var(--cd-line);
    padding-inline-start: var(--cd-space-6);
    /* A long Saturday of 15-minute slots must not stretch the calendar column
       into whitespace, and must not scroll the page away from the month. */
    max-block-size: 34rem;
    overflow-y: auto;
    overscroll-behavior-y: contain;
  }
}


/* ═══ 11 · STEPPER ═════════════════════════════════════════════════════════
   Narrow: a sentence and a rule. Wide: nodes on the waterline.
   Both are driven by the same markup; nothing is duplicated in the DOM. */

.cd-stepper { display: flex; flex-direction: column; gap: var(--cd-space-2); }

.cd-stepper__summary {
  display: flex; align-items: baseline; gap: var(--cd-space-2);
  font-size: var(--cd-text-sm);
}
.cd-stepper__summary b { font-weight: var(--cd-weight-semi); }

.cd-stepper__track {
  position: relative;
  block-size: 2px;
  background: var(--cd-line);
  border-radius: 1px;
  overflow: hidden;
}
.cd-stepper__fill {
  position: absolute; inset-block: 0; inset-inline-start: 0;
  background: var(--cd-accent-line);
  border-radius: inherit;
  transition: inline-size var(--cd-dur-3) var(--cd-ease);
}

.cd-stepper__list { display: none; }

@container cd (min-width: 560px) {
  .cd-stepper__summary, .cd-stepper__track { display: none; }
  .cd-stepper__list {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    align-items: start;
    gap: var(--cd-space-2);
    position: relative;
  }
  .cd-stepper__step {
    display: grid; justify-items: center; gap: var(--cd-space-2);
    position: relative;
    font-size: var(--cd-text-sm);
    color: var(--cd-ink-3);
    text-align: center;
  }
  /* The rail, drawn in SEGMENTS rather than as one line behind everything.
     A single hairline can only be one colour, so it can only say "there are
     four steps"; a segment per step can also say how far along you are, which
     is the question the rail is actually being asked.

     Each segment runs from the previous node's centre to this one's, so the
     first step has none. A step that is current or done has, by definition,
     every step before it done — so the segment arriving at it is the accent,
     and everything beyond the current step stays grey. */
  .cd-stepper__step + .cd-stepper__step::before {
    content: "";
    position: absolute;
    inset-inline: -50% 50%;
    inset-block-start: 15px;
    block-size: var(--cd-hairline);
    background: var(--cd-line);
  }
  .cd-stepper__step[data-state="done"]::before,
  .cd-stepper__step[data-state="current"]::before { background: var(--cd-accent-line); }

  .cd-stepper__dot {
    /* Above its own rail segment, and opaque, so the line runs BEHIND the node
       rather than out from under it. */
    position: relative;
    z-index: 1;
    inline-size: 30px; block-size: 30px;
    display: grid; place-items: center;
    font-size: var(--cd-text-xs);
    font-weight: var(--cd-weight-semi);
    border-radius: var(--cd-radius-pill);
    background: var(--cd-surface);
    border: var(--cd-border-strong) solid var(--cd-line-strong);
    color: var(--cd-ink-3);
  }
  /* Done is INK, not the accent, and the difference is the whole grammar of the
     rail: the accent means "this is the one you are on or have reached", and ink
     means "this is finished". Filling a completed step with the same colour that
     marks the current one leaves a client counting circles to find their place. */
  .cd-stepper__step[data-state="done"] .cd-stepper__dot {
    background: var(--cd-ink);
    border-color: var(--cd-ink);
    color: var(--cd-surface);
  }
  .cd-stepper__step[data-state="current"] {
    color: var(--cd-ink);
    font-weight: var(--cd-weight-semi);
  }
  .cd-stepper__step[data-state="current"] .cd-stepper__dot {
    background: var(--cd-surface);
    border-color: var(--cd-accent-line);
    color: var(--cd-accent-ink);
  }
}


/* ═══ 11b · ACTION BAR ═════════════════════════════════════════════════════

   The running price and the one action, pinned to the foot of the step.

   THE PROBLEM IT SOLVES. The number is the reason a client keeps going, and
   it is the thing they are least willing to hunt for. Put it in a sidebar and
   two things happen: on a phone there is no sidebar, so the figure lands
   somewhere below the fold and the client ticks four add-ons watching nothing
   change; and even on a desktop the eye is at the bottom of the form, where
   the button is, and the price is up and to the right. A bar carries both to
   the same place, so ticking an add-on visibly moves a number an inch from
   the cursor.

   Sticky rather than fixed, deliberately. position:fixed would glue it to
   the viewport and cover the host page's own content when the widget is
   embedded in a firm's site — we are a component on somebody else's page and
   we do not get to own the bottom of their screen. Sticky confines it to its
   own step: it rides the bottom edge while the step is taller than the
   viewport and simply sits at the end of the content when it is not.

   The background must be OPAQUE, because content scrolls underneath it. In
   embed mode the page is transparent by design (the host's background shows
   through), so book.html turns both the stickiness and the fill off there —
   the frame is sized to its content and never scrolls internally, so there is
   nothing for the bar to stick to and nothing to hide. */

.cd-actionbar {
  position: sticky;
  inset-block-end: 0;
  z-index: var(--cd-z-sticky);
  display: flex;
  align-items: center;
  /* The ends of the row are the ends of the row. With a price between them it
     is the price that stretches; with only a back link and an action, the two
     go to opposite edges rather than huddling on the left. */
  justify-content: space-between;
  gap: var(--cd-space-3) var(--cd-space-4);
  margin-block-start: var(--cd-space-6);
  padding-block: var(--cd-space-3);
  background: var(--cd-canvas);
  border-block-start: var(--cd-border) solid var(--cd-line);
  /* Upward lift, so it reads as a surface the page passes BEHIND rather than
     as a rule someone drew. The house shadows all fall downward; this is the
     one place that needs the opposite, so it is composed here from the same
     ink and the same strength multiplier. */
  box-shadow: 0 -8px 20px -8px rgb(var(--cd-shadow-rgb) /
              calc(0.16 * var(--cd-shadow-strength)));
}

/* Nothing to quote, and nothing to say about it: a bar reading "$0" on the
   first screen is not honesty, it is a price tag on an empty basket. The rule
   is needed because display:flex below out-cascades the UA's [hidden] — same
   specificity, later sheet — and a hidden flex item that still lays out is a
   gap in the middle of the row with nothing in it. */
/* In the flow rather than stuck to the viewport: the first step's bar, which has
   nothing to quote and used to sit on top of the add-on cards below the fold. */
.cd-actionbar--flow { position: static; box-shadow: none; }

.cd-actionbar__price[hidden] { display: none; }

/* The figure fills the space, which is what pushes the button to the far
   edge — one dominant action, as far from the back link as the row allows.
   With no price and no way back — the first screen, where there is nothing yet
   to quote and nowhere yet to go back to — the action centres instead. A lone
   button stretched across the full width reads as a form submit; the same
   button sized to its own words, on the axis of a centred screen, reads as the
   way on. */
.cd-actionbar__price {
  flex: 1 1 auto;
  min-inline-size: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin: 0;
}
.cd-actionbar > .cd-btn:only-child { margin-inline: auto; }
/* Deliberately NOT --cd-font-brand, tempting as tracked caps make it: layer 1
   is our face and this is the client's screen. It matches .cd-eyebrow exactly,
   which is the widget's own small-caps voice. */
.cd-actionbar__label {
  font-size: var(--cd-text-2xs);
  line-height: var(--cd-leading-flat);
  font-weight: var(--cd-weight-semi);
  letter-spacing: var(--cd-track-eyebrow);
  text-transform: uppercase;
  color: var(--cd-ink-3);
}
/* Tabular figures and a reserved width: ticking an add-on must change the
   number without shifting the row it sits in. $1,000 is wider than $290, and
   a total that nudges the button sideways as you tick reads as instability. */
.cd-actionbar__figure {
  min-inline-size: 4.5ch;
  font-size: var(--cd-text-lg);
  font-weight: var(--cd-weight-semi);
  line-height: var(--cd-leading-snug);
  letter-spacing: var(--cd-track-tight);
  color: var(--cd-ink);
  font-variant-numeric: tabular-nums;
}
.cd-actionbar__gst {
  font-size: var(--cd-text-xs);
  font-weight: var(--cd-weight-regular);
  color: var(--cd-ink-3);
}

/* The reassurance goes UNDER the bar, never above it: it answers the question
   the button has just raised — "what am I committing to?" — and an answer that
   arrives before the question reads as a disclaimer. Centred, because it is
   addressed to the step rather than to the control it happens to follow. */
.cd-actionbar__note { text-align: center; }

/* Narrow: the action takes its own full-width row UNDER the price, and the
   quiet back link keeps the price company above it. The button is last in
   source order and stays last visually — a primary action that jumps above
   the figure it commits you to is asking you to agree before you have read. */
@container cd (max-width: 26rem) {
  .cd-actionbar { flex-wrap: wrap; }
  .cd-actionbar > .cd-btn--pill { flex: 1 1 100%; }
}

/* ═══ 12 · CARDS, PANELS, SUMMARY ══════════════════════════════════════════ */

.cd-card {
  background: var(--cd-surface);
  border: var(--cd-border) solid var(--cd-line);
  border-radius: var(--cd-radius-lg);
  box-shadow: var(--cd-shadow-1);
  overflow: clip;
}
.cd-card__head {
  padding: var(--cd-space-5) var(--cd-space-5) var(--cd-space-4);
  border-block-end: var(--cd-hairline) solid var(--cd-line);
}
.cd-card__body { padding: var(--cd-space-5); }
.cd-card__foot {
  padding: var(--cd-space-4) var(--cd-space-5);
  background: var(--cd-surface-2);
  border-block-start: var(--cd-hairline) solid var(--cd-line);
}
.cd-card--flat   { box-shadow: none; }
.cd-card--sunken { background: var(--cd-surface-2); box-shadow: none; }
.cd-card--accent { border-color: var(--cd-accent-line); }

@container cd (min-width: 560px) {
  .cd-card__head { padding: var(--cd-space-6) var(--cd-space-6) var(--cd-space-4); }
  .cd-card__body { padding: var(--cd-space-6); }
  .cd-card__foot { padding: var(--cd-space-4) var(--cd-space-6); }
}

.cd-panel {
  padding: var(--cd-space-4);
  background: var(--cd-surface-2);
  border-radius: var(--cd-radius-md);
}
.cd-panel--accent { background: var(--cd-accent-subtle); }

/* The receipt. Label left, value right, hairline between. Values are tabular
   so a column of times or dollars lines up. */
.cd-summary { display: grid; gap: 0; }
/* minmax(0, 1fr) for the value column, not 1fr. `1fr` is minmax(auto, 1fr), so
   the track cannot go below its min-content width — and the values in this
   component are exactly the strings that have no break opportunity in them:
   an email address, a URL, a reference code. A practice whose support address is
   you@hendersonco.com.au pushed this row to 309px inside a 280px widget and
   sheared the right-hand column off a 320px phone.
   The row must never be wider than the card it sits in, so the track is allowed
   to shrink and the value wraps at any character instead. A wrapped email is
   ugly; a truncated one is unusable, and this is the component that shows a
   client where their appointment is and who to ring. */
.cd-summary__row {
  display: grid;
  grid-template-columns: minmax(6.5rem, 34%) minmax(0, 1fr);
  gap: var(--cd-space-3);
  padding-block: var(--cd-space-3);
  align-items: baseline;
}
.cd-summary__row + .cd-summary__row { border-block-start: var(--cd-hairline) solid var(--cd-line); }
.cd-summary__label { font-size: var(--cd-text-sm); color: var(--cd-ink-3); }
.cd-summary__value {
  font-size: var(--cd-text-base);
  color: var(--cd-ink);
  font-weight: var(--cd-weight-medium);
  /* anywhere, not break-word: break-word will not split a long unbroken token
     that is already on its own line, which is precisely the email case. */
  overflow-wrap: anywhere;
}
.cd-summary__value--total { font-size: var(--cd-text-md); font-weight: var(--cd-weight-semi); }

/* ── The booking receipt ──────────────────────────────────────────────────
   The same rows, stacked and given a glyph, for the ONE place this component
   is read rather than consulted: the card beside the booking flow, which a
   client glances at between questions to check the thing being assembled is
   still the thing they wanted.

   Label above value, not beside it, because a two-column row inside a 19rem
   card gives the value about seven characters before it wraps — and the value
   is the half that matters. The glyph on the right is what makes the card
   scannable without being read: you find the line about WHO by looking for the
   face, not by reading four labels.

   A modifier, not the default, because the flat two-column version is right
   for admin, where these rows sit in a wide panel and are compared down the
   label column. */
.cd-summary--rich .cd-summary__row {
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-rows: auto auto;
  align-items: center;
  gap: 2px var(--cd-space-3);
  padding-block: var(--cd-space-4);
}
.cd-summary--rich .cd-summary__label { grid-area: 1 / 1; }
.cd-summary--rich .cd-summary__value {
  grid-area: 2 / 1;
  font-size: var(--cd-text-md);
  font-weight: var(--cd-weight-semi);
  line-height: var(--cd-leading-snug);
}
.cd-summary--rich .cd-summary__value .cd-meta { font-weight: var(--cd-weight-regular); }
.cd-summary__icon {
  grid-area: 1 / 2 / 3 / 3;
  align-self: center;
  display: grid; place-items: center;
  inline-size: 38px; block-size: 38px;
  color: var(--cd-ink-2);
  background: var(--cd-surface-2);
  border-radius: var(--cd-radius-pill);
}
.cd-summary__icon svg { inline-size: 19px; block-size: 19px; }

/* The promise, at the foot of the card rather than under the button. It answers
   "what am I agreeing to?", which is a question about the whole booking, not
   about the control that happens to be nearest. */
.cd-reassure {
  display: flex;
  align-items: flex-start;
  gap: var(--cd-space-3);
  margin: 0;
  font-size: var(--cd-text-sm);
  line-height: var(--cd-leading-snug);
  color: var(--cd-ink-3);
}
.cd-reassure svg { flex: none; inline-size: 18px; block-size: 18px; margin-block-start: 1px; color: var(--cd-ink-2); }
.cd-reassure b { display: block; font-weight: var(--cd-weight-semi); color: var(--cd-ink); }


/* ═══ 13 · BADGES + STATUS PILLS ═══════════════════════════════════════════
   Never colour alone — every pill carries a word. */

.cd-pill {
  display: inline-flex; align-items: center; gap: var(--cd-space-1);
  padding: 3px var(--cd-space-2);
  font-size: var(--cd-text-xs);
  font-weight: var(--cd-weight-semi);
  letter-spacing: 0.02em;
  line-height: 1.4;
  border-radius: var(--cd-radius-pill);
  background: var(--cd-surface-2);
  color: var(--cd-ink-2);
  border: var(--cd-hairline) solid transparent;
  white-space: nowrap;
}
.cd-pill::before {
  content: "";
  inline-size: 6px; block-size: 6px;
  border-radius: var(--cd-radius-pill);
  background: currentColor;
  flex: none;
}
.cd-pill--confirmed { background: var(--cd-success-subtle); color: var(--cd-success); }
.cd-pill--pending   { background: var(--cd-warning-subtle); color: var(--cd-warning); }
.cd-pill--cancelled { background: var(--cd-danger-subtle);  color: var(--cd-danger); }
.cd-pill--held      { background: var(--cd-info-subtle);    color: var(--cd-info); }
.cd-pill--accent    { background: var(--cd-accent-subtle);  color: var(--cd-accent-ink); }
.cd-pill--plain::before { display: none; }

.cd-badge {
  display: inline-block;
  padding: 2px var(--cd-space-2);
  font-size: var(--cd-text-2xs);
  font-weight: var(--cd-weight-bold);
  letter-spacing: var(--cd-track-eyebrow);
  text-transform: uppercase;
  border-radius: var(--cd-radius-xs);
  background: var(--cd-accent-mark);
  color: var(--cd-on-mark);
}


/* ═══ 14 · SKELETONS ═══════════════════════════════════════════════════════
   Shaped like the thing that is coming, so the layout does not jump. Wrap them
   in aria-hidden and put a visually hidden "Loading times" live region beside. */

.cd-skel {
  background: var(--cd-surface-2);
  border-radius: var(--cd-radius-sm);
  background-image: linear-gradient(
      90deg,
      transparent 0 30%,
      color-mix(in oklab, var(--cd-ink) 6%, transparent) 48% 52%,
      transparent 70% 100%);
  background-size: 280% 100%;
  animation: cd-sheen 1.5s linear infinite;
}
@keyframes cd-sheen {
  from { background-position: 140% 0; }
  to   { background-position: -140% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .cd-skel { animation: none; background-image: none; }
}
.cd-skel--line  { block-size: 0.85em; margin-block: 0.28em; }
.cd-skel--title { block-size: 1.4em; inline-size: 62%; }
.cd-skel--chip  { block-size: var(--cd-tap); border-radius: var(--cd-radius-sm); }
.cd-skel--w40   { inline-size: 40%; }
.cd-skel--w70   { inline-size: 70%; }


/* ═══ 15 · TOASTS ══════════════════════════════════════════════════════════
   role="status" for confirmations, role="alert" for failures. Inside an
   iframe widget the region is fixed to the widget, not the host page. */

.cd-toasts {
  position: fixed;
  inset-block-end: var(--cd-space-4);
  inset-inline: var(--cd-space-4);
  z-index: var(--cd-z-toast);
  display: flex; flex-direction: column-reverse;
  gap: var(--cd-space-2);
  pointer-events: none;
}
.cd-toasts--inline { position: static; margin-block-start: var(--cd-space-4); }

@container cd (min-width: 480px) {
  .cd-toasts { inset-inline: auto var(--cd-space-5); max-inline-size: 26rem; }
}

.cd-toast {
  pointer-events: auto;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: start;
  gap: var(--cd-space-3);
  padding: var(--cd-space-3) var(--cd-space-3) var(--cd-space-3) var(--cd-space-4);
  background: var(--cd-surface);
  color: var(--cd-ink);
  border: var(--cd-border) solid var(--cd-line);
  border-inline-start: 3px solid var(--cd-ink-4);
  border-radius: var(--cd-radius-md);
  box-shadow: var(--cd-shadow-3);
  animation: cd-toast-in var(--cd-dur-3) var(--cd-ease-spring) 1;
}
@keyframes cd-toast-in {
  from { opacity: 0; translate: 0 calc(10px * var(--cd-motion)); }
  to   { opacity: 1; translate: 0 0; }
}
.cd-toast__icon { inline-size: 20px; block-size: 20px; margin-block-start: 1px; }
.cd-toast__body { font-size: var(--cd-text-sm); line-height: var(--cd-leading-snug); }
.cd-toast__title { display: block; font-weight: var(--cd-weight-semi); }
.cd-toast__note  { color: var(--cd-ink-3); }
.cd-toast--success { border-inline-start-color: var(--cd-success); }
.cd-toast--success .cd-toast__icon { color: var(--cd-success); }
.cd-toast--error   { border-inline-start-color: var(--cd-danger); }
.cd-toast--error   .cd-toast__icon { color: var(--cd-danger); }
.cd-toast--info    { border-inline-start-color: var(--cd-info); }
.cd-toast--info    .cd-toast__icon { color: var(--cd-info); }


/* ═══ 16 · EMPTY STATE ═════════════════════════════════════════════════════
   Always offers the next move. "No results found" is not an empty state, it
   is a dead end. */

.cd-empty {
  display: grid; justify-items: center; gap: var(--cd-space-3);
  padding: var(--cd-space-10) var(--cd-space-5);
  text-align: center;
  background: var(--cd-surface-2);
  border-radius: var(--cd-radius-md);
}
.cd-empty__rule {
  inline-size: 48px; block-size: 2px;
  border-radius: 1px;
  background: var(--cd-line-strong);
}
.cd-empty__title { font-family: var(--cd-font-display); font-size: var(--cd-text-lg); }
.cd-empty__note  { font-size: var(--cd-text-sm); color: var(--cd-ink-3); max-inline-size: 34ch; }


/* ═══ 16b · THE GHOST — a cross-fade for replaced content ══════════════════
   Setting innerHTML is a cut: the old question is gone in one frame and the
   new one fades in, which reads as the page being torn down and rebuilt. A
   cross-fade needs the old pixels to STAY while the new ones arrive, so the
   pages that replace a whole region (book.html's steps, setup.html's screens)
   clone the region before replacing it, lay the clone over the same spot, and
   fade it out. This is the clone.

   FIXED, not absolute. A fixed box adds nothing to the document's scrollable
   height — no scrollbar blip when a long step gives way to a short one — and
   nothing to the html box the framed booking page reports to its host.

   Animations are switched off inside it: cloning a node restarts every CSS
   animation on it, and a ghost whose children re-run their own entrance would
   flash from nothing to full while it is meant to be leaving. It declares the
   booking page's query container, because a clone of #main that is not a
   container has children whose container queries resolve against the page.

   Popover height in the stack: above the sticky action bar it briefly covers,
   below a toast. --cd-dur-2 is 1ms under reduced motion (tokens.css), so the
   ghost is gone before it is seen. */
.cd-ghost {
  position: fixed;
  z-index: var(--cd-z-popover);
  margin: 0;
  box-sizing: border-box;
  pointer-events: none;
  overflow: hidden;
  container-type: inline-size;
  container-name: cd;
  opacity: 1;
  transition: opacity var(--cd-dur-2) var(--cd-ease-in-out);
}
.cd-ghost--out { opacity: 0; }
.cd-ghost * { animation: none !important; }


/* ═══ 17 · FORCED COLOURS + PRINT ══════════════════════════════════════════
   Windows High Contrast strips our colours. The audience skews older, so this
   is not a nicety. Every state that we signalled with a fill has to re-signal
   with a border or an outline. */

@media (forced-colors: active) {
  .cd-btn, .cd-slot, .cd-day, .cd-card, .cd-input, .cd-textarea, .cd-select, .cd-toast,
  .cd-option, .cd-segment, .cd-tile, .cd-person, .cd-date, .cd-choice--card,
  .cd-qty__btn, .cd-actionbar {
    border: 1px solid;
  }
  .cd-btn--primary, .cd-badge { forced-color-adjust: none; background: Highlight; color: HighlightText; border-color: HighlightText; }
  .cd-slot[aria-pressed="true"],
  .cd-slot[aria-checked="true"],
  .cd-option[aria-checked="true"],
  .cd-segment[aria-checked="true"],
  .cd-tile[aria-checked="true"],
  .cd-person[aria-checked="true"],
  .cd-date[aria-pressed="true"],
  .cd-date[aria-checked="true"],
  .cd-choice--card:has(> .cd-check:checked),
  .cd-day[aria-current="date"] { outline: 3px solid Highlight; outline-offset: -3px; }
  /* The wash is gone here, so the drawn marks carry the whole signal.
     forced-color-adjust:none is required — without it the UA overrides these
     backgrounds back to Canvas and the "chosen" dot disappears entirely. */
  .cd-option[aria-checked="true"] .cd-option__mark,
  .cd-person[aria-checked="true"] .cd-person__mark,
  .cd-tile[aria-checked="true"] .cd-tile__icon,
  .cd-tile[aria-checked="true"] .cd-tile__mark,
  .cd-segment[aria-checked="true"]::before {
    forced-color-adjust: none;
    background: Highlight;
    border-color: Highlight;
  }
  .cd-option[aria-checked="true"] .cd-option__mark::after,
  .cd-person[aria-checked="true"] .cd-person__mark::after { background: HighlightText; }
  .cd-tile[aria-checked="true"] .cd-tile__mark::after { border-color: HighlightText; }
  .cd-tile[aria-checked="true"] .cd-tile__icon { color: HighlightText; }
  .cd-segment--toggle[aria-checked="true"]::after { border-color: HighlightText; }
  /* "This day has time in it" is the calendar's only positive signal, and a
     4px dot is exactly the kind of thing forced colours flattens into the
     background. It is pinned to CanvasText so the pattern of free days
     survives — without it the grid becomes 35 identical squares. */
  .cd-date__dot { forced-color-adjust: none; background: CanvasText; }
  .cd-date[aria-pressed="true"] .cd-date__dot,
  .cd-date[aria-checked="true"] .cd-date__dot { background: HighlightText; }
  .cd-date[data-today="true"]:not([aria-pressed="true"], [aria-checked="true"]) {
    outline: 1px dashed CanvasText; outline-offset: -4px;
  }
  .cd-slot[data-state="held"],
  .cd-slot[aria-disabled="true"],
  .cd-option[aria-disabled="true"],
  .cd-tile[aria-disabled="true"],
  .cd-date[aria-disabled="true"],
  .cd-segment:disabled,
  .cd-btn:disabled { color: GrayText; border-color: GrayText; }
  .cd :is(a, button, input, select, textarea, [tabindex]):focus-visible {
    outline: 3px solid CanvasText;
    outline-offset: 2px;
    box-shadow: none;
  }
  .cd-skel { animation: none; }
}

@media print {
  .cd { background: #fff; color: #000; }
  .cd-toasts, .cd-btn, .cd-iconbtn, .cd-slots, .cd-datenav, .cd-calendar { display: none !important; }
  .cd-card { box-shadow: none; border: 1px solid #999; break-inside: avoid; }
  .cd-summary__row { break-inside: avoid; }
}
