/* Components: the parts every screen is built from.
 *
 * Each block owns its parts (block__part) and its states (data attributes or
 * is-* classes the app already writes). Colours, sizes and spacing come from
 * tokens.css only. Behaviour never hangs on these class names: the scripts
 * find elements by id or data-ui, so a rename here cannot detach a handler.
 */

@layer components {
  /* Icons: drawn as a mask on ::before, coloured by currentColor. The
     scripts rewrite some button labels with textContent, which would delete
     a child <svg>; a pseudo-element on the button itself survives that. */
  [data-icon]::before {
    content: "";
    flex: none;
    width: var(--size-icon);
    height: var(--size-icon);
    background: currentColor;
    -webkit-mask: var(--icon) center / contain no-repeat;
    mask: var(--icon) center / contain no-repeat;
  }

  [data-icon="camera"] { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z'/%3E%3Ccircle cx='12' cy='13' r='3'/%3E%3C/svg%3E"); }
  [data-icon="camera-off"] { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 2l20 20'/%3E%3Cpath d='M7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16'/%3E%3Cpath d='M9.5 4h5L17 7h3a2 2 0 0 1 2 2v7.3'/%3E%3Cpath d='M14.1 14.1A3 3 0 0 1 9.9 9.9'/%3E%3C/svg%3E"); }
  [data-icon="plus"] { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M12 5v14M5 12h14'/%3E%3C/svg%3E"); }
  [data-icon="space"] { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M22 17v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1v-1'/%3E%3C/svg%3E"); }
  [data-icon="delete"] { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 5h11a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H10l-7-7 7-7z'/%3E%3Cpath d='m18 9-6 6M12 9l6 6'/%3E%3C/svg%3E"); }
  [data-icon="volume"] { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M11 5 6 9H2v6h4l5 4V5z'/%3E%3Cpath d='M15.5 8.5a5 5 0 0 1 0 7M19 5a10 10 0 0 1 0 14'/%3E%3C/svg%3E"); }
  [data-icon="copy"] { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='9' y='9' width='13' height='13' rx='2'/%3E%3Cpath d='M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1'/%3E%3C/svg%3E"); }
  [data-icon="x"] { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M18 6 6 18M6 6l12 12'/%3E%3C/svg%3E"); }
  [data-icon="more"] { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='black'%3E%3Ccircle cx='5' cy='12' r='1.6'/%3E%3Ccircle cx='12' cy='12' r='1.6'/%3E%3Ccircle cx='19' cy='12' r='1.6'/%3E%3C/svg%3E"); }
  [data-icon="qr"] { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='3' width='7' height='7' rx='1'/%3E%3Crect x='14' y='3' width='7' height='7' rx='1'/%3E%3Crect x='3' y='14' width='7' height='7' rx='1'/%3E%3Cpath d='M14 14h3v3M21 14v.01M14 21h7v-4'/%3E%3C/svg%3E"); }
  [data-icon="theme"] { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9z'/%3E%3C/svg%3E"); }
  [data-icon="language"] { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M2 12h20'/%3E%3Cpath d='M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z'/%3E%3C/svg%3E"); }
  [data-icon="info"] { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 16v-4M12 8h.01'/%3E%3C/svg%3E"); }
  [data-icon="image"] { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='3' width='18' height='18' rx='2'/%3E%3Ccircle cx='9' cy='9' r='2'/%3E%3Cpath d='m21 15-3.1-3.1a2 2 0 0 0-2.8 0L6 21'/%3E%3C/svg%3E"); }
  [data-icon="undo"] { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 14 4 9l5-5'/%3E%3Cpath d='M4 9h10.5a5.5 5.5 0 0 1 0 11H11'/%3E%3C/svg%3E"); }
  [data-icon="retry"] { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 12a9 9 0 0 1 15-6.7L21 8'/%3E%3Cpath d='M21 3v5h-5'/%3E%3Cpath d='M21 12a9 9 0 0 1-15 6.7L3 16'/%3E%3Cpath d='M3 21v-5h5'/%3E%3C/svg%3E"); }
  [data-icon="play"] { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M10 8.5v7l5.5-3.5z'/%3E%3C/svg%3E"); }
  [data-icon="external"] { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 3h6v6M10 14 21 3M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6'/%3E%3C/svg%3E"); }

  /* Brand */
  .brand {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-3);
    min-height: var(--size-target);
    min-width: var(--size-target);
    color: var(--color-text);
    text-decoration: none;
    border-radius: var(--r-m);
  }
  .brand__mark { width: 28px; height: 28px; flex: none; }
  .brand__mark rect { fill: var(--brand-tile); stroke: var(--brand-edge); }
  .brand__mark polyline { stroke: var(--brand-trace); }
  .brand__mark circle { fill: var(--brand-trace); }
  /* The caret the traced sign turns into: the mark reads as sign to text. */
  .brand__mark line { stroke: var(--brand-caret); }
  .brand__name { font-size: 1.0625rem; font-weight: var(--fw-bold); letter-spacing: -0.005em; }

  /* Buttons: one base, a few variants. Disabled is visibly different
     (dashed edge, sunken fill), not only fainter. */
  .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    min-height: var(--size-control);
    padding: 0 var(--sp-4);
    border: 1px solid var(--color-control);
    border-radius: var(--r-m);
    background: var(--color-surface);
    color: var(--color-text);
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    line-height: 1.2;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition: background-color var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out);
  }
  .btn:hover { background: var(--color-sunken); }
  .btn--primary {
    border-color: transparent;
    background: var(--color-primary);
    color: var(--color-on-primary);
  }
  .btn--primary:hover { background: color-mix(in srgb, var(--color-primary) 86%, var(--color-surface)); }
  .btn--quiet { border-color: transparent; background: transparent; }
  .btn--icon { width: var(--size-control); padding: 0; flex: none; }
  .btn:disabled,
  .btn[aria-disabled="true"] {
    border-style: dashed;
    border-color: var(--color-control);
    background: var(--color-sunken);
    color: var(--color-text-3);
    cursor: default;
  }

  /* On the dark stage. */
  .btn--stage {
    border-color: var(--stage-line);
    background: var(--stage-raised);
    color: var(--stage-text);
  }
  .btn--stage:hover { background: color-mix(in srgb, var(--stage-raised) 80%, white); }
  .btn--stage-primary {
    border-color: transparent;
    background: var(--stage-button);
    color: var(--stage-on-button);
  }
  .btn--stage-primary:hover { background: color-mix(in srgb, var(--stage-button) 88%, white); }
  .stage :focus-visible { outline-color: var(--stage-lock); }

  /* A keyboard hint inside a button or a list. */
  .kbd {
    display: inline-flex;
    align-items: center;
    min-height: 20px;
    padding: 0 6px;
    border: 1px solid currentColor;
    border-radius: var(--r-s);
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-weight: 500;
    line-height: 1;
    opacity: 0.85;
  }
  .btn--primary .kbd { border-color: transparent; padding-inline: 0; opacity: 0.9; }
  /* The mono face has no keyboard symbols; the system face draws them. */
  .kbd--symbol { font-family: system-ui, sans-serif; font-size: var(--fs-sm); }

  /* Mode switch */
  .modes {
    min-width: 0;
    display: flex;
    gap: var(--sp-1);
    padding: var(--sp-1);
    border-radius: var(--r-strip);
    background: var(--color-sunken);
  }
  .modes__item {
    flex: 1 1 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    min-height: var(--size-target);
    padding: 0 var(--sp-4);
    border: 0;
    border-radius: 9px;
    background: transparent;
    color: var(--color-text-2);
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    white-space: nowrap;
    cursor: pointer;
  }
  .modes__item:hover { color: var(--color-text); }
  .modes__item.is-active {
    background: var(--color-selected-bg);
    color: var(--color-selected-fg);
  }
  .modes__tag { font-size: var(--fs-xs); font-weight: var(--fw-semibold); color: var(--color-text-3); }
  .modes__item.is-active .modes__tag { color: inherit; opacity: 0.8; }

  /* Quiet links in the top bar and its menu. */
  .navlink {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    min-height: var(--size-target);
    padding: 0 var(--sp-3);
    border: 0;
    border-radius: var(--r-m);
    background: transparent;
    color: var(--color-text);
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    white-space: nowrap;
    cursor: pointer;
  }
  .navlink:hover { background: var(--color-sunken); }
  .navlink.is-active { text-decoration: underline; text-underline-offset: 4px; }

  /* Camera indicator in the bar: only shown while the camera runs. */
  .live-indicator { display: none; }
  .live-indicator.is-live {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: 0 var(--sp-3);
    min-height: 32px;
    border: 1px solid var(--color-line);
    border-radius: var(--r-pill);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--color-text);
  }
  .live-indicator > span:first-child {
    width: 8px;
    height: 8px;
    border-radius: var(--r-pill);
    background: var(--color-error);
  }

  /* Stage: the mirror. Its frame carries the state the app writes to
     data-state: idle, low, maybe, good, recording. */
  .stage {
    position: relative;
    overflow: hidden;
    border-radius: var(--r-stage);
    background: var(--stage-bg);
    color: var(--stage-text);
    isolation: isolate;
  }
  .stage::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    box-shadow: inset 0 0 0 1px var(--stage-line);
    pointer-events: none;
    z-index: var(--z-stage-plate);
    transition: box-shadow var(--dur-2) var(--ease-out);
  }
  .stage[data-state="good"]::after { box-shadow: inset 0 0 0 3px var(--stage-lock); }
  .stage[data-state="recording"]::after { box-shadow: inset 0 0 0 3px var(--stage-rec); }

  .stage__video,
  .stage__overlay {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
  }
  /* A mirror: the user's right hand appears on the right. The overlay draws
     its own mirrored coordinates, so only the video flips. */
  .stage__video { object-fit: cover; transform: scaleX(-1); }
  .stage__overlay { pointer-events: none; }

  .stage__top {
    position: absolute;
    inset: var(--sp-3) var(--sp-3) auto;
    z-index: var(--z-stage-hud);
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--sp-2);
    pointer-events: none;
  }
  .stage__top > * { pointer-events: auto; }

  .stage-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    min-height: 32px;
    max-width: 100%;
    padding: 0 var(--sp-3);
    border: 1px solid var(--stage-line);
    border-radius: var(--r-pill);
    background: var(--stage-raised);
    color: var(--stage-text);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .stage-chip::before {
    content: "";
    flex: none;
    width: 8px;
    height: 8px;
    border-radius: var(--r-pill);
    background: var(--stage-text-2);
  }
  .stage[data-state="good"] .stage-chip::before { background: var(--stage-lock); }
  .stage[data-state="recording"] .stage-chip::before { background: var(--stage-rec); }

  /* Empty state: what the app does, before the camera is asked for. */
  .stage__empty {
    position: absolute;
    inset: 0;
    z-index: var(--z-stage-hud);
    display: flex;
    flex-direction: column;
    align-items: center;
    /* safe: when the content is taller than the stage it starts at the top
       and scrolls, instead of overflowing upwards out of reach. */
    justify-content: safe center;
    gap: var(--sp-5);
    /* The bottom keeps clear of the message line, which can wrap to two. */
    padding: var(--sp-16) var(--sp-6) calc(2 * var(--sp-12) + var(--sp-2));
    text-align: center;
    overflow: auto;
  }
  .stage__illustration { width: 104px; height: auto; flex: none; }
  .stage__illustration .bone { stroke: var(--stage-text-2); }
  .stage__illustration .joint { fill: var(--stage-text-2); }
  .stage__headline {
    margin: 0;
    max-width: 22ch;
    font-size: var(--fs-2xl);
    font-weight: var(--fw-heavy);
    line-height: var(--lh-tight);
    letter-spacing: -0.02em;
    text-wrap: balance;
  }
  .stage__lead {
    margin: 0;
    max-width: 44ch;
    font-size: var(--fs-sm);
    color: var(--stage-text-2);
    text-wrap: pretty;
  }
  .stage__cta {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    width: min(100%, 22rem);
  }
  .stage__cta .btn { width: 100%; }
  .stage__facts {
    display: grid;
    gap: var(--sp-2);
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: var(--fs-xs);
    color: var(--stage-text-2);
    text-align: start;
  }
  .stage__facts li { display: flex; align-items: center; gap: var(--sp-2); }
  .stage__facts li::before { --size-icon: 16px; }

  /* The first-visit content is wrapped only so the app can hide it as one
     while a camera problem shows; it takes no part in the layout. */
  .stage__intro { display: contents; }

  /* Camera problem: replaces the first-visit content when the camera cannot
     open. These are the phone rules; layout.css adjusts them to the stage's
     height and to phone landscape. */
  .stage__problem {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-4);
    width: min(100%, 30rem);
  }
  .stage__problem-icon {
    --size-icon: 28px;
    display: grid;
    place-items: center;
    width: 64px;
    height: 64px;
    flex: none;
    border: 1px solid var(--stage-line);
    border-radius: var(--r-pill);
    background: var(--stage-raised);
    color: var(--stage-text);
  }
  .stage__problem-title { font-size: var(--fs-xl); font-weight: var(--fw-bold); line-height: 1.2; }
  .stage__problem-text {
    margin: 0;
    max-width: 44ch;
    font-size: var(--fs-sm);
    line-height: 1.45;
    color: var(--stage-text-2);
    text-wrap: pretty;
  }
  .stage__problem .stage__cta { width: 100%; }
  .stage__help { width: 100%; text-align: start; }
  .stage__help > summary {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: var(--size-target);
    padding: 0 var(--sp-3);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
    list-style: none;
  }
  .stage__help > summary::-webkit-details-marker { display: none; }
  .stage__help-steps {
    display: grid;
    gap: var(--sp-3);
    margin: var(--sp-2) 0 0;
    padding: var(--sp-4) var(--sp-4) var(--sp-4) var(--sp-8);
    border: 1px solid var(--stage-line);
    border-radius: var(--r-l);
    background: var(--stage-raised);
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
    color: var(--stage-text-2);
  }
  .stage__help-steps strong { color: var(--stage-text); }
  /* While a problem shows, the plate's message line would only repeat it,
     so the plate is hidden and the stage keeps no room for it. */
  .stage:has(.stage__problem:not([hidden])) + .plate { display: none; }
  .stage__empty:has(> .stage__problem:not([hidden])) { padding-block: var(--sp-12) var(--sp-6); }
  /* Short phones: the icon goes first, as the illustration does. */
  @media (max-height: 40rem) {
    .stage__problem-icon { display: none; }
    .stage__problem { gap: var(--sp-3); }
  }

  /* Camera on: the empty state is hidden by the app; the close button shows. */
  .stage__close { display: none; }
  .stage:has(> .stage__empty[hidden]) .stage__close { display: inline-flex; }

  /* The camera-free demo (js/media/demo-source.js), while the stage's
     data-demo names its phase. Its stop takes the close button's place at
     the right of the top row; the card says which word the recorded hand
     spells, with the letter on the stage marked, and what to do. The card
     takes a row of its own under the top row, and on a laptop it stands
     between the chip and the stop. The overlay draws the hand in the room
     the card and the plate leave, so the card is kept short: one row of
     progress, two lines of text. */
  .stage[data-demo] .stage__top { flex-wrap: wrap; }
  .stage-demo__stop { margin-inline-start: auto; }
  .stage-demo {
    flex: 0 0 100%;
    order: 3;
    display: grid;
    gap: var(--sp-2);
    max-width: 32rem;
    padding: var(--sp-3);
    border: 1px solid var(--stage-line);
    border-radius: var(--r-m);
    background: var(--stage-plate);
    color: var(--stage-text);
  }
  .stage-demo__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    min-width: 0;
  }
  .stage-demo__progress {
    display: flex;
    align-items: baseline;
    gap: var(--sp-3);
    min-width: 0;
    margin: 0;
  }
  .stage-demo__word {
    display: inline-flex;
    gap: 0.2em;
    font-size: var(--fs-xl);
    font-weight: var(--fw-heavy);
    line-height: 1;
  }
  /* The letter on the stage: the lock colour and an underline, so it is not
     told by colour alone; the letters still to come are dimmed. */
  .stage-demo__word > [data-state="current"] {
    color: var(--stage-lock);
    text-decoration: underline 3px;
    text-underline-offset: 5px;
  }
  .stage-demo__word > [data-state="next"] { color: var(--stage-text-2); }
  .stage-demo__count { font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--stage-text-2); }
  .stage-demo__text {
    margin: 0;
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
    color: var(--stage-text);
    text-wrap: pretty;
  }
  .stage-demo__skip { flex: none; min-height: 40px; padding-inline: var(--sp-3); font-size: var(--fs-sm); }
  .stage-demo__camera { justify-self: start; }
  .stage[data-demo="done"] .stage-demo__text { font-size: var(--fs-md); font-weight: var(--fw-semibold); }
  /* At the end there is no reading left: the plate keeps only its message
     line, as with the camera off. */
  .stage[data-demo="done"] + .plate .plate__visual,
  .stage[data-demo="done"] + .plate .plate__level { display: none; }

  /* Caption plate: the one place the current result lives. With the camera
     off only its message line shows, so loading and errors are never off
     screen. It is the stage's next sibling, so it reads the stage's state
     through "+"; layout.css places it over the stage or beside it. */
  .plate {
    z-index: var(--z-stage-plate);
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    padding: var(--sp-3) var(--sp-4);
    border: 2px solid transparent;
    border-radius: var(--r-l);
    background: var(--stage-plate);
    color: var(--stage-text);
  }
  .stage[data-state="good"] + .plate { border-color: var(--stage-lock); }

  .plate__visual {
    position: relative;
    display: grid;
    place-items: center;
    width: var(--size-ring);
    height: var(--size-ring);
    flex: none;
  }
  .plate__glyph {
    position: relative;
    font-size: var(--fs-glyph);
    font-weight: var(--fw-heavy);
    line-height: 1;
    letter-spacing: -0.02em;
  }
  /* The app writes the score (0-100) to --score and the level to the class. */
  .confidence-ring {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    --ring: var(--stage-text-2);
    background: conic-gradient(var(--ring) calc(var(--score, 0) * 1%), var(--stage-bar-off) 0);
    -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 4px), black calc(100% - 4px));
    mask: radial-gradient(farthest-side, transparent calc(100% - 4px), black calc(100% - 4px));
  }
  /* A word: no ring around it, the word face, and room to be read. */
  .stage[data-kind="word"] + .plate .plate__visual { width: auto; height: auto; min-width: 0; place-items: start; }
  .stage[data-kind="word"] + .plate .confidence-ring { display: none; }
  .stage[data-kind="word"] + .plate .plate__glyph {
    font-size: var(--fs-word);
    line-height: 1.05;
    letter-spacing: -0.01em;
    overflow-wrap: anywhere;
  }
  .stage[data-kind="word"] + .plate { flex-wrap: wrap; }
  .confidence-ring.maybe { --ring: var(--stage-text); }
  .confidence-ring.good { --ring: var(--stage-lock); }

  .plate__body { display: grid; gap: var(--sp-1); min-width: 0; }
  .plate__level {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-1) var(--sp-3);
    margin: 0;
  }
  .plate__decision { font-size: var(--fs-level); font-weight: var(--fw-bold); line-height: 1.2; }
  .plate__pct { font-size: var(--fs-md); font-weight: 500; }
  .plate__message {
    margin: 0;
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
    color: var(--stage-text-2);
    text-wrap: pretty;
  }

  /* Three bars: level by count as well as by colour. Not named meter: the
     legacy layer styles .meter as the word recording's progress bar. */
  .signal { display: inline-flex; align-items: flex-end; gap: 3px; height: 17px; }
  .signal i { width: 5px; border-radius: 2px; background: var(--stage-bar-off); }
  .signal i:nth-child(1) { height: 9px; }
  .signal i:nth-child(2) { height: 13px; }
  .signal i:nth-child(3) { height: 17px; }
  .stage[data-state="low"] + .plate .signal i:nth-child(1) { background: var(--stage-text-2); }
  .stage[data-state="maybe"] + .plate .signal i:nth-child(-n + 2) { background: var(--stage-text); }
  .stage[data-state="good"] + .plate .signal i { background: var(--stage-lock); }

  /* Camera off: keep only the message, as a quiet line under the empty state. */
  .stage:has(> .stage__empty:not([hidden])) + .plate {
    justify-content: center;
    padding-block: var(--sp-2);
    background: transparent;
    border-color: transparent;
  }
  .stage:has(> .stage__empty:not([hidden])) + .plate .plate__visual,
  .stage:has(> .stage__empty:not([hidden])) + .plate .plate__level { display: none; }
  .stage:has(> .stage__empty:not([hidden])) + .plate .plate__message { text-align: center; }

  .stage__countdown {
    position: absolute;
    inset: 0;
    z-index: var(--z-stage-plate);
    display: grid;
    place-items: center;
    font-size: 6rem;
    font-weight: var(--fw-heavy);
    color: var(--stage-text);
    pointer-events: none;
  }
  .stage__countdown:not([hidden])::before {
    content: "";
    position: absolute;
    width: 9rem;
    height: 9rem;
    border: 3px solid var(--stage-text);
    border-radius: 50%;
  }

  /* Warnings from the model: confusion, hard, low quality, accepted word.
     The app rewrites this element's class to feedback-bar feedback-bar--kind,
     so it is styled through that name. */
  .stage .feedback-bar {
    position: absolute;
    inset: calc(var(--sp-3) + 40px) var(--sp-3) auto;
    z-index: var(--z-stage-hud);
    width: fit-content;
    max-width: calc(100% - 2 * var(--sp-3));
    margin-inline: auto;
    padding: var(--sp-2) var(--sp-3);
    border: 1px solid var(--stage-line);
    border-radius: var(--r-m);
    background: var(--stage-raised);
    color: var(--stage-text);
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
    display: flex;
    align-items: center;
    gap: var(--sp-2);
  }
  .stage .feedback-bar::before {
    content: "";
    flex: none;
    width: 8px;
    height: 8px;
    border-radius: var(--r-pill);
    background: var(--stage-text-2);
  }
  .stage .feedback-bar--low::before { background: var(--stage-rec); }
  .stage .feedback-bar--good::before { background: var(--stage-lock); }
  .stage .feedback-bar[hidden] { display: none; }

  /* Confirmation toast: floats over the stage, so a shadow and no border. */
  .toast {
    position: absolute;
    z-index: var(--z-toast);
    /* Below the chip and the 48 px camera buttons of the top row. */
    inset: calc(var(--sp-3) + 56px) var(--sp-3) auto;
    width: fit-content;
    max-width: calc(100% - 2 * var(--sp-3));
    margin-inline: auto;
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    min-height: var(--size-target);
    padding: var(--sp-1) var(--sp-1) var(--sp-1) var(--sp-4);
    border-radius: var(--r-strip);
    background: var(--color-surface);
    color: var(--color-text);
    box-shadow: var(--elev-float);
    font-size: var(--fs-sm);
  }
  .toast[hidden] { display: none; }
  .toast__action { --size-icon: 18px; min-height: 36px; padding-inline: var(--sp-3); }
  /* The toast is a light surface on the dark stage: the stage's saffron ring
     would be 1.6:1 here. */
  .toast :focus-visible { outline-color: var(--color-focus); }

  /* Transcript strip */
  .strip {
    display: flex;
    align-items: center;
    gap: var(--sp-1);
    min-height: 56px;
    padding: 0 6px 0 var(--sp-4);
    border: 1px solid var(--color-line);
    border-radius: var(--r-strip);
    background: var(--color-surface);
  }
  .strip__text {
    flex: 1;
    min-width: 0;
    margin: 0;
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    line-height: var(--lh-snug);
    letter-spacing: 0.04em;
    white-space: nowrap;
    /* Scrolled to its end by the app, so the newest letter shows; an ellipsis
       at the end would hide exactly that letter. The scrollbar would only
       take height from a 56 px strip. */
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
  }
  .strip__text::-webkit-scrollbar { display: none; }
  /* Text scrolled off at the start fades out instead of being cut hard. */
  .strip__text[data-overflow="start"] {
    -webkit-mask-image: linear-gradient(to right, transparent, black var(--sp-6));
    mask-image: linear-gradient(to right, transparent, black var(--sp-6));
  }
  .strip__text .cursor {
    display: inline-block;
    width: 2px;
    height: 1.1em;
    margin-left: 2px;
    vertical-align: -0.15em;
    overflow: hidden;
    color: transparent;
    background: var(--color-text);
    animation: caret-blink 1.1s steps(1) infinite;
  }
  @keyframes caret-blink { 50% { opacity: 0; } }
  /* The placeholder reads as a hint, not as typed text. */
  .strip__text[data-empty="true"] {
    font-size: var(--fs-md);
    font-weight: var(--fw-regular);
    letter-spacing: 0;
    color: var(--color-text-3);
    /* The hint reads from its start, cut with an ellipsis where it must be. */
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .strip__text[data-empty="true"] .cursor { display: none; }
  .strip .btn--icon { width: var(--size-target); min-height: var(--size-target); }

  /* Panels: the side column on a laptop, below the fold on a phone */
  .panel {
    display: grid;
    gap: var(--sp-3);
    padding: var(--sp-5);
    border: 1px solid var(--color-line);
    border-radius: var(--r-l);
    background: var(--color-surface);
  }
  .panel__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
  }
  .panel__title { margin: 0; font-size: 1.125rem; line-height: var(--lh-snug); font-weight: var(--fw-bold); }
  .panel__kicker { font-size: var(--fs-xs); font-weight: var(--fw-semibold); color: var(--color-text-2); }
  .panel__note { margin: 0; font-size: var(--fs-sm); color: var(--color-text-2); }

  /* Ranked results. The rows are built by renderTop3(). */
  .ranked .top3-list { display: grid; gap: var(--sp-3); }
  .ranked .top3-row {
    display: grid;
    grid-template-columns: 2.5rem minmax(0, 1fr) 3rem;
    align-items: center;
    gap: var(--sp-3);
    color: var(--color-text-2);
  }
  .ranked .top3-row:first-child { color: var(--color-text); }
  /* Word results need a label column wide enough to read them. */
  .app-shell:has(.stage[data-kind="word"]) .ranked .top3-row { grid-template-columns: minmax(0, 8.5rem) minmax(0, 1fr) 3rem; }
  .app-shell:has(.stage[data-kind="word"]) .ranked .top3-row b { font-size: var(--fs-md); }
  .ranked .top3-empty { margin: 0; font-size: var(--fs-sm); color: var(--color-text-2); }
  .ranked .top3-row .rank { display: none; }
  .ranked .top3-row b { font-size: var(--fs-lg); font-weight: var(--fw-heavy); overflow: hidden; text-overflow: ellipsis; }
  .ranked .top3-row .bar { height: 8px; border-radius: 4px; background: var(--color-sunken); overflow: hidden; }
  .ranked .top3-row .bar > span { display: block; height: 100%; background: currentColor; }
  .ranked .top3-row > span:last-child {
    text-align: right;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-sm);
  }

  /* Reference card */
  .reference__body { display: grid; gap: var(--sp-3); }
  .reference__body img {
    width: 100%;
    max-height: 22rem;
    object-fit: contain;
    border-radius: var(--r-strip);
    background: var(--color-sunken);
  }
  .reference__actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
  .reference .reference-thumbs { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
  .reference .reference-thumb {
    min-width: var(--size-target);
    min-height: var(--size-target);
    border: 1px solid var(--color-control);
    border-radius: var(--r-m);
    background: var(--color-surface);
    font-family: var(--font-mono);
    font-weight: 500;
  }
  .reference .reference-thumb[data-active="true"] {
    background: var(--color-selected-bg);
    color: var(--color-selected-fg);
    border-color: transparent;
  }

  /* Shortcut list */
  .shortcuts { display: grid; grid-template-columns: auto 1fr; gap: var(--sp-2) var(--sp-3); align-items: center; margin: 0; }
  .shortcuts dt { margin: 0; }
  .shortcuts dd { margin: 0; font-size: var(--fs-sm); color: var(--color-text-2); }

  /* Start-up error: shown when the app could not start, so the page does not
     look ready while every button does nothing. It floats under the top bar,
     over whatever screen the markup shows, and is announced by the app
     through #announcer, so it is not a live region itself. */
  .startup-error {
    position: fixed;
    top: calc(var(--size-topbar) + var(--sp-2));
    left: 50%;
    z-index: var(--z-toast);
    width: min(calc(100% - 2 * var(--sp-3)), 34rem);
    margin: 0;
    padding: var(--sp-3) var(--sp-4);
    border: 1px solid var(--color-error);
    border-left-width: 4px;
    border-radius: var(--r-m);
    background: var(--color-surface);
    color: var(--color-text);
    box-shadow: var(--elev-float);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    line-height: var(--lh-snug);
    transform: translateX(-50%);
  }

  /* Skip link: off screen until it has keyboard focus, then above the top
     bar. */
  .skip-link {
    position: absolute;
    top: var(--sp-2);
    left: var(--sp-2);
    z-index: var(--z-lightbox);
    display: inline-flex;
    align-items: center;
    min-height: var(--size-target);
    padding: 0 var(--sp-4);
    border-radius: var(--r-m);
    background: var(--color-selected-bg);
    color: var(--color-selected-fg);
    font-weight: var(--fw-semibold);
    text-decoration: none;
    transform: translateY(calc(-100% - var(--sp-4)));
  }
  .skip-link:focus { transform: none; }
  /* The skip link's target takes focus from script; the ring would outline
     the whole page. */
  main[tabindex="-1"]:focus { outline: none; }

  /* Popover menu: "Diğer" on a phone */
  .menu {
    margin: 0;
    padding: var(--sp-2);
    border: 1px solid var(--color-line);
    border-radius: var(--r-l);
    background: var(--color-surface);
    color: var(--color-text);
    box-shadow: var(--elev-float);
  }
}
