/*
 * The patient surface. DESIGN_SPEC §5 and §6.
 *
 * Tokens come from globals.css and nothing here defines a colour: ADR-004 §8 rule 6 makes
 * "no hex literal outside the token sheet" a grep over src/render/**, and it reads comments
 * too, so not even an aside naming what a token resolves to.
 *
 * LOGICAL PROPERTIES ONLY. margin-inline-start, inset-inline-start, text-align: end. Arabic
 * is the layout reference locale (CLAUDE.md §6) and §13.1 requires ONE layout to serve seven
 * scripts: the grid and the spacing scale do not change per locale, only type metrics do.
 * CLAUDE.md §11 forbids the physical properties outright, and this file must not name them
 * even in prose, because the check that enforces it is a grep over the whole file.
 *
 * FIXED, NOT RESPONSIVE. §5.1: one enclosure, 1280x800, landscape, wall-mounted. The kiosk
 * does not scroll and does not respond. There is no breakpoint in this file and there must
 * not be one — §16.5 puts reflow rules for other sizes out of scope by name, and inventing
 * them invents requirements.
 *
 * LIGHT ONLY. §14.5: no dark theme, and the reason is not taste. In direct sunlight a dark
 * ground loses contrast to reflected ambient light, so the outdoor-glare requirement argues
 * for the brightest possible ground.
 *
 * NO ACUITY. §4.6 and CLAUDE.md §7: --acuity-* appears nowhere in this file and must not.
 * A number shown to a layperson is either frightening or falsely reassuring, and it invites
 * queue arguments.
 */

/* ------------------------------------------------------------------------------------
 * Local component dimensions.
 *
 * §3 makes any length that is not one of the nine spacing tokens a review failure. These
 * are not spacing: they are the component dimensions §5.2, §5.7, §5.9 and §12.1 declare by
 * value, plus the four §11 motion durations. They are named once here rather than repeated
 * at each use, so a reviewer can check them against the specification in one place.
 *
 * §11's durations are specified as tokens in DESIGN_SPEC and are absent from globals.css,
 * which tokens.test.ts seals to a single :root block. They are therefore declared on the
 * page element, which is the outermost element this sheet owns, and inherit from there.
 * ---------------------------------------------------------------------------------- */

.kiosk-page {
  /* §5.1 */
  --kiosk-width: 1280px;
  --kiosk-height: 800px;
  --kiosk-header: 88px;
  /*
   * §5.1 states 88 + 600 + 112 = 800, and 112 does not hold a 72px control with any
   * clearance. ED_010 / ED_012 record the paired_admin audio control at a bottom edge of
   * 797.5 of 800; MEASURING IT SHOWED CONTINUE AT 797.5 ON ALL EIGHTEEN SCREENS, because
   * the footer's own budget is 1 hairline + 34 support + 72 control row = 107 inside 112,
   * and `align-content: center` splits the 5px remainder two and a half each way. The
   * defect is §5.10's, not §5.9's, and it was never paired_admin-specific.
   *
   * 128 = 1 hairline + 9 + 34 support + 72 controls + 12 clearance. The 12 is declared as
   * `padding-block-end` on the footer and the 9 is what is left over, taken at the top by
   * `align-content: end`, so the number that matters is exact rather than halved. Continue
   * and the audio control now both end at 788.
   *
   * The 16 comes out of the body, which is the only place it can come from — 800 is the
   * panel. Two stacks were using more than 584 and both are re-budgeted below:
   * --card-grid-6 (SCR_COMPLAINT_GROUP) and --card-paired (SCR_ABOUT_YOU). Every other
   * screen's stack is 560 or less and is untouched.
   */
  --kiosk-body: 584px;
  --kiosk-footer: 128px;
  --kiosk-question-column: 448px;
  --kiosk-options-column: 688px;

  /* §5.2 — height varies with count, so fewer options means bigger targets. */
  --card-2: 200px;
  --card-3: 152px;
  --card-4: 120px;
  /* §5.7 — the two-column arrangement, used above four options. */
  --card-grid: 336px;
  /*
   * SIX options, which is SCR_COMPLAINT_GROUP and nothing else. ED_009 confirms §5.2's
   * "no option list exceeds four except SCR_LOCALE" is false and proposes reusing §5.7's
   * 336x120; executing that showed the arithmetic it checked was the wrong one. The stack
   * fits vertically at 120 (3 x 120 + 2 x 16 = 392). The LABEL does not: §5.2's internals
   * leave 336 - (24 + 64 + 24 + 24 + 32 + 24) = 144 for the label, and "Heat, drinking, or
   * the stomach" at 28/40 needs four lines = 160 inside a 120 card. It overflowed and the
   * next row's opaque card painted over it, cutting a word in half on the demo path.
   *
   * 172 is the height that fits four lines with the stack still inside the body:
   * 3 x 172 + 2 x 16 = 548, against the 552 the stack has after its own --space-6 padding.
   * SCR_LOCALE stays at 120 and its 4 x 120 + 3 x 16 = 528, which is why this is keyed on
   * the count rather than on the layout. The touch target grows and never shrinks (§12.1).
   *
   * Was 176 against a 600 body. The footer's clearance fix above took 16 from the body, and
   * 172 is what re-fits: still 12 clear of the 160 a four-line label needs, and still 108
   * clear of the 64 touch floor.
   */
  --card-grid-6: 172px;
  /*
   * §5.9 declares 568x96 for a paired_admin option and its own arithmetic stops at the
   * option stack: 5 x 96 + 4 x 12 = 528, inside the 600 body. It does not count the
   * sub-question above the stack or the "not sure" control below it. With both, the column
   * needs 40 + 12 + 528 + 12 + 64 = 656 against 600 and does not fit (ED_010).
   *
   * 80 was the first answer and it was still 8 too tall, which nothing caught because the
   * overflow was absorbed silently: the sub-column is a flex column, so the "not sure"
   * control SHRANK to 56 — measured, on the demo path — putting a live control 8px under
   * `presentation.min_touch_target_px`. Two causes, both fixed:
   *
   *   1. The 12 between the stack and the control was spent TWICE, once as the column's
   *      flex `gap` and once as a `margin-block-start` on the pill. Only the gap remains.
   *   2. The footer's clearance fix above takes 16 from the body, so the sub-column's box
   *      is 568 rather than 584.
   *
   * At 76: 16 margin + 40 sub-question + 16 padding + (5 x 76 + 4 x 12 = 428) + 12 + 64
   * = 576 against the 584 row, and 560 against the 568 box inside the margin. Eight spare,
   * and 76 still clears the 64px touch floor by 12. The pill is `flex: none` below so the
   * next 8px of drift fails visibly instead of eating a touch target.
   *
   * Reported; §12.1's 96 is the value that has to move, and the body already has.
   */
  --card-paired: 76px;

  /* §12.1 — every one clears presentation.min_touch_target_px, most by a wide margin. */
  --control-continue-inline: 320px;
  --control-continue-block: 72px;
  --control-pill-inline: 332px;
  --control-pill-block: 64px;
  --control-audio-inline: 448px;
  --control-audio-block: 72px;
  --control-back-inline: 160px;
  --control-back-block: 64px;
  --icon-slot: 64px;
  /*
   * The glyph inside the §5.2 icon box, which stays 64. Smaller than its box on purpose: a
   * 24-unit lucide drawing scaled to a full 64 reads heavier than a 28px label beside it,
   * and the box is a slot rather than a frame. Paired with a stroke-width below lucide's
   * own 2, for the same reason — at this size the native ratio is a poster weight.
   */
  --icon-glyph: 48px;
  --indicator: 32px;

  /* §11 — five things animate in the entire product. Adding a sixth needs a reason. */
  --motion-instant: 0ms;
  --motion-fade: 120ms;
  --motion-audio: 600ms;
  --motion-breathe: 2400ms;

  /*
   * Ring and border widths. §3 exception 1: they are their own ramp because 1px increments
   * below --space-1 are required, and on this surface they carry selection rather than
   * decoration.
   */
  --ring-rest: 2px;
  --ring-selected: 6px;
  --ring-focus: 3px;
  --rule-hairline: 1px;
  --rule-spine: 2px;
  --rule-dashed: 1.5px;
}

/* ------------------------------------------------------------------------------------
 * The enclosure
 * ---------------------------------------------------------------------------------- */

/*
 * The one reset in the sheet. §5.1 states the enclosure and every region inside it as an
 * OUTER dimension — "page padding-inline 48", "header height 88" — and the arithmetic
 * 448 + 48 + 688 = 1184 = 1280 - 96 only closes if padding is inside the box it is on.
 */
*,
*::before,
*::after {
  box-sizing: border-box;
}

/*
 * PRESENTED SCALED, NEVER REFLOWED. ED_029.
 *
 * §5.1 is a fixed enclosure and §16.5 rules out responsive layout by name — no breakpoint,
 * no fluid type, no portrait fallback. Both stand, unamended, and this is not an evasion of
 * either. What follows changes how the enclosure is PRESENTED and not how it is laid out:
 * at every viewport the page is still 1280 by 800 of layout, with the same column widths,
 * the same card heights, the same options per row, the same reading order. The layout at
 * 1024 is byte-identical to the layout at 1280. Every arithmetic proof in this file stays
 * literally true because not one of its numbers moves. The enclosure is drawn to fit the
 * space it has and centred in it, the way a projector fits a slide.
 *
 * WHAT IT FIXES. The page was pinned to the start edge with `overflow: hidden` above it, so
 * below about 1200 the final 96 of the enclosure — which is exactly where Continue sits, at
 * 1184 — was clipped away with no scrollbar to recover it. The primary action became
 * invisible and the flow dead-ended. Above 1280 the same pinning left the whole surplus as
 * dead space on one side. Measured in Chromium at 900, 1100, 1280, 1440 and 1920.
 *
 * FOUR THINGS ARE LOAD-BEARING. Each of them looks like something a later session would
 * simplify, and each has already been measured rather than assumed:
 *
 * 1. `tan(atan2(<length>, 1px))` is the portable spelling of "this length as a number".
 *    Baseline: Chrome 111, Firefox 108, Safari 15.4. A naked `calc(100vw / 1280px)` — a
 *    length divided by a length — resolves only in Chromium. And `scale()` of a `min()` of
 *    two lengths does nothing whatsoever, silently, because `min()` of lengths is a length
 *    and `scale()` takes a number. Both confirmed by measurement, not by reading.
 *
 * 2. `zoom`, not `transform`. Text is laid out and hinted at the size it is drawn rather
 *    than composited at 1280 and resampled; hit testing is native, so §12's touch geometry
 *    is the geometry that is actually hit; no phantom 1280-wide layout box is left behind
 *    to produce a scrollbar; and §11's position on transforms on the patient surface never
 *    has to be argued with.
 *
 * 3. The `@property` registration is the mechanism, not decoration. `zoom` changes how
 *    lengths compute ON the element carrying it, so a viewport-derived zoom declared
 *    directly on `.kiosk-page` is circular. Registering the custom property as `<number>`
 *    computes the value at the root, where zoom is 1, and inherits a plain number downward.
 *    The circularity is removed rather than survived by luck.
 *
 * 4. The at-rule lives in this file because `tokens.test.ts` seals `globals.css` to exactly
 *    one `:root` selector, and an at-rule cannot go there.
 *
 * THE TWO BARE NUMBERS. 1280 and 800 duplicate --kiosk-width and --kiosk-height and cannot
 * reference them, because that needs a length divided by a length, which is footnote 1's
 * non-portable form. `kiosk-stylesheet.test.ts` asserts the two numbers here equal the two
 * token values, so the duplication is a checked one rather than a noted one.
 *
 * THE COST, STATED RATHER THAN BURIED. Below 1280 every length on the surface is drawn
 * smaller, so a 64 control renders at 51 device pixels at 1024 and at 55 at 1100. That is
 * under presentation.min_touch_target_px. It is a relaxation of a §12.1 floor for a
 * pointer-input demo build on a laptop, it is not settled, and `ED_029` records it as
 * awaiting the repository owner's ratification. At 1280 and above the factor is capped at 1
 * and every §12.1 value is drawn at its declared size.
 */
@property --kiosk-fit {
  syntax: '<number>';
  inherits: true;
  initial-value: 1;
}

html {
  --kiosk-fit: min(1, tan(atan2(min(calc(100vw / 1280), calc(100vh / 800)), 1px)));
}

html,
body {
  block-size: 100%;
  margin: 0;
  /*
   * §5.1: the kiosk does not scroll. Not "should not" — a wall-mounted tablet with no
   * pointer and a person who may be unwell has no recoverable scroll gesture, and §12.5
   * already refuses swipe navigation.
   */
  overflow: hidden;
}

body {
  background: var(--surface);
  color: var(--ink-900);
  /*
   * The enclosure is centred rather than pinned to the start edge. Above 1280 this is the
   * whole of the change; at and below it, it centres what --kiosk-fit has already fitted.
   * A grid with `place-items: center` and not an inline-margin trick, because the same one
   * declaration centres on both axes and neither axis is a direction.
   */
  display: grid;
  place-items: center;
}

.kiosk-page {
  inline-size: var(--kiosk-width);
  block-size: var(--kiosk-height);
  /* The fit computed at the root, applied here. See the block above `html, body`. */
  zoom: var(--kiosk-fit);
  padding-inline: var(--space-7);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /*
   * NO ENTRANCE ANIMATION, and its ABSENCE is the requirement. Do not restore one.
   *
   * §11.1 specified `animation: kiosk-fade-in var(--motion-fade) ease-out` here, reasoning
   * that 120ms of opacity "gives just enough continuity that the screen does not appear to
   * flicker". MEASURED 2026-08-29 by CDP screencast across a Continue press: in this
   * architecture it was the SOLE CAUSE of the flicker. ADR-004 makes every screen a new
   * DOCUMENT, so an entrance animation runs on every navigation and starts this element at
   * opacity 0 — the first frame the compositor emits after the navigation is a fully styled
   * and completely EMPTY page, and the content fades up over the next ~111ms.
   *
   * §11.1 was written for an in-document screen swap, where there is an outgoing screen to
   * give continuity FROM. Inside a new document there is none. The continuity it asked for
   * is already supplied, and supplied better, by the browser's paint-holding, which keeps
   * the PREVIOUS screen on the glass until the new one can paint whole — measured holding
   * for 83ms on localhost and 690ms on a 1.5 Mbps link, and never giving up early.
   *
   * Removing exactly this one declaration and nothing else collapsed the transition to a
   * single frame: the first changed pixel became the finished screen, at every link speed
   * tested. Long max-age on the assets and inlining the sheets into the head were each
   * measured as controls and each left the blank frame present and unchanged, so this is
   * not a network effect and caching it away is not a fix for it.
   *
   * `--motion-fade` above stays declared and is now unused. DESIGN_SPEC §11 owns that token
   * table; an unused token is not a defect, and deleting it would be a second decision
   * riding on this one.
   */
}

/* ------------------------------------------------------------------------------------
 * Header — §5.10
 * ---------------------------------------------------------------------------------- */

.kiosk-header {
  flex: none;
  block-size: var(--kiosk-header);
  border-block-end: var(--rule-hairline) solid var(--line-soft);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/*
 * The slot is drawn whether or not Back is in it. §5.10: when canGoBack is false the
 * control is REMOVED rather than disabled, and the header reserves its footprint so the
 * counter does not move between screens.
 */
.kiosk-header__back {
  inline-size: var(--control-back-inline);
  block-size: var(--control-back-block);
}

.kiosk-back {
  inline-size: var(--control-back-inline);
  block-size: var(--control-back-block);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  border: 0;
  background: none;
  color: var(--ink-500);
  font-size: var(--type-kiosk-meta-size);
  line-height: var(--type-kiosk-meta-leading);
  font-weight: var(--weight-regular);
  cursor: pointer;
}

/*
 * §13.5: mirroring is an allowlist and a directional chevron is on it. Drawn from two
 * border edges rather than from a glyph, so it costs no icon dependency and inverts with
 * `dir` for free — the box is rotated in the block/inline plane, not flipped by a rule.
 */
.kiosk-back__chevron {
  inline-size: var(--space-3);
  block-size: var(--space-3);
  border-inline-start: var(--rule-spine) solid var(--ink-500);
  border-block-end: var(--rule-spine) solid var(--ink-500);
  transform: rotate(45deg);
}

.kiosk-counter {
  margin: 0;
  color: var(--ink-500);
  font-size: var(--type-kiosk-meta-size);
  line-height: var(--type-kiosk-meta-leading);
  /* §13.2: the numeral is Intl's, formatted upstream. Nothing here substitutes one. */
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------------------------------
 * A refused submission — re-rendered onto the screen it came from
 * ---------------------------------------------------------------------------------- */

.kiosk-notice {
  flex: none;
  margin: 0;
  padding-block: var(--space-3);
  color: var(--ink-900);
  font-size: var(--type-kiosk-support-size);
  line-height: var(--type-kiosk-support-leading);
  /*
   * Deliberately not red and deliberately not an --acuity-* token. §6.1's argument
   * generalises: alarm colour on a screen a person who may be unwell is reading converts a
   * routine refusal into a crisis, and a refusal here is a machine problem, not theirs.
   */
  border-block-end: var(--rule-hairline) solid var(--line-soft);
}

/* ------------------------------------------------------------------------------------
 * The screen grid — §5.1
 *
 * 448 + 48 + 688 = 1184 = 1280 - 96. Every number is a token or a sum of tokens.
 *
 * The form is the grid. The fieldset inside it is `display: contents`, so the legend lands
 * in the question column and the option stack in the options column while the group's role
 * and accessible name survive: a rendered <legend> is placed at the fieldset's block-start
 * by the HTML rendering model and cannot be moved beside its own options.
 * ---------------------------------------------------------------------------------- */

.kiosk-screen {
  flex: 1;
  min-block-size: 0;
  display: grid;
  grid-template-columns: var(--kiosk-question-column) var(--kiosk-options-column);
  column-gap: var(--space-7);
  grid-template-rows: auto minmax(0, 1fr) var(--kiosk-footer);
  margin: 0;
}

.kiosk-group {
  display: contents;
}

.kiosk-question {
  grid-column: 1;
  grid-row: 1;
  margin: 0;
  padding: 0;
  padding-block-start: var(--space-6);
  font-size: var(--type-kiosk-question-size);
  line-height: var(--type-kiosk-question-leading);
  font-weight: var(--weight-semibold);
  color: var(--ink-900);
}

.kiosk-aside {
  grid-column: 1;
  grid-row: 2;
  padding-block-start: var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  align-items: start;
}

.kiosk-instruction {
  margin: 0;
  font-size: var(--type-kiosk-support-size);
  line-height: var(--type-kiosk-support-leading);
  color: var(--ink-500);
}

.kiosk-privacy {
  margin: 0;
  inline-size: var(--kiosk-question-column);
  font-size: var(--type-kiosk-support-size);
  line-height: var(--type-kiosk-support-leading);
  color: var(--ink-500);
}

/* ------------------------------------------------------------------------------------
 * The option stack — §5.2, §5.4, §5.7
 * ---------------------------------------------------------------------------------- */

.kiosk-options {
  grid-column: 2;
  grid-row: 1 / span 2;
  /* §5.1: top-aligned, never vertically centred. The first option sits at the same y on
   * every screen, which is what builds the tap rhythm that gets a person through the bank. */
  align-self: start;
  padding-block-start: var(--space-6);
  display: grid;
  align-content: start;
  position: relative;
  /* §5.7: if a smaller enclosure ever does scroll, the layout must not shift between
   * locales. Reserved on every stack rather than on one screen, so the reservation cannot
   * be the thing that differs between them. */
  scrollbar-gutter: stable both-edges;
}

.kiosk-options[data-layout='stack'] {
  grid-template-columns: var(--kiosk-options-column);
  row-gap: var(--space-4);
}

.kiosk-options[data-layout='stack'][data-count='2'] {
  row-gap: var(--space-5);
}

/*
 * §5.7's arrangement, used above four options.
 *
 * SCR_LOCALE is seven and is the declared exception. SCR_COMPLAINT_GROUP is SIX, which
 * §5.2's "no option list on the patient surface exceeds four, except SCR_LOCALE" says does
 * not exist and complaint_group's own option list contradicts. Rather than invent a sixth
 * card height, both use the arrangement the specification already gives for the case it
 * did anticipate: 2 x 336 cards at 120, gap 16, which is 4 rows x 120 + 3 x 16 = 528 at
 * seven and 3 x 120 + 2 x 16 = 392 at six. Both inside the 600 body.
 */
.kiosk-options[data-layout='grid'] {
  grid-template-columns: var(--card-grid) var(--card-grid);
  column-gap: var(--space-4);
  row-gap: var(--space-4);
}

/*
 * ED_009. SIX options is SCR_COMPLAINT_GROUP, whose longest label needs four lines in a
 * 144px label track and clipped mid-word at the inherited 120. See --card-grid-6.
 *
 * Keyed on the count, not on the layout: SCR_LOCALE is seven, its labels are single-word
 * endonyms, it carries no icon box at all (§15.6), and four rows at 176 would not fit the
 * body. Neither screen is specified by §5.2's table, which stops at four; this is the
 * arrangement ED_009 asks for and its arithmetic is in the token's comment.
 *
 * The catalogue string is NOT the thing that moves. That label is under clinical review and
 * trading a complaint-group label for pixels is the wrong direction.
 */
.kiosk-options[data-layout='grid'][data-count='6'] > .kiosk-option {
  block-size: var(--card-grid-6);
}

/*
 * §5.4 — the spine. A 2px rule down the inline-start edge of the stack, behind the icon
 * boxes, connecting them. That is the ENTIRE differentiator of a single_select_scale, and
 * it is enough: it says "points on one continuum" rather than "alternatives" without
 * colour, without numbers, and without a label that would need translating.
 *
 * Ordering is top-to-bottom, which is direction-neutral and survives RTL unchanged. A
 * horizontal scale would mirror, and its meaning with it.
 *
 * Centred on the icon box: --space-5 (card padding) + --space-6 (half of the 64 slot).
 */
.kiosk-options--scale::before {
  content: '';
  position: absolute;
  /* From the block-start of the FIRST card to the block-end of the last, never through the
   * padding above the stack: a rule that starts at the header hairline connects the stack
   * to the chrome rather than connecting the cards to each other. */
  inset-block: var(--space-6) 0;
  inset-inline-start: calc(var(--space-5) + var(--space-6) - (var(--rule-spine) / 2));
  inline-size: var(--rule-spine);
  background: var(--line-soft);
}

/* ------------------------------------------------------------------------------------
 * The option card — §5.2, §4.4
 * ---------------------------------------------------------------------------------- */

.kiosk-option {
  display: grid;
  /*
   * §5.2's internals, inline-start to inline-end:
   *   [ 24 ][ icon box 64 ][ 24 ][ label ][ flex ][ indicator 32 ][ 24 ]
   *
   * minmax(0, 1fr) rather than 1fr: a bare `1fr` will not shrink below the label's
   * min-content width, and a long option label then runs under the indicator. The `flex`
   * is a real --space-5 gutter rather than a `1fr` that a full-width label collapses to
   * nothing — on SCR_HEAT_DANGER_CLUSTER the longest label fills the track exactly, and a
   * collapsing gutter put the last word against the tick box.
   */
  grid-template-columns:
    var(--space-5) var(--icon-slot) var(--space-5) minmax(0, 1fr)
    var(--space-5) var(--indicator) var(--space-5);
  align-items: center;
  block-size: var(--card-4);
  /* §14.1: 4px, not 12, not 16, not a pill. A slight radius reads as a physical control;
   * a large uniform radius on everything reads as a template. */
  border-radius: var(--space-1);
  background: var(--surface);
  color: var(--ink-900);
  /* §4.4: selection is an INSET RING, never a border. A 2px->6px border change reflows the
   * content box by 4px on every edge and a shift under a finger already in motion is a
   * mistap. An inset box-shadow does not touch layout. */
  box-shadow: inset 0 0 0 var(--ring-rest) var(--line-strong);
  cursor: pointer;
  position: relative;
}

[data-count='2'] > .kiosk-option {
  block-size: var(--card-2);
}

[data-count='3'] > .kiosk-option {
  block-size: var(--card-3);
}

/*
 * The slot itself: §5.2's 64x64 box, whatever is in it.
 *
 * Both states occupy exactly this footprint, so resolving an icon never moves a card and
 * the option stack has the same rhythm on a screen with glyphs and a screen without. That
 * is the whole reason the slot was built empty before any glyph existed.
 */
.kiosk-option__icon {
  grid-column: 2;
  inline-size: var(--icon-slot);
  block-size: var(--icon-slot);
}

/*
 * §15.7 — the placeholder. 64px box, 1.5px dashed, one dashed diagonal corner to corner.
 *
 * 62 of the 84 answer tokens (§15.2, as revised by ADR-005 decision 7), and the demo states
 * that count out loud rather than hoping nobody counts the boxes (§15.8).
 *
 * It must read as "not drawn yet", never as an error: no alarm colour, no --acuity-*
 * token, no warning triangle, no exclamation mark, and never an emoji (§14.2 — emoji
 * render differently on every platform and carry unintended cultural load). It is also
 * deliberately LESS prominent than a resolved icon, which now exists to be compared
 * against: dashed against solid, 0.55 opacity against full, --line-strong against --ink-900.
 * A placeholder that shouts pulls attention off the label, which is the thing carrying the
 * meaning while §15.1's invariant stands UNMET.
 *
 * The token name lives in data-icon-token and never in visible text: at 64px an
 * identifier is unreadable, and a truncated one under a question is noise.
 */
.kiosk-option__icon--placeholder {
  border: var(--rule-dashed) dashed var(--line-strong);
  border-radius: var(--space-1);
  background-image: linear-gradient(
    to bottom right,
    transparent calc(50% - (var(--rule-dashed) / 2)),
    var(--line-strong) calc(50% - (var(--rule-dashed) / 2)),
    var(--line-strong) calc(50% + (var(--rule-dashed) / 2)),
    transparent calc(50% + (var(--rule-dashed) / 2))
  );
  opacity: 0.55;
}

/*
 * A vendored glyph (ADR-005 decision 4). 22 of 84.
 *
 * COLOUR COMES FROM HERE AND NEVER FROM THE ASSET (decision 4a). Every vendored path is
 * stroke="currentColor", so `color` below is the only thing that decides what a glyph looks
 * like, and a hex literal in an .svg would be a rule 6 violation that both
 * check-boundaries.ts and boundaries.test.ts now grep for.
 *
 * It does NOT change on selection. §4.4 already carries selection on two channels — the
 * 6px inset ring and the filled indicator — and a glyph that also changed colour would be
 * a third with nothing extra to say, on a surface where §4.3 keeps the label's weight
 * constant for the same reason.
 *
 * Nothing about a resolved glyph is a comprehension claim. §15.1 stands UNMET, ADR-005
 * decision 10 states that adopting a library moves the resolution rate and leaves the
 * comprehension rate at zero, and the accessibility path remains text plus audio.
 */
.kiosk-option__icon--glyph {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-900);
}

.kiosk-option__icon--glyph > svg {
  inline-size: var(--icon-glyph);
  block-size: var(--icon-glyph);
  /*
   * Below lucide's own 2. The files are drawn for 24px, where 2 is a twelfth of the box;
   * at 48 that ratio renders a 4px stroke that sits heavier than the 28px label beside it.
   * 1.5 renders 3px. Set here rather than edited into the files: a presentation attribute
   * loses to the cascade, so the vendored geometry stays byte-identical to its source and
   * the drift test in kiosk-icons.test.ts keeps working.
   */
  stroke-width: 1.5;
}

/*
 * A card with no icon box. §5.6 forbids one on a footer control and §15.6 resolves the
 * seven lang_* tokens to typography, so both arrive without a slot and the label takes the
 * space rather than being indented past an empty column.
 */
.kiosk-option:not(:has(.kiosk-option__icon)) {
  grid-template-columns:
    var(--space-5) minmax(0, 1fr) var(--space-5) var(--indicator) var(--space-5);
}

.kiosk-option:not(:has(.kiosk-option__icon)) > .kiosk-option__label {
  grid-column: 2;
}

.kiosk-option:not(:has(.kiosk-option__icon)) > .kiosk-option__input {
  grid-column: 4;
}

.kiosk-option__label {
  grid-column: 4;
  font-size: var(--type-kiosk-option-size);
  line-height: var(--type-kiosk-option-leading);
  /* §5.2, §4.3: the weight does NOT change on selection. A 400->600 shift reflows the text
   * and moves it under the finger. */
  font-weight: var(--weight-regular);
}

/*
 * The native input IS the indicator.
 *
 * `appearance: none` and a 32px box in the card's last content column, so the control the
 * browser gives roles, keyboard behaviour and screen-reader semantics to is the same
 * element a person sees. §12.3 forbids extending a hit area with a transparent overlay;
 * none is needed, because the <label> wraps the whole card and native label activation
 * gives the full 688 x 120 target.
 */
.kiosk-option__input {
  grid-column: 6;
  inline-size: var(--indicator);
  block-size: var(--indicator);
  margin: 0;
  appearance: none;
  border-radius: var(--space-1);
  background: transparent;
  position: relative;
  cursor: pointer;
}

/*
 * §5.5 difference 1: a cluster shows an empty box on EVERY card from the moment the screen
 * paints; a single-select shows its indicator only once selected. This is the difference
 * that has to be legible without reading, because a person who does not realise they may
 * pick several will pick one and continue.
 */
.kiosk-option--multi > .kiosk-option__input {
  box-shadow: inset 0 0 0 var(--ring-rest) var(--line-strong);
}

.kiosk-option__input:checked {
  box-shadow: inset 0 0 0 var(--ring-rest) var(--brand-green-700);
}

/* The check glyph. Two border edges rotated, so it inherits the selection colour and needs
 * no icon dependency. */
.kiosk-option__input::after {
  content: '';
  position: absolute;
  inset-block-start: var(--space-2);
  inset-inline-start: var(--space-3);
  inline-size: var(--space-2);
  block-size: var(--space-4);
  border-inline-end: var(--rule-spine) solid var(--brand-green-700);
  border-block-end: var(--rule-spine) solid var(--brand-green-700);
  transform: rotate(45deg);
  opacity: 0;
}

.kiosk-option__input:checked::after {
  opacity: 1;
}

/*
 * §4.4 selected. Sunlight legibility is carried by two NON-CHROMATIC signals, because hue
 * is the first thing direct sun takes away: the ring triples in weight and the fill changes
 * from white to tinted. Either alone is enough to see the selection across a room. The
 * green is the third signal, not the first.
 *
 * §11.4: selection is instant. Not 80ms, not "snappy". Zero. There is no transition
 * property in this block and there must not be one — an eased selection reads as an
 * unresponsive device and provokes a second tap, which on a cluster toggles the answer off.
 */
.kiosk-option:has(.kiosk-option__input:checked) {
  background: var(--brand-green-100);
  box-shadow: inset 0 0 0 var(--ring-selected) var(--brand-green-700);
  color: var(--brand-green-900);
}

.kiosk-option:active:has(.kiosk-option__input) {
  background: var(--brand-green-100);
  box-shadow: inset 0 0 0 var(--ring-selected) var(--brand-green-900);
  /* §11.4, §14.3: no transform. A card that shrinks under the finger moves its own hit
   * area during the press. */
}

/*
 * §14.3: hover is guarded. A kiosk has no pointer, and on several touch engines a tap
 * leaves a sticky :hover behind that makes an unselected option look selected.
 */
@media (hover: hover) {
  .kiosk-option:hover:not(:has(.kiosk-option__input:checked)) {
    box-shadow: inset 0 0 0 var(--ring-selected) var(--line-strong);
  }
}

.kiosk-option:has(.kiosk-option__input:focus-visible) {
  outline: var(--ring-focus) solid var(--focus);
  outline-offset: var(--ring-focus);
}

/* ------------------------------------------------------------------------------------
 * The audio control — §5.10, §11.2
 * ---------------------------------------------------------------------------------- */

.kiosk-audio {
  margin: 0;
  inline-size: var(--control-audio-inline);
  block-size: var(--control-audio-block);
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding-inline: var(--space-5);
  border-radius: var(--space-1);
  box-shadow: inset 0 0 0 var(--ring-rest) var(--line-strong);
  font-size: var(--type-kiosk-control-size);
  line-height: var(--type-kiosk-control-leading);
  font-weight: var(--weight-semibold);
  color: var(--ink-900);
  /* §14.2: never icon-only, on any patient control, including this one. The label is
   * always present and the glyph accompanies it. */
}

/*
 * The speaker glyph, drawn as a triangle from borders.
 *
 * Not from the icon set: §15 resolves no audio token, and §16.3 DS_008 requires that a
 * missing icon name fail loudly rather than render an empty box. A shape built from the
 * type system cannot go missing.
 */
.kiosk-audio__glyph {
  inline-size: 0;
  block-size: 0;
  border-block-start: var(--space-3) solid transparent;
  border-block-end: var(--space-3) solid transparent;
  border-inline-start: var(--space-5) solid currentcolor;
}

/* ------------------------------------------------------------------------------------
 * The footer — §5.6, §5.10
 *
 * 128 = 1 hairline + 9 slack + 34 support line + 72 control row + 12 clearance. Continue at
 * inline-end, per-question and cluster controls at inline-start, and the support line above
 * both so a long sentence has the full 1184 to sit on rather than the 840 left beside
 * Continue.
 *
 * THE 12 AT THE BLOCK END IS THE FIX, NOT THE DECORATION. §5.10's 112 leaves 5px around a
 * 72px control row and `align-content: center` gave each end 2.5 of it — so Continue's
 * bottom edge sat at 797.5 of 800 on every screen, and on `SCR_ABOUT_YOU` the §5.10 audio
 * control sat there beside it (ED_010, ED_012). Two and a half pixels is not clearance; it
 * is a control that has not clipped yet.
 *
 * `align-content: end` rather than `center` so the remainder is spent at the TOP, where it
 * is slack, and the 12 at the bottom is exactly 12 rather than 12 plus half of something.
 * Nothing moves relative to anything else: the support line still sits directly on the
 * control row, as it did.
 * ---------------------------------------------------------------------------------- */

.kiosk-footer {
  grid-column: 1 / -1;
  grid-row: 3;
  border-block-start: var(--rule-hairline) solid var(--line-soft);
  padding-block-end: var(--space-3);
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-rows: auto auto;
  align-content: end;
  align-items: center;
  column-gap: var(--space-5);
}

.kiosk-footer__support {
  grid-column: 1 / -1;
  grid-row: 1;
  margin: 0;
  /* §4.4: the inert state is EXPLAINED, never merely shown. This line names the way out of
   * it — on a cluster, that it is `none_of_these` and not Continue that says "no". */
  font-size: var(--type-kiosk-support-size);
  line-height: var(--type-kiosk-support-leading);
  color: var(--ink-500);
  text-align: end;
}

.kiosk-footer__controls {
  grid-column: 1;
  grid-row: 2;
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

/*
 * §5.6 — deliberately not options.
 *
 * A footer control must read as a different KIND of thing from a clinical item, and it does
 * so on four channels at once: pill instead of card, no icon box ever, footer region
 * instead of body region, and neutral instead of green when chosen.
 *
 * The absence of an icon is doing real work. It is the fastest-read difference, and it
 * sidesteps the fact that no honest icon exists for "none of these" anyway.
 *
 * NEVER --brand-green-* here. Green selection is the vocabulary of "I have this symptom";
 * wearing it on "none of these" makes the affirmative negative look like a fifth symptom,
 * which is the exact misreading the control exists to prevent.
 */
.kiosk-pill {
  inline-size: var(--control-pill-inline);
  block-size: var(--control-pill-block);
  display: flex;
  align-items: center;
  justify-content: center;
  /* §14.1: 32px here, and only here, because the pill shape is doing semantic work. */
  border-radius: var(--space-6);
  border: 0;
  background: var(--surface);
  box-shadow: inset 0 0 0 var(--ring-rest) var(--line-strong);
  color: var(--ink-500);
  font-size: var(--type-kiosk-control-size);
  line-height: var(--type-kiosk-control-leading);
  font-weight: var(--weight-semibold);
  font-family: inherit;
  cursor: pointer;
  position: relative;
}

.kiosk-pill__input {
  /* The radio is the control; the pill is its face. Sized to the pill and transparent, so
   * the accessible element and the drawn element are one box rather than two overlapping
   * hit areas (§12.3). */
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  margin: 0;
  appearance: none;
  border-radius: var(--space-6);
  background: transparent;
  cursor: pointer;
}

.kiosk-pill:has(.kiosk-pill__input:checked) {
  background: var(--surface-sunken);
  box-shadow: inset 0 0 0 var(--ring-selected) var(--ink-500);
  color: var(--ink-900);
}

.kiosk-pill:has(.kiosk-pill__input:focus-visible),
.kiosk-pill--button:focus-visible {
  outline: var(--ring-focus) solid var(--focus);
  outline-offset: var(--ring-focus);
}

@media (hover: hover) {
  .kiosk-pill:hover {
    box-shadow: inset 0 0 0 var(--ring-selected) var(--line-strong);
  }
}

.kiosk-pill__label {
  position: relative;
}

/* ------------------------------------------------------------------------------------
 * Continue — §4.4, §5.10, §12.4
 * ---------------------------------------------------------------------------------- */

.kiosk-continue {
  grid-column: 2;
  grid-row: 2;
  inline-size: var(--control-continue-inline);
  block-size: var(--control-continue-block);
  border: 0;
  border-radius: var(--space-1);
  background: var(--brand-green-700);
  color: var(--surface);
  font-size: var(--type-kiosk-control-size);
  line-height: var(--type-kiosk-control-leading);
  font-weight: var(--weight-semibold);
  font-family: inherit;
  cursor: pointer;
}

.kiosk-continue:focus-visible {
  outline: var(--ring-focus) solid var(--focus);
  outline-offset: var(--ring-focus);
}

/*
 * §12.4: no auto-advance on selection, on any screen type. Continue is always required.
 * That costs one tap on each of roughly eighteen screens and it is worth it: a
 * single-select that advances on tap makes a mistap UNRECOVERABLE, and here the mistap can
 * be on SCR_CONSCIOUSNESS, where a wrong tap produces potential acuity 1 and a queue
 * bypass.
 *
 * §4.4: the inert treatment, until the screen is answerable. The state is carried by
 * :has() rather than by an attribute, because with no client script an attribute set at
 * render time would still say "unanswered" after the first tap. An explanation that goes
 * stale is worse than none.
 */
.kiosk-screen:not(:has(.kiosk-option__input:checked)):not(:has(.kiosk-pill__input:checked))
  .kiosk-continue {
  background: var(--surface-sunken);
  color: var(--ink-500);
  box-shadow: inset 0 0 0 var(--ring-rest) var(--line-soft);
  cursor: not-allowed;
}

/*
 * §5.9: on paired_admin, Continue stays inert until BOTH questions are answered.
 * `resolvePaired` throws on a missing one, so an incomplete submission has to be
 * unreachable rather than merely rejected.
 *
 * Two rules and not one, and `:not()` OUTSIDE `:has()` rather than inside it. The obvious
 * spelling — `:has(.kiosk-paired__group:not(:has(input:checked)))` — puts a `:has()` inside
 * a `:has()`, which is invalid, and an invalid selector anywhere in a comma-separated list
 * DROPS THE WHOLE RULE. That failure is silent: the sheet parses, the page renders, and
 * Continue simply looks answerable when it is not. Found by measuring the computed
 * background rather than by reading the file.
 */
.kiosk-screen--paired:not(:has(.kiosk-paired__group:nth-of-type(1) input:checked))
  .kiosk-continue,
.kiosk-screen--paired:not(:has(.kiosk-paired__group:nth-of-type(2) input:checked))
  .kiosk-continue {
  background: var(--surface-sunken);
  color: var(--ink-500);
  box-shadow: inset 0 0 0 var(--ring-rest) var(--line-soft);
  cursor: not-allowed;
}

/* ------------------------------------------------------------------------------------
 * paired_admin — §5.9
 *
 * The only two-question screen, and the only one that overrides the two-column split. Both
 * questions are non-clinical, which is the sole reason it is permitted.
 *
 * 568 + 32 + 568 = 1168 against a 1184 content box. §5.9 gives both numbers and they are 16
 * apart; the slack is placed at the inline end rather than shared, so the sub-columns start
 * where the question column starts on every other screen and Continue keeps its position.
 * ---------------------------------------------------------------------------------- */

.kiosk-screen--paired {
  grid-template-columns: 568px 568px 1fr;
  column-gap: var(--space-6);
  grid-template-rows: minmax(0, 1fr) var(--kiosk-footer);
}

.kiosk-paired__group {
  grid-row: 1;
  margin: 0;
  padding: 0;
  border: 0;
  min-inline-size: 0;
  /*
   * A rendered <legend> is laid out in the fieldset's block-start BORDER area, outside the
   * padding box, so padding-block-start moves the options and never the legend. The margin
   * is what gives the sub-question air under the header hairline; the padding is the gap
   * between it and the first card.
   *
   * Budget, against the 584 body: 16 margin + 40 legend + 16 padding + 5 x 76 + 4 x 12
   * options + 12 gap + 64 control = 576. The 12 is the flex `gap` below and is spent ONCE —
   * it used to be spent again as a margin on the pill, which overflowed the column by 8 and
   * was paid for by the pill shrinking to 56, under the touch floor. See --card-paired.
   */
  margin-block-start: var(--space-4);
  padding-block-start: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  align-items: start;
}

.kiosk-paired__question {
  padding: 0;
  font-size: var(--type-kiosk-option-size);
  line-height: var(--type-kiosk-option-leading);
  font-weight: var(--weight-semibold);
}

.kiosk-screen--paired .kiosk-options {
  grid-column: auto;
  grid-row: auto;
  /* Same reason as the pill below: in a fixed-height flex column, "too tall" is spent by
   * shrinking a child rather than by overflowing where someone can see it. */
  flex: none;
  padding-block-start: 0;
  grid-template-columns: 568px;
  row-gap: var(--space-3);
}

.kiosk-screen--paired .kiosk-option {
  inline-size: 568px;
  block-size: var(--card-paired);
}

/*
 * The per-question "not sure", at its declared 332x64 and no smaller.
 *
 * `flex: none` is load-bearing and not tidiness. The sub-column is a flex column with a
 * fixed block size, so a column that is one pixel too tall is not an overflow anyone sees —
 * it is a control that quietly shrinks. That is what happened: measured at 56 on
 * `SCR_ABOUT_YOU`, 8 under `presentation.min_touch_target_px`, on the demo path, with every
 * test green. With `flex: none` the next 8px of drift overflows visibly instead.
 *
 * No `margin-block-start`: the column's own `gap` already places it, and declaring both
 * spent --space-3 twice. That double spend is the 12 of the 8px overflow above.
 */
.kiosk-screen--paired .kiosk-pill {
  flex: none;
}

.kiosk-screen--paired .kiosk-footer {
  grid-row: 2;
}

/* ------------------------------------------------------------------------------------
 * Terminal screens — §6
 *
 * All three share one shell: no header, no counter, no back, full --surface, content in a
 * single 800-wide column centred in the viewport, and the audio control. The footer is
 * empty and its --kiosk-footer is retained, so the content sits at the same optical height
 * as every screen before it. Named by the token rather than by its value, because that
 * value moved from 112 to 128 and a comment carrying the old one is how the two drift.
 *
 * SCR_RED_FLAG_HOLD and SCR_STAFF_ASSIST are visually IDENTICAL and that is deliberate
 * (§6.2). They are clinically distinct; to the person in front of the tablet they are the
 * same event. A difference that carries no meaning for the reader is noise, and here it
 * would be noise on the screen least able to afford it.
 * ---------------------------------------------------------------------------------- */

[data-terminal='true'],
[data-landing='true'] {
  flex: 1;
  min-block-size: 0;
  display: flex;
  justify-content: center;
  padding-block-end: var(--kiosk-footer);
}

.kiosk-terminal,
.kiosk-landing {
  inline-size: 800px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: start;
  gap: var(--space-5);
}

/*
 * §11.3 — the breathing dot. The only ambient motion in the patient surface, and the
 * exception is earned: a static screen and a frozen screen are indistinguishable, and this
 * is the screen where the difference matters most. It tells a person who cannot read the
 * screen that the system is live and staff are coming.
 *
 * 2400ms is slow enough to read as breathing rather than as a loading spinner, which is the
 * thing it must not become, because nothing is loading.
 *
 * --ink-500, never an alarm colour: §6.1 forbids red here outright. A red flag means
 * SOMEONE IS COMING, which is good news, and red on a screen a frightened person is reading
 * converts a routine escalation into a crisis.
 */
.kiosk-terminal__dot {
  inline-size: var(--space-4);
  block-size: var(--space-4);
  border-radius: 50%;
  background: var(--ink-500);
  animation: kiosk-breathe var(--motion-breathe) ease-in-out infinite;
}

@keyframes kiosk-breathe {
  0%,
  100% {
    opacity: 0.4;
  }
  50% {
    opacity: 1;
  }
}

.kiosk-terminal__headline,
.kiosk-landing__headline {
  margin: 0;
  font-size: var(--type-kiosk-question-size);
  line-height: var(--type-kiosk-question-leading);
  font-weight: var(--weight-semibold);
}

.kiosk-terminal__support,
.kiosk-landing__support {
  margin: 0;
  font-size: var(--type-kiosk-support-size);
  line-height: var(--type-kiosk-support-leading);
  color: var(--ink-500);
}

/* ------------------------------------------------------------------------------------
 * The landing screen
 *
 * NO SECTION OF DESIGN_SPEC SPECIFIES THIS PAGE. §5 begins at SCR_LOCALE, which is right
 * for a wall-mounted tablet a member of staff hands over already started; an unattended
 * link turns that physical act into a screen and the screen has no section. It is built
 * against §6 instead — reached, not submitted; no header, no counter, no back; one centred
 * 800 column — which is why every rule it needs is a selector added to the §6 block above
 * rather than a treatment of its own. Only the two demo paragraphs are new, because only
 * they are new on the page.
 *
 * The start control is `.kiosk-continue`, unchanged: §5.10's primary action at 320x72,
 * radius 4, --brand-green-700. The inert treatment above it is scoped to `.kiosk-screen`
 * and cannot reach here, which is correct — there is nothing to answer before pressing it,
 * so it is never inert.
 * ---------------------------------------------------------------------------------- */

/*
 * The demonstration's framing, in --ink-900 at support size.
 *
 * NOT the §5.11 unbuilt treatment: this page is built, and a dashed box would say the
 * opposite of what the sentence says. NOT --ink-500 either, which is the support line's
 * colour: on an unattended link this is the sentence that tells a stranger what they are
 * about to do and which of the two roles they are in, and it is read before the control,
 * not after it.
 *
 * DESIGN_SPEC specifies no treatment for demo PROSE on the patient surface. §10 puts the
 * handoff sentence at --type-report-headline, which is the clinician scale and would be
 * §1.4's forbidden sharing if borrowed here. This is the kiosk scale instead, and it is a
 * gap the specification owes rather than a decision it made.
 */
.kiosk-landing__intro {
  margin: 0;
  font-size: var(--type-kiosk-support-size);
  line-height: var(--type-kiosk-support-leading);
  color: var(--ink-900);
}

/*
 * CLAUDE.md §12's disclosure. Last, smallest, and still 20px.
 *
 * --type-kiosk-meta is the smallest size on this surface and is exactly §1.1's 20px floor,
 * so "brief and lower on the page" is spent on hierarchy rather than on legibility. It is
 * below the control and above the fold: §5.1's enclosure does not scroll, so nothing here
 * can be pushed out of sight, which is the usual way a disclosure gets quietly demoted.
 */
.kiosk-landing__unreviewed {
  margin: 0;
  font-size: var(--type-kiosk-meta-size);
  line-height: var(--type-kiosk-meta-leading);
  color: var(--ink-500);
}

/*
 * The two controls that are NOT the footer Continue: Start, and the demo-only way off a
 * terminal. §5.10's 320x72 becomes a FLOOR here rather than a fixed size.
 *
 * §5.10 specifies 320 for the control at the inline-end of a 112px footer, where a fixed
 * width is what keeps Continue in the same place on all eighteen screens. Neither of these
 * is in a footer and neither has a neighbour to stay level with, and both carry a longer
 * label than "Continue": at a fixed 320, "Continue the demonstration" filled the button
 * edge to edge with no padding left, which was found by looking at the screenshot rather
 * than by reading the sheet. English is the demo cut and it FITS at 320 — the failure a
 * fixed width was heading for is a locale where it does not, and it would arrive as
 * clipped text on the calmest screen in the product.
 *
 * The floor is what preserves §12.1: the block size is untouched at 72, and the inline size
 * can only grow. `padding-inline` is a §3 token, so the growth is in the same rhythm as
 * everything else.
 */
.kiosk-landing .kiosk-continue,
.kiosk-terminal .kiosk-continue {
  inline-size: auto;
  min-inline-size: var(--control-continue-inline);
  padding-inline: var(--space-5);
}

/* ------------------------------------------------------------------------------------
 * §5.11 — the unbuilt treatment
 *
 * The locales without catalogues, and the audio control while DS_002 stands.
 *
 * It reads as NOT DRAWN YET. It must not read as an error: no alarm colour, no warning
 * triangle, no exclamation mark, no --acuity-* token. One says the product is broken, the
 * other says the team knows exactly what stage it is at, and the second is both true and
 * the stronger position in front of a buyer.
 *
 * DECLARED LAST ON PURPOSE. It replaces a component's resting ring, and a component's ring
 * is set at the same specificity, so the cascade order is the whole mechanism. Moving this
 * block earlier silently un-dashes whatever is defined after it — which is exactly what it
 * did to the audio control the first time it was written.
 * ---------------------------------------------------------------------------------- */

.is-unbuilt {
  box-shadow: none;
  background: none;
  border: var(--rule-dashed) dashed var(--line-strong);
  color: var(--ink-500);
}

/* ------------------------------------------------------------------------------------
 * §11.6 — reduced motion
 *
 * THERE IS NO SCREEN FADE TO RETAIN. This block used to say "the screen fade STAYS",
 * reasoning that a 120ms opacity change with no transform is not a vestibular trigger. The
 * reasoning was sound about vestibular triggers and wrong about what that animation did:
 * it did not fade visible content, it painted the page BLANK and then revealed it, on every
 * navigation, including for a reader who had asked for reduced motion. It is gone from
 * `.kiosk-page`; see the comment there. This block now zeroes §11.3's dot and nothing else,
 * which is what it always did.
 * ---------------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .kiosk-terminal__dot {
    animation: none;
    opacity: 1;
  }
}

/* ------------------------------------------------------------------------------------
 * The demonstration's device frame. FEATURE_DEMO_CHROME only, and presentation only.
 *
 * WHY IT EXISTS. docs/DEMO.md: the demonstration is a public link opened in a desktop
 * browser. The product is a wall-mounted tablet in a waiting area, and a viewer of an
 * unattended link has nobody to say so — ED_030 records the same gap for the landing
 * screen, which §5.12 answered with a screen. This answers it with the enclosure. The
 * reason is a disclosure of deployment context and NOT that it looks better: §14.1's last
 * row rules out any justification amounting to "modern", "clean" or "polished".
 *
 * DRAWN, NEVER PHOTOGRAPHED. No device image is vendored: ADR-005 requires version, sha256
 * and licence per vendored binary; no tablet on the market is the 16:10 this enclosure is
 * (iPad Pro 11 is 1.4508, iPad mini 1.5228, this is 1.6000, so any real frame letterboxes);
 * and a named manufacturer's hardware around an SFDA candidate implies an endorsement
 * nobody has cleared. It is featureless on purpose — no camera, no button, no port. A
 * camera drawn on a pre-triage kiosk asserts a sensor this deployment does not have, beside
 * a privacy line saying the opposite, and §14.2 forbids invented imagery for the same
 * reason it forbids an invented logo mark.
 *
 * IT CHANGES NO NUMBER OF THE LAYOUT. Every dimension §5 declares is untouched and every
 * arithmetic proof in this file stays literally true. What changes is how much of the
 * viewport the enclosure is drawn into, which is §5.1's presentation rule.
 *
 * SCOPED TO ONE CLASS. Every rule below is qualified by .kiosk-device, which renderDocument
 * emits only when its view model says so, which shell() sets only from DemoChrome.enabled.
 * With the flag off no element carries the class, no rule matches, and the rendered document
 * is byte-for-byte the one this repository served before.
 *
 * NO COLOUR IS INTRODUCED. Every value is an existing token, so globals.css is untouched and
 * ADR-004 rule 6 has nothing to find. Recorded rather than hidden: --ink-900 carries a green
 * chroma near 9, so this device is drawn in the product's own neutrals rather than in a true
 * grey. A neutral device would be new tokens and a §4.1 entry, which is a palette decision
 * and not a builder's.
 *
 * ------------------------------------------------------------------------------------
 * THE FIT IS RE-DECLARED HERE, AND IT IS THE ONE THING TO BE CAREFUL WITH.
 *
 * The enclosure is fitted to the viewport by the factor declared on `html` near the top of
 * this file. Measured in Chromium: whenever that factor is below 1 the enclosure fills the
 * binding axis EXACTLY — at 1366x768 the page renders at y=0 with height 768, and at
 * 1280x800 it renders at 0,0,1280,800 — so the room outside it is ZERO and a frame of any
 * thickness is clipped. At the 1280x800 reference viewport a 48px ring measured at −49 on
 * all four sides and vanished completely. Measured, not reasoned.
 *
 * So the frame is RESERVED IN THE DENOMINATOR, and reserved by reference rather than by a
 * new number: the two bare divisors below are still 1280 and 800, still equal to
 * --kiosk-width and --kiosk-height, and still checked by kiosk-stylesheet.test.ts. What is
 * subtracted is --device-reserve, which is --device-bezel plus --device-halo.
 *
 * This declaration sits on .kiosk-device, which carries no zoom, so 100vw and 100vh compute
 * here exactly as they do at the root and a plain number inherits down to .kiosk-page.
 * Nothing overrides `zoom` and nothing re-registers the property: the @property block above
 * stays the one registration, and ADR-006's four load-bearing facts hold unchanged. This
 * block supplies a value to that registration and nothing more.
 *
 * CONSEQUENCE, STATED. The frame is drawn UNZOOMED while the enclosure inside it scales, so
 * the chamfer stays exactly one device pixel at every viewport instead of being composited
 * away at a fractional one. The cost is on the other side of the same trade and it is the
 * owner's: reserving 96 of each axis lowers the factor at the reference viewport itself, so
 * a §12.1 control declared at 64 renders at 56.3 at 1280x800 in a demo build, against 64.0
 * unframed. ED_029 records the below-1280 relaxation as unratified; this widens it to 1280
 * on the demo path only. Computed from the tokens below, not measured in a browser.
 * ---------------------------------------------------------------------------------- */

body:has(.kiosk-device) {
  /*
   * THE GROUND. A device on the same white as its own glass is a mockup. ADR-006's
   * Consequences leaves the surround above 1280 explicitly undecided and names this token
   * as the candidate; this takes that decision FOR THE DEMONSTRATION ONLY. With the flag
   * off the surround is still --surface and ADR-006 checklist item 2 is still open.
   */
  background: var(--surface-sunken);
}

.kiosk-device {
  /*
   * Three lengths, all from the §3 scale, so the frame introduces none outside it.
   *
   *   bezel  32  the dark band. 2.5 per cent of the enclosure's width per side.
   *   halo   16  the room a ground shadow may occupy outside the band. RESERVED, never
   *              hoped for: a shadow reaching past it is clipped, which is the same defect
   *              as a clipped bezel. Reserved before the shadow exists, because the reserve
   *              is what the fit subtracts and changing it later moves every viewport.
   *              UNDER-RESERVED FOR ITS OWN PURPOSE, and recorded rather than fixed: the
   *              chamfer takes one of the sixteen, so fifteen of free field is what any
   *              ground shadow has to fit inside, and no wide soft shadow does. Raising it
   *              is the owner's, because the reserve is what the fit subtracts and a §12.1
   *              control drawn at 56.3 here would fall to 53.8. See the block above.
   *   screen 32  the glass corner, and it is EQUAL to the bezel rather than half of it.
   *              Concentricity is not sufficient on its own: 16 inside a 32 band is
   *              arithmetically concentric and still reads as a nearly-square white sheet
   *              in a generously rounded tray, because the eye compares the two arcs and
   *              not their centres. Hardware runs the other way round — the display corner
   *              is as round as the bezel is thick, or rounder. §14.1 forbids UNIFORM large
   *              radii and permits one that does semantic work, as it does for the §5.6
   *              footer pill at this same 32: cards and controls stay at 4, and this radius
   *              is the whole of what makes an aperture read as an aperture.
   *              The BAND's outer corner is not declared anywhere and must not be: per
   *              css-backgrounds-3 a spread shadow's corner radii are the box's radii
   *              increased by the spread, so 32 + 32 = 64 falls out, and each ring below
   *              carries its own spread and so is concentric with the glass by mechanism
   *              instead of by a second number that can drift. It holds only while the
   *              screen radius is above zero, which is why a value and not 0.
   */
  --device-bezel: var(--space-6);
  --device-halo: var(--space-4);
  --device-screen-radius: var(--space-6);
  /* = --device-bezel + --device-halo. Read by the fit below and asserted by the test. */
  --device-reserve: var(--space-7);
  /*
   * The chamfer, one device pixel. §3 exception 1 — the same one the ring and rule widths
   * above are declared under: increments below --space-1 are required for an edge.
   *
   * It duplicates --rule-hairline and CANNOT reference it. Every local dimension in this
   * sheet is declared on .kiosk-page, which is this element's CHILD, and custom properties
   * inherit downward only: `var(--rule-hairline)` here resolves to nothing, which makes the
   * whole box-shadow invalid at computed-value time and paints NO frame at all — silently,
   * with every grep in kiosk-stylesheet.test.ts still green. The first draft of this block
   * did exactly that. So the duplication is unavoidable, and it is turned into a CHECKED one
   * rather than into a comment: kiosk-stylesheet.test.ts asserts this value equals
   * --rule-hairline's, and a second assertion asserts every token this block names is
   * reachable from this element.
   */
  --device-chamfer: 1px;

  /* See the block above before touching this. */
  --kiosk-fit: min(
    1,
    tan(
      atan2(
        min(
          calc((100vw - 2 * var(--device-reserve)) / 1280),
          calc((100vh - 2 * var(--device-reserve)) / 800)
        ),
        1px
      )
    )
  );

  /*
   * The glass. §5.1's table states the page background as --surface and this sheet declares
   * it on `body`; framed, `body` is the field, so the aperture states it here.
   *
   * This block used to add that the glass is "what keeps §11.1's screen fade honest". There
   * is no longer a screen fade — it was removed 2026-08-29 as the measured cause of the
   * flash on every Continue press; see the `.kiosk-page` rule. THIS ELEMENT IS NOT
   * OBSOLETE: it still carries the glass background, the aperture radius, the corner clip
   * and the four shadow layers, none of which `.kiosk-page` can hold. Only the fade half of
   * the argument for it has retired.
   */
  background: var(--surface);
  border-radius: var(--device-screen-radius);
  /*
   * Clips the page's square corners to the glass radius. It does not clip this element's
   * own shadows, which are painted outside its box.
   */
  overflow: hidden;
  /*
   * THE EDGE, NOT THE FRAME. Four layers, painted first to last, front to back. Each
   * non-inset layer is a DISC of its colour inflated by its own spread and painted behind
   * the element, so what the eye sees of each is only the part the layer in front of it
   * does not cover. Reading outward from the glass:
   *
   *   1 the aperture wall — one hairline of shade all the way round, where the bezel's
   *     inner wall shades the panel. Inset, so it paints above the background and below
   *     the content, and ZERO-OFFSET with the chamfer as its SPREAD: an offset hairline
   *     walls one edge only, and against --surface a single top edge is invisible at any
   *     magnification, which is what the first draft shipped.
   *     ITS COLOUR IS --line-strong AND THAT IS THE POINT. The second draft used
   *     --line-soft here, an eleven per cent step off --surface, measured invisible at 1x
   *     and near-invisible at 6x: the band met the glass as a hard near-black-to-white
   *     transition and the third part of the edge — the shade on the panel — was simply
   *     not delivered. This is layer 3's argument with the sign reversed. --line-strong is
   *     WRONG out there because it is darker than the field it sits against; it is RIGHT
   *     here for exactly that reason, because darker is what a recess reads as against
   *     white. It stays a hard hairline and never a blurred inset: a blur would be depth,
   *     which is the §14.1 row this block is holding, and one device pixel of shade is a
   *     wall rather than a card border. The 1x and 6x readings above are a review pass's
   *     measurement of both variants, not this block's author's: no pixel claim here was
   *     made by the session that wrote it.
   *   2 the band — 31 of --ink-900, i.e. the bezel less the chamfer the next layer takes.
   *   3 the catch-light — one hairline of --line-soft at 32, the band's outer boundary.
   *     THIS IS THE WHOLE OF THE EFFECT AND ITS POSITION IS THE REASON IT WORKS. The first
   *     draft put a light ring at the OUTERMOST edge, against --surface-sunken, where it is
   *     the darker of the two and so reads as anti-aliasing rather than as an edge catching
   *     light. A bright line reads only with the band's own darkness on both sides of it,
   *     which is what layers 2 and 4 supply.
   *   4 the rim — one hairline of --ink-900 at 33, so the silhouette meets the field dark
   *     and the catch-light is enclosed. A solid ring and never a gradient: §14.1 forbids a
   *     gradient anywhere, and at this size a ramp is the "designed" look this is avoiding.
   *
   * Every spread is stated in terms of --device-bezel, so the silhouette is 33 — one past
   * the bezel, exactly as the three-layer draft's outermost ring was — and --device-reserve
   * still covers it with the halo to spare. Nothing here introduces a colour: three tokens,
   * every one already in globals.css and every one used at the boundary where it is the
   * correct side of its neighbour — --line-strong darker than the glass it recesses,
   * --line-soft lighter than the ink on both sides of it, --ink-900 darker than the field.
   * A fourth value is not reached for and a gradient is not reached for.
   *
   * A FIFTH LAYER IS MISSING AND ITS ABSENCE IS DELIBERATE. §14.1: "Drop shadows by
   * default | Depth is not information here. Order is. Zero shadows in this product." Every
   * other shadow in this sheet and in report.css is `inset` or a zero-blur ring, so that row
   * describes the tree rather than aspiring to it, and a cast shadow under this device would
   * be the first exception. §0.1 puts the demo's presentation chrome inside DESIGN_SPEC's
   * ownership, so the exception is the owner's to grant and not a builder's to take.
   */
  box-shadow:
    inset 0 0 0 var(--device-chamfer) var(--line-strong),
    0 0 0 calc(var(--device-bezel) - var(--device-chamfer)) var(--ink-900),
    0 0 0 var(--device-bezel) var(--line-soft),
    0 0 0 calc(var(--device-bezel) + var(--device-chamfer)) var(--ink-900);
}
