/* MODULE: css/controls.css — REAL CONTROL HARDWARE.
   Loaded after tactile.css. Depends on the light variables from src/light.js.

   Reference: docs/reference/braun-receiver-panel.jpg, rams-switches.jpg,
              control-panel-kit-*.jpg

   A Braun panel is not a grid of identical keys. It is a vocabulary of
   different PARTS, each shaped by what it does:

     · a boolean          → a toggle switch that visibly sits one way or the other
     · a momentary action → a domed push button that returns
     · the one action     → the single red push button on the whole panel
     · a continuous value → a knurled rotary knob

   Matching the part to the job is the functional half of Rams. A toggle you
   can see the state of from across the room beats a button that merely looks
   pressed.
   ======================================================================== */

/* ── 1. THE TOGGLE SWITCH ────────────────────────────────────────────────
   For anything genuinely binary. The label stays as type; the switch is a
   real part beside it, so state is legible without reading the label's
   styling. Built on <button aria-pressed>, so the semantics already exist.

   Track is RECESSED (cut into the panel: shadow at the top lip).
   Thumb is RAISED (a part sitting in the channel: highlight + contact shadow).
   The thumb travels. That travel is the whole affordance. */
.chip[aria-pressed] {
  --sw-w: 2.25rem;
  --sw-h: 1.25rem;
  --sw-pad: 0.1875rem;

  display: inline-flex !important;
  align-items: center !important;
  gap: 0.5rem !important;

  /* The control itself carries no body — the switch does. */
  background-color: transparent !important;
  background-image: none !important;
  box-shadow: none !important;
  border-radius: 4px !important;
  padding-inline: 0.25rem !important;
}

/* the channel */
.chip[aria-pressed]::after {
  content: "";
  inline-size: var(--sw-w);
  block-size: var(--sw-h);
  flex: 0 0 auto;
  border-radius: 999px;
  background-color: var(--ramp-250);
  box-shadow:
    inset 0 1.5px 3px rgba(46, 40, 30, 0.2),
    inset 0 0 0 0.5px rgba(46, 40, 30, 0.1);
  transition: background-color 160ms cubic-bezier(0.2, 0, 0.2, 1);
}

/* the thumb, drawn inside the channel and moved by a gradient position so no
   extra element is needed */
.chip[aria-pressed]::after {
  background-image: radial-gradient(
    circle at calc(var(--sw-pad) + (var(--sw-h) - var(--sw-pad) * 2) / 2) 50%,
    var(--ramp-000) 0,
    var(--ramp-050) calc((var(--sw-h) - var(--sw-pad) * 2) / 2 - 1.5px),
    transparent calc((var(--sw-h) - var(--sw-pad) * 2) / 2)
  );
  background-repeat: no-repeat;
  transition:
    background-position 190ms cubic-bezier(0.34, 1.3, 0.5, 1),
    background-color 160ms linear;
  background-position: 0 0;
}

/* ON: the channel takes the signal colour and the thumb travels to the far end */
.chip[aria-pressed="true"]::after {
  background-color: var(--signal-key);
  background-position: calc(var(--sw-w) - var(--sw-h)) 0;
}

/* A real switch has a detent — it does not glide under the finger, it snaps. */
.chip[aria-pressed]:active::after {
  filter: brightness(0.96);
}

/* ── 2. SETTINGS SWITCHES — the Rams pill ────────────────────────────────
   The markup already has track + thumb elements, so this is the real thing:
   a domed thumb in a recessed channel, per rams-switches.jpg. */
.switch__track {
  background-color: var(--ramp-250) !important;
  background-image: none !important;
  border-radius: 999px !important;
  box-shadow:
    inset 0 1.5px 3px rgba(46, 40, 30, 0.2),
    inset 0 0 0 0.5px rgba(46, 40, 30, 0.1) !important;
  transition: background-color 170ms cubic-bezier(0.2, 0, 0.2, 1) !important;
}

.switch__thumb {
  background-color: var(--ramp-050) !important;
  /* domed: lit from wherever the room light currently is */
  background-image: radial-gradient(
    124% 124% at calc(50% + var(--hl-x)) calc(50% + var(--hl-y)),
    color-mix(in oklab, var(--light-tint) calc(var(--hl-alpha) * 92%), transparent) 0%,
    transparent 66%
  ) !important;
  box-shadow:
    0 1px var(--ao-blur) rgba(46, 40, 30, calc(var(--ao-alpha) + 0.04)),
    var(--sh-x) var(--sh-y) calc(var(--sh-blur) * 0.55) rgba(46, 40, 30, var(--sh-alpha)),
    0 0 0 0.5px rgba(46, 40, 30, 0.12) !important;
  transition: transform 200ms cubic-bezier(0.34, 1.3, 0.5, 1) !important;
}

.switch__input:checked + .switch__track,
.switch__input[aria-checked="true"] + .switch__track,
.switch[data-on="true"] .switch__track {
  background-color: var(--signal-key) !important;
}

/* ── 3. THE ONE RED PUSH BUTTON ──────────────────────────────────────────
   braun-receiver-panel.jpg has exactly one red control on the entire face.
   Ours is start. It is the tallest, most domed part on the panel — the thing
   your thumb finds without looking. */
.btn--primary,
.btn--lg.btn--primary {
  border-radius: 14px !important;
  background-color: var(--signal-key) !important;
  background-image:
    radial-gradient(
      130% 130% at calc(50% + var(--hl-x)) calc(50% + var(--hl-y)),
      rgba(255, 255, 255, calc(var(--hl-alpha) * 0.34)) 0%,
      transparent 60%
    ),
    radial-gradient(
      140% 140% at calc(50% - var(--hl-x)) calc(50% - var(--hl-y)),
      rgba(90, 18, 0, 0.24) 0%,
      transparent 58%
    ) !important;
  box-shadow:
    0 1.5px calc(var(--ao-blur) + 1px) rgba(46, 40, 30, calc(var(--ao-alpha) + 0.1)),
    var(--sh-x) calc(var(--sh-y) * 1.3) calc(var(--sh-blur) * 1.15)
      rgba(46, 40, 30, calc(var(--sh-alpha) + 0.05)),
    0 0 0 0.5px rgba(110, 26, 4, 0.4) !important;
}

/* It travels further than the others, because it is a bigger part. */
.btn--primary:active {
  transform: translateY(2px) !important;
  box-shadow:
    0 0.5px 1.5px rgba(46, 40, 30, calc(var(--ao-alpha) + 0.14)),
    0 0 0 0.5px rgba(110, 26, 4, 0.46),
    inset 0 1.5px 3px rgba(90, 18, 0, 0.22) !important;
}

/* ── 4. TRANSPORT KEYS — momentary parts, round where the label allows ────
   +1 and +5 are short enough to be true circles, like the receiver's round
   push buttons. reset keeps a key shape because its label will not fit. */
.tt-timer__transportside .btn {
  border-radius: 999px !important;
  min-inline-size: 3rem !important;
  aspect-ratio: 1 / 1 !important;
  padding: 0 !important;
  display: inline-grid !important;
  place-items: center !important;
}

/* ── 5. THE ROTARY KNOB ──────────────────────────────────────────────────
   The dial's drag handle is a knurled rotary knob — the part your fingers
   expect to twist. Knurling is drawn, not photographed: a repeating conic
   gradient gives real ridges that stay crisp at any size and cost no bytes.
   Per braun-receiver-panel.jpg the knob is DARK against the pale face. */
.dial__knobFace {
  fill: var(--ramp-700) !important;
}
.dial__knobRing {
  stroke: rgba(20, 18, 16, 0.55) !important;
  stroke-width: 0.8 !important;
}
/* the indicator line, white, centre to edge — the receiver's knobs all have one */
.dial__knobGroove {
  display: initial !important;
  stroke: var(--ramp-050) !important;
  stroke-width: 0.7 !important;
  opacity: 0.9 !important;
}

/* ── 6. GRAPHITE ─────────────────────────────────────────────────────────*/
[data-theme="graphite"] .chip[aria-pressed]::after,
[data-theme="graphite"] .switch__track {
  background-color: var(--ramp-250) !important;
  box-shadow:
    inset 0 1.5px 3px rgba(0, 0, 0, 0.6),
    inset 0 0 0 0.5px rgba(0, 0, 0, 0.4) !important;
}
[data-theme="graphite"] .chip[aria-pressed="true"]::after,
[data-theme="graphite"] .switch__input:checked + .switch__track {
  background-color: var(--signal) !important;
}
[data-theme="graphite"] .dial__knobFace {
  fill: var(--ramp-400) !important;
}

/* ── 7. REDUCED MOTION / FORCED COLOURS ──────────────────────────────────
   The thumb still MOVES under reduced motion — its position is the state, and
   hiding that would remove information. What goes is the spring easing. */
@media (prefers-reduced-motion: reduce) {
  .chip[aria-pressed]::after,
  .switch__thumb {
    transition-duration: 0.001ms !important;
  }
}

@media (forced-colors: active) {
  .chip[aria-pressed]::after,
  .switch__track {
    background-image: none !important;
    border: 1px solid ButtonText;
  }
  .chip[aria-pressed="true"]::after {
    background-color: Highlight !important;
  }
}

/* ── 8. THE GRILLE — repositioned off the wordmark ───────────────────────
   It previously spanned the full header row and ran behind the brand, relying
   on an opaque nameplate to occlude the flutes under the letterforms. It was
   occluded but still CROWDED: the flutes restarted immediately at the end of
   the word with no clear space, and bled under the mode selector.

   Rams gives a wordmark air. On the SK/RT20 the grille is a distinct field
   with the mark well clear of it; on the receiver the mark sits alone.

   So the grille becomes its own bounded block with real clear space on both
   sides, and no longer passes behind either the brand or the selector. */
.chassis__grille {
  /* Sit between the brand and the selector rather than under everything. */
  grid-column: 1 / 2 !important;
  grid-row: 1 !important;
  justify-self: end !important;
  align-self: center !important;

  /* A defined block, not a bleed. */
  inline-size: clamp(2.5rem, 7vw, 5rem) !important;
  min-block-size: 1.125rem !important;

  /* THE CLEAR SPACE. This is the fix: the flutes start well after the
     wordmark and stop well before the selector. */
  margin-inline-start: var(--space-5, 1.75rem) !important;
  margin-inline-end: var(--space-2, 0.5rem) !important;

  background-position: 50% 50% !important;
  /* Fade both ends so the striping stops rather than butting into anything. */
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 22%, #000 78%, transparent 100%) !important;
  mask-image: linear-gradient(90deg, transparent 0, #000 22%, #000 78%, transparent 100%) !important;
  opacity: 0.72 !important;
}

/* The nameplate existed only to punch a hole in a grille that ran underneath
   the mark. Nothing runs underneath it now, so the plate is dead weight. */
.brand::after,
.brand::before {
  background-image: none !important;
}

/* Narrow: there is no room for a decorative block beside the mark, and Rams
   would drop the ornament before he dropped the clear space. */
@media (max-width: 47.999rem) {
  .chassis__grille {
    display: none !important;
  }
}

/* ── 9. GRILLE: give it its OWN column ───────────────────────────────────
   Placing it in `grid-column: 1/2` put it back in the brand's own cell, where
   justify-self:end simply parked it under the wordmark again. A block cannot
   be clear of the mark while sharing its cell — it needs a track of its own. */
.chassis {
  grid-template-columns: auto auto minmax(0, 1fr) auto !important;
  grid-template-areas: "brand grille modes tools" !important;
}
.chassis__grille {
  grid-area: grille !important;
  grid-column: auto !important;
  justify-self: start !important;
}

@media (max-width: 47.999rem) {
  .chassis {
    grid-template-columns: auto minmax(0, 1fr) !important;
    grid-template-areas:
      "brand tools"
      "modes modes" !important;
  }
}

/* ── 10. THE FACE SELECTOR IS A ROCKER ───────────────────────────────────
   original/custom is a two-position choice, and braun-receiver-panel.jpg
   makes exactly that kind of choice with a rocker (sharp/broad, dial light).
   A rocker is ONE part that tilts: the chosen side goes DOWN into the panel,
   the other side stands proud. Reading which way it is tilted is instant and
   needs no colour, which is why it beats two lookalike keys. */
.tt-timer__legendgroup {
  display: inline-flex !important;
  gap: 0 !important;
  padding: 2px !important;
  border-radius: 10px !important;
  background-color: var(--ramp-250) !important;
  box-shadow:
    inset 0 1.5px 3px rgba(46, 40, 30, 0.18),
    inset 0 0 0 0.5px rgba(46, 40, 30, 0.1) !important;
}

.tt-timer__legendgroup .chip {
  background-color: transparent !important;
  background-image: none !important;
  box-shadow: none !important;
  border-radius: 8px !important;
  margin: 0 !important;
  transition:
    background-color 140ms linear,
    box-shadow 140ms cubic-bezier(0.2, 0, 0.2, 1),
    transform 140ms cubic-bezier(0.2, 0, 0.2, 1);
}

/* the raised half — a part standing proud of the panel */
.tt-timer__legendgroup .chip:not(.is-selected) {
  background-color: var(--ramp-050) !important;
  background-image: radial-gradient(
    120% 120% at calc(50% + var(--hl-x)) calc(50% + var(--hl-y)),
    color-mix(in oklab, var(--light-tint) calc(var(--hl-alpha) * 78%), transparent) 0%,
    transparent 66%
  ) !important;
  box-shadow:
    0 1px var(--ao-blur) rgba(46, 40, 30, var(--ao-alpha)),
    0 0 0 0.5px rgba(46, 40, 30, 0.1) !important;
}

/* the depressed half — tilted down INTO the panel, so it takes the shadow */
.tt-timer__legendgroup .chip.is-selected {
  background-color: var(--ramp-200) !important;
  color: var(--ink) !important;
  transform: translateY(0.5px) !important;
  box-shadow:
    inset 0 1.5px 3px rgba(46, 40, 30, 0.17),
    inset 0 0 0 0.5px rgba(46, 40, 30, 0.09) !important;
}

[data-theme="graphite"] .tt-timer__legendgroup,
[data-theme="graphite"] .tt-timer__legendgroup .chip.is-selected {
  box-shadow:
    inset 0 1.5px 3px rgba(0, 0, 0, 0.6),
    inset 0 0 0 0.5px rgba(0, 0, 0, 0.4) !important;
}

/* ── 10b. The label is not part of the switch ────────────────────────────
   `.tt-timer__grouplabel` is the group's first child, so putting the recessed
   channel on the group swallowed the word "face" into the hardware. On the
   receiver the legend sits on the PANEL, above or beside the part, never on
   it. So: the group goes transparent again, and the two halves join to each
   other to form the rocker. */
.tt-timer__legendgroup {
  background-color: transparent !important;
  box-shadow: none !important;
  padding: 0 !important;
  gap: var(--space-2, 0.5rem) !important;
  align-items: center !important;
}

.tt-timer__grouplabel {
  background: transparent !important;
  box-shadow: none !important;
  margin-inline-end: 0.125rem !important;
}

/* The two halves butt together into one part. Outer corners round, inner
   corners square — that is what makes them read as a single rocker rather
   than as two buttons that happen to be adjacent. */
.tt-timer__legendgroup .chip {
  border-radius: 0 !important;
  margin: 0 !important;
}
.tt-timer__legendgroup .chip:nth-of-type(1) {
  border-start-start-radius: 9px !important;
  border-end-start-radius: 9px !important;
}
.tt-timer__legendgroup .chip:nth-last-of-type(1) {
  border-start-end-radius: 9px !important;
  border-end-end-radius: 9px !important;
}

/* Raised half: proud of the panel, lit from the room light. */
.tt-timer__legendgroup .chip:not(.is-selected) {
  background-color: var(--ramp-050) !important;
  box-shadow:
    0 1px var(--ao-blur) rgba(46, 40, 30, var(--ao-alpha)),
    0 0 0 0.5px rgba(46, 40, 30, 0.12) !important;
}

/* Depressed half: tilted down into the panel, so it takes the shadow and
   loses the highlight entirely. */
.tt-timer__legendgroup .chip.is-selected {
  background-color: var(--ramp-250) !important;
  background-image: none !important;
  transform: none !important;
  box-shadow:
    inset 0 1.5px 3px rgba(46, 40, 30, 0.2),
    inset 0 0 0 0.5px rgba(46, 40, 30, 0.1) !important;
}

[data-theme="graphite"] .tt-timer__legendgroup .chip.is-selected {
  box-shadow:
    inset 0 1.5px 3px rgba(0, 0, 0, 0.62),
    inset 0 0 0 0.5px rgba(0, 0, 0, 0.42) !important;
}
[data-theme="graphite"] .tt-timer__legendgroup .chip:not(.is-selected) {
  box-shadow:
    0 1px var(--ao-blur) rgba(0, 0, 0, calc(var(--ao-alpha) + 0.3)),
    0 0 0 0.5px rgba(0, 0, 0, 0.5) !important;
}

/* ── 10c. The two halves must actually touch ─────────────────────────────
   A gap on the group separated the label AND split the rocker down the
   middle, so it read as two adjacent buttons. Zero the group gap and space
   the label on its own, so the halves meet on a single seam — which is the
   line the part appears to pivot about. */
.tt-timer__legendgroup {
  gap: 0 !important;
}
.tt-timer__grouplabel {
  margin-inline-end: var(--space-3, 0.75rem) !important;
}
/* the seam */
.tt-timer__legendgroup .chip:nth-last-of-type(1) {
  border-inline-start: 1px solid rgba(46, 40, 30, 0.14) !important;
}

/* ── 11. HEIGHT BUDGET ───────────────────────────────────────────────────
   Making the transport round and the control row into real hardware added
   56px to the stack, which broke the no-page-scroll rule at every breakpoint.
   The rule is not negotiable — you must never scroll to reach start — so the
   parts give the height back. 44px is the floor: that is the touch-target
   minimum and nothing here may go under it. */
.tt-timer__transportside .btn {
  min-inline-size: 2.75rem !important; /* 44px exactly */
  min-block-size: 2.75rem !important;
  font-size: 0.8125rem !important;
}

.btn--lg.btn--primary,
.tt-timer__primary {
  padding-block: 0.6875rem !important;
  min-block-size: 2.75rem !important;
}

.tt-timer__legend {
  gap: var(--space-3, 0.75rem) !important;
  row-gap: var(--space-2, 0.5rem) !important;
}

.tt-timer__legendgroup .chip,
.chip[aria-pressed] {
  padding-block: 0.4375rem !important;
  min-block-size: 2.125rem !important;
}

/* ── 12. NOTE ────────────────────────────────────────────────────────────
   An earlier revision scoped the switch as `.chip[aria-pressed], .btn[aria-pressed]`.
   That matched `.focus-exit` — a chrome button hidden until focus mode is on —
   and the rule's `display: inline-flex !important` overrode its hiding, putting
   a stray 44px button below the shell and breaking the no-scroll rule.

   Fixed at source above: the switch is `.chip[aria-pressed]` only. `repeat` is
   a chip, so nothing is lost. Note also that `display: revert` is the WRONG
   repair here — it reverts past the author origin to the UA default
   (`inline-block`), discarding the app's own `display: none`. The fix for an
   over-broad selector is to narrow it, never to fight it downstream. */

/* ── 13. THE DRAWER ──────────────────────────────────────────────────────
   The tactile control sizing (44px minimums, roomier padding) is right on the
   instrument, where there is space and where you are pressing things with a
   thumb. Inside a 380px drawer it is wrong: the tablist measured 416px in a
   380px panel and overflowed by 36px, which is what made the drawer look
   broken.

   The drawer is a different context — denser, read more than pressed — so it
   gets its own metrics. Touch targets stay at 44px where you actually tap. */

.tt-panel__clip {
  /* nothing may escape the drawer wall */
  overflow-x: hidden;
}

/* Tabs fill the width they are given and share it evenly, rather than sizing
   to their content and pushing past the edge. */
.tt-panel [role="tablist"] {
  display: flex !important;
  inline-size: 100% !important;
  max-inline-size: 100% !important;
  box-sizing: border-box !important;
  gap: 2px !important;
}
.tt-panel [role="tab"] {
  flex: 1 1 0 !important;
  min-inline-size: 0 !important;
  padding-inline: 0.5rem !important;
  font-size: 0.6875rem !important;
  white-space: nowrap !important;
  overflow: hidden !important;
  text-overflow: ellipsis !important;
}

/* Cards get real gutters instead of touching the drawer wall. */
.tt-panel .module,
.tt-panel .tt-module,
.tt-panel .panel {
  box-sizing: border-box !important;
  max-inline-size: 100% !important;
}

/* Controls inside the drawer: same hardware language, tighter metrics. */
.tt-panel .btn,
.tt-panel .chip {
  min-block-size: 2.25rem !important;
  padding-block: 0.4375rem !important;
  padding-inline: 0.75rem !important;
  font-size: 0.8125rem !important;
}

/* Fields and their trailing units must share a row without clipping the unit.
   "MIN" was being pushed past the wall by a field that would not shrink. */
.tt-panel .field,
.tt-panel input[type="text"],
.tt-panel input[type="number"] {
  min-inline-size: 0 !important;
  max-inline-size: 100% !important;
  box-sizing: border-box !important;
}
.tt-panel .module > *,
.tt-panel .tt-module > * {
  min-inline-size: 0 !important;
}

/* The drawer's own round chrome keys stay round but shrink to fit the header
   beside three tabs. */
.tt-panel .btn--icon {
  min-inline-size: 2.25rem !important;
  min-block-size: 2.25rem !important;
  flex: 0 0 auto !important;
}

/* Anything that lays out in a row inside the drawer wraps rather than
   overflowing — a drawer is tall, not wide. */
.tt-panel .module,
.tt-panel .tt-module {
  overflow-wrap: anywhere;
}

/* ── 15. LANDSCAPE — composed ───────────────────────────────────────────
   Earlier attempts (retired to docs/retired/) pinned the dial absolutely and
   inset everything else. That left three different left edges and a readout
   collapsed to ZERO width, trapped in a stage box the dial had abandoned.

   A later attempt used `display: contents` to dissolve `.tt-timer__stage` and
   promote its children to grid items. That broke badly: the dial's SVG sizes
   against the stage box, so removing the box let the artwork blow out to
   roughly 3x and overlap the whole panel. Worth recording — `display:
   contents` is not free when a descendant depends on the dissolved box for
   its own sizing.

   THE DESIGN. Keep the stage intact and treat it as ONE unit: the dial with
   its numeric echo directly beneath it, which is the relationship they
   already have and the one that reads correctly. That unit takes the left
   column. Every control shares a single alignment axis in the right column,
   with the transport pinned to the bottom where your thumbs are when the
   phone is on its side. One big element left, controls right — the SK/RT20.  */
/* ── RETIRED: the landscape-phone block ────────────────────────────────────
   Everything that used to live here shrank the PORTRAIT layout until it fitted
   a landscape phone: a smaller dial, a shorter chassis, tighter transport keys.
   Landscape no longer runs that layout. It runs its own instrument — the
   full-bleed linear gauge in src/bar.js — and css/bar.css §2 owns the shell
   state that goes with it.

   These rules were not merely redundant, they were actively in the way: they
   were written as `html body .tt-timer__stage`, which out-specifies anything
   css/bar.css can say about the same element no matter what order the
   stylesheets load in. Overriding them was not an option; retiring them was.

   See docs/superpowers/specs/2026-08-25-landscape-face-design.md.
   ───────────────────────────────────────────────────────────────────────── */

/* ── 16. MAKE THE MATERIAL VISIBLE ──────────────────────────────────────
   The textures were loading and compositing correctly, but a veil of the base
   colour sat over them at 90–95%, so only 5–10% of the material reached the
   screen. That was my own instruction ("if you can consciously see the
   texture, it is too strong") and it was wrong for this brief: the ask was to
   generate real material and make the thing feel like an object, and material
   you cannot see does not do that.

   Opened up so the grain actually reads, while staying well short of the
   point where it competes with the type. */
:root,
[data-theme="ceramic"],
[data-theme="graphite"] {
  --m-veil-ground: 80% !important;  /* the surface the object sits on */
  --m-veil-tile: 76% !important;    /* the moulded body */
  --m-veil-metal: 84% !important;   /* brushed parts */
}

/* ── 17. LOWERCASE LEGENDS ──────────────────────────────────────────────
   Braun sets its control legends in small lowercase — "höhen / tiefen /
   volumen / ein aus / phono" on the SK/RT20, "sharp / broad / dial light" on
   the receiver. Tracked-out uppercase is for section headers, not for the
   label on a part. (This lived in the retired phase6.css; it was the one
   thing in that file worth keeping.) */
.btn,
.chip,
.segmented__key,
.tt-timer__transport .btn,
.set-seg__opt {
  text-transform: lowercase !important;
  letter-spacing: 0.005em !important;
}

/* Section headers keep their tracked caps — that contrast is the hierarchy. */
.tt-timer__grouplabel,
.set-seg__legend,
.settings__head,
.tt-panel__head,
[role="tab"] {
  text-transform: uppercase !important;
  letter-spacing: 0.14em !important;
}

/* ── 17b. The mode switch is a control, not a header ─────────────────────
   clock / timer / alarm are legends on a part you press, so they take the
   lowercase treatment like every other control. Only the drawer's tabs and
   the section legends keep tracked-out caps. */
.segmented__key,
.modes [role="tab"] {
  text-transform: lowercase !important;
  letter-spacing: 0.005em !important;
}
.tt-panel [role="tab"] {
  text-transform: uppercase !important;
  letter-spacing: 0.14em !important;
}

/* ── 18. GRAPHITE: damp the highlight ───────────────────────────────────
   The domed highlight was calibrated on the ceramic panel and reused verbatim
   in graphite, where it blew out — reset/+1/+5 and the toggle thumb read as
   glossy white pearls, and the labels on them washed out.

   That is a physical error, not just a taste one: a dark matte surface has a
   low albedo and reflects far less light than a pale one. Same room, same
   lamp, much weaker highlight. So the light stays where it is and the
   MATERIAL's response to it changes — which is also why the damping belongs
   on the theme rather than on src/light.js.

   --hl-mult scales the highlight per material. light.js writes --hl-alpha
   inline on :root, and an inline custom property cannot be overridden from a
   stylesheet, so the theme multiplies it at the point of use instead. */
:root,
[data-theme="ceramic"] {
  --hl-mult: 1;
}
[data-theme="graphite"] {
  --hl-mult: 0.26;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="ceramic"]) {
    --hl-mult: 0.26;
  }
}

/* Keys */
[data-theme="graphite"] .btn,
[data-theme="graphite"] .chip,
[data-theme="graphite"] .segmented__key[aria-selected="true"],
[data-theme="graphite"] .tt-timer__legendgroup .chip:not(.is-selected),
:root:not([data-theme="ceramic"]) .btn,
:root:not([data-theme="ceramic"]) .chip {
  background-image: radial-gradient(
    118% 118% at calc(50% + var(--hl-x)) calc(50% + var(--hl-y)),
    rgba(255, 252, 246, calc(var(--hl-alpha) * var(--hl-mult) * 0.9)) 0%,
    transparent 62%
  ) !important;
}

/* The red key keeps a little more, because a saturated surface still catches
   light — but nowhere near the ceramic value. */
[data-theme="graphite"] .btn--primary,
:root:not([data-theme="ceramic"]) .btn--primary {
  background-image: radial-gradient(
    130% 130% at calc(50% + var(--hl-x)) calc(50% + var(--hl-y)),
    rgba(255, 255, 255, calc(var(--hl-alpha) * var(--hl-mult) * 1.6)) 0%,
    transparent 60%
  ) !important;
}

/* The switch thumb is a small pale part on a dark panel: it stays lighter
   than its surroundings, but it is matte, not a pearl. */
[data-theme="graphite"] .switch__thumb,
:root:not([data-theme="ceramic"]) .switch__thumb {
  background-color: var(--ramp-400) !important;
  background-image: radial-gradient(
    124% 124% at calc(50% + var(--hl-x)) calc(50% + var(--hl-y)),
    rgba(255, 252, 246, calc(var(--hl-alpha) * var(--hl-mult) * 1.2)) 0%,
    transparent 66%
  ) !important;
}
[data-theme="graphite"] .chip[aria-pressed]::after,
:root:not([data-theme="ceramic"]) .chip[aria-pressed]::after {
  background-image: radial-gradient(
    circle at calc(var(--sw-pad) + (var(--sw-h) - var(--sw-pad) * 2) / 2) 50%,
    var(--ramp-500) 0,
    var(--ramp-400) calc((var(--sw-h) - var(--sw-pad) * 2) / 2 - 1.5px),
    transparent calc((var(--sw-h) - var(--sw-pad) * 2) / 2)
  ) !important;
}

/* Labels must clear their own key. With the dome damped these read cleanly,
   but pin them so a future light change cannot wash them out again. */
[data-theme="graphite"] .btn,
[data-theme="graphite"] .chip,
:root:not([data-theme="ceramic"]) .btn,
:root:not([data-theme="ceramic"]) .chip {
  color: var(--ink) !important;
}
[data-theme="graphite"] .btn--primary,
:root:not([data-theme="ceramic"]) .btn--primary {
  color: var(--on-signal) !important;
}
/* The unselected half of the rocker is a real label, not a disabled one. */
[data-theme="graphite"] .tt-timer__legendgroup .chip:not(.is-selected),
:root:not([data-theme="ceramic"]) .tt-timer__legendgroup .chip:not(.is-selected) {
  color: var(--ink-2) !important;
}

/* ── 19. LANDSCAPE: the timer is the largest thing on the page ───────────
   At 46vh the dial was 179px on an 844x390 phone — smaller than the header
   plus the transport combined, which is the wrong hierarchy for an app whose
   entire premise is a disc you look at.

   Height is the scarce axis in landscape, so it gets taken from the chrome
   and given to the instrument:
     · the header loses its padding and its keys shrink to the 32px that a
       secondary control can afford (the transport keeps its 44px floor)
     · the day ribbon thins to a band — still readable, a third of the height
     · the readout and status tighten under the dial
   Everything reclaimed goes to the dial. */

/* ── 19b. Size the dial to the space that actually exists ────────────────
   64vh made the dial 250px, but the left column also carries the readout and
   status (~46px), so the stage came to ~300px inside a ~248px row and
   centring pushed it up THROUGH the header. Bigger is not better if it
   overlaps the chrome.

   The honest budget: viewport − header − day ribbon − padding, minus what the
   readout and status cost. That lands at 54vh, which is still 3.5x the header
   and comfortably the largest element on the page. */

/* ── 20. THE SESSION-LABEL NUDGE ────────────────────────────────────────
   Quiet by construction: it sits under the field, uses micro type, offers
   recent labels as one-tap chips, and removes itself after 12 seconds. It
   never blocks the timer and never covers a control. */
.tt-tasknudge {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem;
  margin-block-start: 0.375rem;
  animation: tt-nudge-in 180ms cubic-bezier(0.2, 0, 0.2, 1) both;
}
.tt-tasknudge__msg {
  color: var(--ink-3);
  text-transform: lowercase;
  letter-spacing: 0.02em;
  margin-inline-end: 0.125rem;
}
.tt-tasknudge__chip {
  font: inherit;
  font-size: 0.6875rem;
  text-transform: lowercase;
  color: var(--ink-2);
  background-color: var(--ramp-200);
  border: 0;
  border-radius: 999px;
  padding: 0.25rem 0.625rem;
  min-block-size: 1.75rem;
  cursor: pointer;
  box-shadow: 0 0 0 1px rgba(46, 40, 30, 0.09);
}
.tt-tasknudge__chip:hover { background-color: var(--ramp-250); }
.tt-tasknudge__dismiss {
  font: inherit;
  line-height: 1;
  color: var(--ink-3);
  background: transparent;
  border: 0;
  cursor: pointer;
  padding: 0.25rem 0.375rem;
  min-inline-size: 1.75rem;
  min-block-size: 1.75rem;
}
[data-theme="graphite"] .tt-tasknudge__chip {
  background-color: var(--ramp-050);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5);
}

@keyframes tt-nudge-in {
  from { opacity: 0; transform: translateY(-2px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .tt-tasknudge { animation: none; }
}

/* Sideways, the field is hidden entirely, so the nudge has nowhere to live
   and stays silent — by design, not by accident. */

/* ══════════════════════════════════════════════════════════════════════════
   21. TALL CAPS, SOFT SHADOWS, NO BORDERS
   Refs: docs/reference/rams-palette-tall-caps.jpg   (the governing one)
         docs/reference/braun-bc02-abloh-tonal.jpg
         docs/reference/braun-phase3-clocks.jpg

   What those objects do that we were not doing:

   1. THE CAPS ARE TALL. Each button on the palette board is a cylinder seen
      from slightly above — you can read its side wall. Ours were flat pads.
      A cap is built from a face plus a wall: `0 Npx 0 <darker>` renders the
      side, and pressing collapses it. That collapse IS the travel, and it is
      why a tall cap feels like a switch and a flat one feels like a rectangle.

   2. NO BORDERS. Not one hairline ring anywhere on those objects. We had
      `0 0 0 1px` on nearly every control; every one is removed here. Contrast
      comes from form and shadow, never from a drawn line.

   3. SHADOWS ARE SOFT AND LONG, not tight contact crescents. Two stacked
      blurs, low opacity, offset down — the object sits in a lit room, not
      under a hard lamp.

   4. TONAL. The BC02 is one hue throughout: case, face, hands, hub. Contrast
      between neighbouring parts is small; form carries the hierarchy.

   5. MINIMAL LABELS. Tiny, quiet, above the part.
   ══════════════════════════════════════════════════════════════════════════ */

:root,
[data-theme="ceramic"] {
  /* Warmer, greener greige — the palette board's panel, not our near-white. */
  --panel-tone: #e7e6de;
  --key-face: var(--cap-mid);
  --key-wall: #cfcdc2;   /* the cylinder's side, in shade */
  --key-face-press: #eeece5;
  --cast-1: rgba(58, 54, 44, 0.13);
  --cast-2: rgba(58, 54, 44, 0.08);
}
[data-theme="graphite"] {
  --panel-tone: var(--ramp-200);
  --key-face: #2a2a2d;
  --key-wall: #141416;
  --key-face-press: #242427;
  --cast-1: rgba(0, 0, 0, 0.5);
  --cast-2: rgba(0, 0, 0, 0.32);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="ceramic"]) {
    --panel-tone: var(--ramp-200);
    --key-face: #2a2a2d;
    --key-wall: #141416;
    --key-face-press: #242427;
    --cast-1: rgba(0, 0, 0, 0.5);
    --cast-2: rgba(0, 0, 0, 0.32);
  }
}

/* The ground the object sits on. */
body {
  background-color: var(--panel-tone) !important;
}

/* ── The tall cap ──────────────────────────────────────────────────────── */
.btn,
.chip:not([aria-pressed]),
.set-danger__btn {
  --cap: 4px;                       /* how tall the cylinder reads */
  background-color: var(--key-face) !important;
  background-image: none !important;   /* tonal: no dome gradient */
  border: 0 !important;
  border-radius: 12px !important;
  color: var(--ink) !important;
  box-shadow:
    /* the side wall */
    0 var(--cap) 0 var(--key-wall),
    /* soft, long, low — a lit room */
    0 calc(var(--cap) + 4px) 10px var(--cast-1),
    0 calc(var(--cap) + 10px) 22px var(--cast-2) !important;
  transition:
    transform 110ms cubic-bezier(0.2, 0, 0.2, 1),
    box-shadow 110ms cubic-bezier(0.2, 0, 0.2, 1),
    background-color 110ms linear;
}

/* Round caps stay round — the palette board's are circles. */
.btn--icon,
.btn--round,
.tt-timer__transportside .btn {
  border-radius: 50% !important;
}

/* Pressing collapses the wall. The cap travels its own height and the shadow
   tightens because the gap it was casting across has closed. */
.btn:active,
.chip:not([aria-pressed]):active,
.set-danger__btn:active {
  background-color: var(--key-face-press) !important;
  transform: translateY(var(--cap)) !important;
  box-shadow:
    0 0 0 var(--key-wall),
    0 2px 5px var(--cast-1) !important;
}

/* The one signal key: same construction, its own pigment. */
.btn--primary,
.btn--lg.btn--primary {
  --cap: 5px;
  background-color: var(--signal-key) !important;
  background-image: none !important;
  color: var(--on-signal) !important;
  box-shadow:
    0 var(--cap) 0 color-mix(in oklab, var(--signal-key) 62%, #000),
    0 calc(var(--cap) + 4px) 12px var(--cast-1),
    0 calc(var(--cap) + 10px) 24px var(--cast-2) !important;
}
.btn--primary:active {
  transform: translateY(var(--cap)) !important;
  box-shadow:
    0 0 0 color-mix(in oklab, var(--signal-key) 62%, #000),
    0 2px 6px var(--cast-1) !important;
}

/* Quiet controls carry no body at all. */
.btn--quiet,
.btn--plain {
  background-color: transparent !important;
  box-shadow: none !important;
  transform: none !important;
}
.btn--quiet:active,
.btn--plain:active { transform: none !important; }

/* ── No borders. Anywhere. ─────────────────────────────────────────────── */
.panel,
.module,
.tt-module,
.tt-panel,
.tut__card,
.settings__panel,
.chassis,
.field,
input[type="text"],
input[type="search"] {
  border: 0 !important;
}
.panel,
.module,
.tt-module,
.settings__panel,
.tut__card {
  border-radius: 16px !important;
  box-shadow: 0 2px 10px var(--cast-2), 0 8px 28px var(--cast-2) !important;
}

/* Fields are cut in: a soft tonal well, no ring. */
.field,
input[type="text"],
input[type="search"] {
  background-color: color-mix(in oklab, var(--panel-tone) 92%, #000) !important;
  border-radius: 12px !important;
  box-shadow: inset 0 2px 5px var(--cast-1) !important;
}

/* ── The mode switch: a tonal channel, no ring, no seams ───────────────── */
.segmented {
  background-color: color-mix(in oklab, var(--panel-tone) 94%, #000) !important;
  border: 0 !important;
  border-radius: 14px !important;
  padding: 4px !important;
  box-shadow: inset 0 2px 5px var(--cast-1) !important;
}
.segmented__key {
  border: 0 !important;
  border-right: 0 !important;
  border-radius: 11px !important;
  background-color: transparent !important;
  background-image: none !important;
  box-shadow: none !important;
  transform: none !important;
}
.segmented__key[aria-selected="true"],
.segmented__key[aria-pressed="true"],
.segmented__key.is-active {
  background-color: var(--key-face) !important;
  box-shadow: 0 2px 0 var(--key-wall), 0 5px 12px var(--cast-1) !important;
}

/* ── The face rocker: two tall caps sharing a channel, no seam line ────── */
.tt-timer__legendgroup .chip {
  border: 0 !important;
  border-inline-start: 0 !important;
  border-radius: 11px !important;
}
.tt-timer__legendgroup .chip:not(.is-selected) {
  background-color: var(--key-face) !important;
  background-image: none !important;
  box-shadow: 0 3px 0 var(--key-wall), 0 7px 14px var(--cast-1) !important;
}
.tt-timer__legendgroup .chip.is-selected {
  background-color: color-mix(in oklab, var(--panel-tone) 94%, #000) !important;
  background-image: none !important;
  box-shadow: inset 0 2px 5px var(--cast-1) !important;
}

/* ── The toggle: tonal channel, tall thumb, no ring ────────────────────── */
.chip[aria-pressed]::after,
.switch__track {
  background-color: color-mix(in oklab, var(--panel-tone) 92%, #000) !important;
  box-shadow: inset 0 2px 4px var(--cast-1) !important;
}
.switch__thumb {
  background-color: var(--key-face) !important;
  background-image: none !important;
  box-shadow: 0 2px 0 var(--key-wall), 0 5px 10px var(--cast-1) !important;
}

/* ── Minimal labels ────────────────────────────────────────────────────── */
.tt-timer__grouplabel,
.set-seg__legend {
  font-size: 0.5625rem !important;
  letter-spacing: 0.16em !important;
  color: var(--ink-3) !important;
}

/* ── The chassis: a soft moulded case, generous radius, no border ──────── */
.chassis {
  background-color: transparent !important;
  border-radius: 20px !important;
  box-shadow: none !important;
}

/* ── 21b. Tonal corrections ─────────────────────────────────────────────
   Three things still read as drawn rather than moulded:

   · the session field was a dark slab — on the BC02 a recess is barely
     darker than its surround, and the shadow does the work, not the fill
   · the brand still carried the old opaque nameplate, which reads as a box
     around the wordmark now that nothing runs behind it
   · the dial's bezel was a heavy grey ring; the Phase 3 and BC02 bezels are
     the SAME tone as the case, separated only by a soft shadow */

/* The recess is tonal — only a hair darker, with the shadow carrying it. */
.field,
input[type="text"],
input[type="search"] {
  background-color: color-mix(in oklab, var(--panel-tone) 97%, #000) !important;
  box-shadow: inset 0 2px 6px var(--cast-1) !important;
}
.field::placeholder,
input::placeholder { color: var(--ink-3) !important; opacity: 1 !important; }

/* The wordmark sits on the case, not on a plate. */
.brand,
.brand::before,
.brand::after {
  background: none !important;
  background-color: transparent !important;
  background-image: none !important;
  box-shadow: none !important;
}

/* The mode channel is tonal too — it was reading as a grey bar. */
.segmented {
  background-color: color-mix(in oklab, var(--panel-tone) 97%, #000) !important;
}

/* The bezel takes the case's own tone; the shadow separates it, not a line. */
.dial__bezel,
.dial__rim,
.dial__rim--out,
.dial__track {
  stroke: color-mix(in oklab, var(--panel-tone) 88%, #000) !important;
  stroke-opacity: 0.55 !important;
}
.dial {
  background-color: transparent !important;
}
.dial__plate {
  fill: color-mix(in oklab, var(--panel-tone) 96%, #fff) !important;
}

/* ══════════════════════════════════════════════════════════════════════════
   22. LIGHT / DARK / SYSTEM — contrast is not negotiable

   A dark-mode audit found the readout at 1.06:1 — text effectively invisible.
   Its computed colour was the CERAMIC ink while its parent carried the
   graphite ink, so something upstream was pinning it to a light-theme value
   that never flipped.

   Rather than keep hunting the source, the theme-critical text is pinned to
   tokens here. A token cannot fail to flip: it is redefined by every theme
   block AND by the system media query, so all three modes resolve correctly
   by construction.

   Also raising the tonal separations. The 97% color-mixes from §21 were
   deliberately subtle, which is right for a moulded surface and wrong for a
   recess you need to SEE. Low-contrast light grey is the specific failure
   mode called out, so recesses now sit further from their surround and the
   tertiary ink is lifted off the panel.
   ══════════════════════════════════════════════════════════════════════════ */

/* --- theme-critical text, pinned so it cannot drift ------------------- */
.tt-timer__readout,
.t-display,
.tt-timer__stage,
.tt-timer,
.mode-panel {
  color: var(--ink) !important;
}
.tt-timer__meta,
.tt-timer__grouplabel,
.set-seg__legend,
[class*="daystrip"] {
  color: var(--ink-2) !important;
}

/* --- recesses you can actually see ------------------------------------ */
:root,
[data-theme="ceramic"] {
  --recess: var(--bg-sunk);          /* a real step down from the panel, not a hint */
  --recess-ink: var(--muted-ink);      /* tertiary that clears AA on the panel */
}
[data-theme="graphite"] {
  --recess: #08080a;
  --recess-ink: var(--muted-ink);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="ceramic"]) {
    --recess: #08080a;
    --recess-ink: var(--muted-ink);
  }
}

.field,
input[type="text"],
input[type="search"],
.segmented,
.tt-timer__legendgroup .chip.is-selected,
.chip[aria-pressed]::after,
.switch__track {
  background-color: var(--recess) !important;
}

/* Micro type sits on the panel, so it needs its own ink — not a light grey. */
.t-micro,
.tt-tasknudge__msg,
.set-seg__hint {
  color: var(--recess-ink) !important;
}

/* Placeholders are the classic low-contrast offender. */
.field::placeholder,
input::placeholder,
textarea::placeholder {
  color: var(--recess-ink) !important;
  opacity: 1 !important;
}

/* --- SYSTEM MODE: the graphite treatment must apply without the attribute -
   Everything §21 defined under [data-theme="graphite"] is repeated for the
   system media query, so "follow the OS" is a first-class mode rather than a
   half-styled fallback. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="ceramic"]) body { background-color: var(--panel-tone) !important; }
  :root:not([data-theme="ceramic"]) .btn,
  :root:not([data-theme="ceramic"]) .chip:not([aria-pressed]),
  :root:not([data-theme="ceramic"]) .segmented__key[aria-selected="true"],
  :root:not([data-theme="ceramic"]) .tt-timer__legendgroup .chip:not(.is-selected) {
    background-color: var(--key-face) !important;
    color: var(--ink) !important;
  }
  :root:not([data-theme="ceramic"]) .btn--primary { color: var(--on-signal) !important; }
}

/* --- DEFERRED: cross-fading between themes -----------------------------
   A colour transition between light and dark moves the TEXT and its
   BACKGROUND through mid-grey at the same time. For part of that window the
   two converge and the readout becomes unreadable — a genuine accessibility
   hazard, not a cosmetic one, and worst for exactly the users this app is
   for. Any future implementation has to hold the ink at full contrast and
   move only the surface, or cut at a point where they cannot meet.
   Deliberately not implemented. The theme switch is instant. */

/* ── 22b. The readout, settled ──────────────────────────────────────────
   In graphite the readout computed to rgb(26,25,23) — the CERAMIC ink —
   while its own --ink token resolved correctly to the light ink and its parent
   rendered light. So a declaration with higher precedence than a plain
   `!important` on a single class was pinning it, and it did not surface in a
   styleSheets scan.

   Rather than keep hunting: raise the specificity past anything a single
   class rule can carry, and use literals so the value cannot depend on a
   token that something else may have shadowed. Blunt, but a timer whose
   digits are invisible in dark mode is not a defect worth being elegant about.

   Measured after: 1.06:1 → 15.0:1. */
html[data-theme="graphite"] body .tt-timer__readout,
html[data-theme="graphite"] body .tt-timer__readout *,
html[data-theme="graphite"] body .t-display {
  color: var(--ink) !important;
  -webkit-text-fill-color: var(--ink) !important;
}
html[data-theme="ceramic"] body .tt-timer__readout,
html[data-theme="ceramic"] body .tt-timer__readout *,
html[data-theme="ceramic"] body .t-display {
  color: var(--ink) !important;
  -webkit-text-fill-color: var(--ink) !important;
}
@media (prefers-color-scheme: dark) {
  html:not([data-theme="ceramic"]) body .tt-timer__readout,
  html:not([data-theme="ceramic"]) body .tt-timer__readout *,
  html:not([data-theme="ceramic"]) body .t-display {
    color: var(--ink) !important;
    -webkit-text-fill-color: var(--ink) !important;
  }
}

/* The signal key's label was 3.67:1 in graphite — below AA. In dark mode the
   signal is lighter (#ff4d1f), so WHITE no longer clears it; a dark glyph
   does, which is also what the ET66 does with its yellow key. */
html[data-theme="graphite"] body .btn--primary,
html[data-theme="graphite"] body .btn--lg.btn--primary {
  color: #14090a !important;
  -webkit-text-fill-color: #14090a !important;
}
@media (prefers-color-scheme: dark) {
  html:not([data-theme="ceramic"]) body .btn--primary {
    color: #14090a !important;
    -webkit-text-fill-color: #14090a !important;
  }
}

/* ── 23. DARK MODE: material tokens that cannot lose a specificity tie ───
   In graphite the key caps rendered LIGHT grey, so `reset` / `+1` / `+5` and
   the unselected rocker half had near-invisible labels.

   Cause: my light block was written `:root, [data-theme="ceramic"]`. `:root`
   matches <html> whatever its data-theme, and it carries the same (0,1,0)
   specificity as `[data-theme="graphite"]` — so the two tie on every dark
   page and the winner is decided by source order alone. Any later edit that
   moved a block flipped the theme. Fragile by construction.

   Fixed by qualifying the dark selectors as `html[data-theme=...]` (0,2,0),
   which outranks a bare `:root` outright and no longer depends on ordering. */
html[data-theme="graphite"] {
  --panel-tone: var(--ramp-200);
  --key-face: #2a2a2d;
  --key-wall: var(--ramp-200);
  --key-face-press: #232326;
  --recess: #0a0a0c;
  --recess-ink: #a5a29b;
  --cast-1: rgba(0, 0, 0, 0.55);
  --cast-2: rgba(0, 0, 0, 0.34);
  --hl-mult: 0.26;
}
@media (prefers-color-scheme: dark) {
  html:not([data-theme="ceramic"]) {
    --panel-tone: var(--ramp-200);
    --key-face: #2a2a2d;
    --key-wall: var(--ramp-200);
    --key-face-press: #232326;
    --recess: #0a0a0c;
    --recess-ink: #a5a29b;
    --cast-1: rgba(0, 0, 0, 0.55);
    --cast-2: rgba(0, 0, 0, 0.34);
    --hl-mult: 0.26;
  }
}

/* The caps and their labels, pinned at the same specificity. */
html[data-theme="graphite"] body .btn,
html[data-theme="graphite"] body .chip:not([aria-pressed]),
html[data-theme="graphite"] body .segmented__key[aria-selected="true"],
html[data-theme="graphite"] body .tt-timer__legendgroup .chip:not(.is-selected),
html[data-theme="graphite"] body .switch__thumb {
  background-color: #2a2a2d !important;
  background-image: none !important;
  color: var(--ink) !important;
  -webkit-text-fill-color: var(--ink) !important;
}
html[data-theme="graphite"] body .btn--primary {
  background-color: var(--signal-key, #d4330c) !important;
  color: #14090a !important;
  -webkit-text-fill-color: #14090a !important;
}
/* Inactive mode keys sit on the channel and must still be readable. */
html[data-theme="graphite"] body .segmented__key:not([aria-selected="true"]) {
  color: #b8b5ad !important;
  -webkit-text-fill-color: #b8b5ad !important;
}
/* Recesses stay clearly darker than the caps that sit in them. */
html[data-theme="graphite"] body .segmented,
html[data-theme="graphite"] body .field,
html[data-theme="graphite"] body input[type="text"],
html[data-theme="graphite"] body .tt-timer__legendgroup .chip.is-selected,
html[data-theme="graphite"] body .switch__track,
html[data-theme="graphite"] body .chip[aria-pressed]::after {
  background-color: #0a0a0c !important;
}

@media (prefers-color-scheme: dark) {
  html:not([data-theme="ceramic"]) body .btn,
  html:not([data-theme="ceramic"]) body .chip:not([aria-pressed]),
  html:not([data-theme="ceramic"]) body .segmented__key[aria-selected="true"],
  html:not([data-theme="ceramic"]) body .tt-timer__legendgroup .chip:not(.is-selected),
  html:not([data-theme="ceramic"]) body .switch__thumb {
    background-color: #2a2a2d !important;
    background-image: none !important;
    color: var(--ink) !important;
    -webkit-text-fill-color: var(--ink) !important;
  }
  html:not([data-theme="ceramic"]) body .btn--primary {
    color: #14090a !important;
    -webkit-text-fill-color: #14090a !important;
  }
  html:not([data-theme="ceramic"]) body .segmented,
  html:not([data-theme="ceramic"]) body .field,
  html:not([data-theme="ceramic"]) body .tt-timer__legendgroup .chip.is-selected,
  html:not([data-theme="ceramic"]) body .switch__track {
    background-color: #0a0a0c !important;
  }
}

/* ── 23b. Pin LIGHT symmetrically ───────────────────────────────────────
   §23 pinned the dark material tokens at `html[data-theme="graphite"]`
   (0,2,0) but left light on a bare `:root` (0,1,0). On a machine whose OS is
   in dark mode, a `prefers-color-scheme` block then outranked the light
   theme and an explicitly-chosen ceramic rendered with DARK key caps.

   An explicit choice must beat the system preference in both directions, so
   ceramic gets the same html-qualified treatment. The rule of thumb: if you
   pin one theme, you must pin the other, or the unpinned one is only ever
   correct by accident of source order. */
html[data-theme="ceramic"] {
  --panel-tone: #e7e6de;
  --key-face: var(--cap-mid);
  --key-wall: #cfcdc2;
  --key-face-press: #eeece5;
  --recess: var(--bg-sunk);
  --recess-ink: var(--muted-ink);
  --cast-1: rgba(58, 54, 44, 0.13);
  --cast-2: rgba(58, 54, 44, 0.08);
  --hl-mult: 1;
}
html[data-theme="ceramic"] body .btn,
html[data-theme="ceramic"] body .chip:not([aria-pressed]),
html[data-theme="ceramic"] body .segmented__key[aria-selected="true"],
html[data-theme="ceramic"] body .tt-timer__legendgroup .chip:not(.is-selected),
html[data-theme="ceramic"] body .switch__thumb {
  background-color: var(--cap-mid) !important;
  background-image: none !important;
  color: var(--cap-ink) !important;
  -webkit-text-fill-color: var(--cap-ink) !important;
}
html[data-theme="ceramic"] body .btn--primary {
  background-color: var(--signal-key, #d4330c) !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
}
html[data-theme="ceramic"] body .segmented__key:not([aria-selected="true"]) {
  color: var(--muted-ink) !important;
  -webkit-text-fill-color: var(--muted-ink) !important;
}
html[data-theme="ceramic"] body .segmented,
html[data-theme="ceramic"] body .field,
html[data-theme="ceramic"] body input[type="text"],
html[data-theme="ceramic"] body .tt-timer__legendgroup .chip.is-selected,
html[data-theme="ceramic"] body .switch__track,
html[data-theme="ceramic"] body .chip[aria-pressed]::after {
  background-color: var(--bg-sunk) !important;
}
html[data-theme="ceramic"] body {
  background-color: #e7e6de !important;
}

/* ══════════════════════════════════════════════════════════════════════════
   24. DESKTOP IS ITS OWN INSTRUMENT

   Measured at 1680x1050: the shell was 1088px wide — 296px of dead space on
   EACH side, 65% of the canvas used, everything capped at 1040px. That is the
   phone layout stretched and centred, which is what "not designed for desktop"
   means concretely. The horizontal axis was doing no work at all.

   The fix is the same principle that fixed landscape phone: use the axis you
   actually have. And on a desktop the Panel should not be an overlay drawer —
   there is room for the instrument AND the information at the same time, so
   the drawer becomes a permanent right-hand column. That removes a click from
   every glance at your history, and it is how a desk instrument with a chart
   recorder beside it would actually be laid out.

        ┌──────────────────────────────────────────────┐
        │ header                                        │
        ├─────────────────────────┬────────────────────┤
        │      DIAL (large)       │  TRACK/SHAPE/DAY   │
        │        24:59            │  sessions          │
        │   label · controls      │  estimate          │
        │  reset START +1 +5      │                    │
        ├─────────────────────────┴────────────────────┤
        │ day ribbon                                    │
        └──────────────────────────────────────────────┘
   ══════════════════════════════════════════════════════════════════════════ */

@media (min-width: 75rem) {          /* 1200px and up */
  /* Take the width back. */
  .app-shell {
    max-inline-size: 100rem !important;   /* 1600px */
    inline-size: 100% !important;
  }
  .chassis,
  .app-stage {
    max-inline-size: none !important;
    inline-size: 100% !important;
  }

  /* Two columns: instrument, then the docked panel. */
  .app-stage {
    display: grid !important;
    grid-template-columns: minmax(0, 1fr) 24rem !important;
    column-gap: clamp(1.5rem, 3vw, 3rem) !important;
    align-items: start !important;
  }
  .mode-panel {
    grid-column: 1 !important;
    min-inline-size: 0 !important;
  }

  /* ── Dock the drawer ──────────────────────────────────────────────────
     It is `position: fixed; inset: 0` with a scrim as an overlay. On desktop
     it becomes an ordinary grid item: no scrim, no fixed positioning, no
     transform, always present. */
  .tt-panel {
    position: static !important;
    inset: auto !important;
    display: block !important;
    z-index: auto !important;
    grid-column: 2 !important;
    grid-row: 1 !important;
    pointer-events: auto !important;
    visibility: visible !important;
    opacity: 1 !important;
  }
  .tt-panel__scrim {
    display: none !important;
  }
  .tt-panel__clip {
    position: static !important;
    inset: auto !important;
    inline-size: 100% !important;
    max-inline-size: none !important;
    block-size: auto !important;
    transform: none !important;
    overflow: visible !important;
  }
  .tt-panel__sheet {
    position: static !important;
    transform: none !important;
    inline-size: 100% !important;
    max-inline-size: none !important;
    block-size: auto !important;
    max-block-size: none !important;
    border-radius: 16px !important;
    box-shadow: 0 2px 10px var(--cast-2), 0 8px 28px var(--cast-2) !important;
  }
  /* Its close button is meaningless when it is furniture. */
  .tt-panel__close {
    display: none !important;
  }

  /* The dial gets the room the dead space was wasting. */
  .tt-timer__dial,
  .dial {
    inline-size: min(46vh, 30vw, 460px) !important;
    block-size: min(46vh, 30vw, 460px) !important;
  }
}

/* Very wide screens: a third column would be feature creep, so the panel
   simply gets a little more room and the instrument stays centred in what
   remains rather than drifting to one edge. */
@media (min-width: 100rem) {
  .app-stage {
    grid-template-columns: minmax(0, 1fr) 27rem !important;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   25. REAL PHOTOGRAPHED CAPS

   The round transport keys stop being CSS approximations of a button and
   become an actual photographed part: a matte cylinder shot from overhead,
   its side wall visible along the lower edge, its own soft shadow pooled
   beneath it on the panel.

   The caps were generated on a panel of exactly --panel-tone, so the square
   image tiles seamlessly into the surrounding surface and the shadow reads as
   falling on the real panel rather than sitting in a box. That is why these
   elements carry NO border-radius and NO background-colour of their own — the
   roundness and the ground both come from the photograph.

   assets/parts/cap-light.jpg   assets/parts/cap-signal.jpg   cap-dark.jpg
   ══════════════════════════════════════════════════════════════════════════ */

.tt-timer__transportside .btn,
.btn--round {
  background-position: center !important;
  background-repeat: no-repeat !important;
  background-color: transparent !important;
  border-radius: 0 !important;         /* the photo supplies the shape */
  box-shadow: none !important;         /* the photo supplies the shadow */
  transform: none !important;
  /* the cap fills ~74% of the frame, so the label must sit inside that */
  padding: 0 !important;
  display: inline-grid !important;
  place-items: center !important;
  min-inline-size: 3.25rem !important;
  min-block-size: 3.25rem !important;
  aspect-ratio: 1 / 1 !important;
  color: var(--ink) !important;
  -webkit-text-fill-color: var(--ink) !important;
}

/* Pressing a photographed part: it cannot deform, so it travels and its
   shadow tightens — done by nudging the image down a hair and scaling it
   fractionally, which reads as the cap sinking into the panel. */
.tt-timer__transportside .btn:active,
.btn--round:active {
  background-size: 97% 97% !important;
  background-position: center 54% !important;
  filter: brightness(0.985) !important;
}

html[data-theme="graphite"] body .tt-timer__transportside .btn,
html[data-theme="graphite"] body .btn--round {
  color: var(--ink) !important;
  -webkit-text-fill-color: var(--ink) !important;
}
@media (prefers-color-scheme: dark) {
  html:not([data-theme="ceramic"]) body .tt-timer__transportside .btn,
  html:not([data-theme="ceramic"]) body .btn--round {
    color: var(--ink) !important;
    -webkit-text-fill-color: var(--ink) !important;
  }
}

/* The panel material, photographed. Very low opacity — it is grain, not
   pattern, and if you can see it as an image it is wrong. */
body {
  background-image: url('../assets/textures/ground.jpg') !important;
  background-size: 420px 420px !important;
  background-repeat: repeat !important;
  background-blend-mode: multiply !important;
}
html[data-theme="graphite"] body {
  background-blend-mode: soft-light !important;
}

/* ══════════════════════════════════════════════════════════════════════════
   26. DESKTOP COMPOSITION — not a stretched phone

   The deployed desktop was one centred column pulled down a landscape screen:
   an oversized dial, everything below it squeezed, and the transport jammed
   against the day ribbon at the very bottom edge. Vertical stretch is what a
   phone layout does when you give it a phone's proportions on a desk monitor.

   Desktop gets its own composition:
     · the instrument is a fixed-width block, vertically CENTRED in its column
       rather than stretched to fill it — a desk object sits on a surface, it
       does not grow to the size of the room
     · the dial is sized to sit comfortably beside the docked panel, not to
       dominate a column that has nothing else in it
     · the transport keeps real clearance from the day ribbon
     · the day ribbon spans the instrument column only, since the dock is a
       different plane
   ══════════════════════════════════════════════════════════════════════════ */

@media (min-width: 75rem) {
  /* The instrument is a block on a surface, centred in the space it has. */
  .app-stage {
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
    min-block-size: 0 !important;
  }
  .mode-panel {
    inline-size: 100% !important;
    max-inline-size: 46rem !important;   /* the instrument's own measure */
    margin-inline: auto !important;
  }
  .tt-timer {
    display: flex !important;
    flex-direction: column !important;
    align-items: center !important;
    gap: clamp(0.5rem, 1.4vh, 1.25rem) !important;
    block-size: auto !important;
  }

  /* Proportioned to sit beside the dock, not to fill an empty column. */
  .tt-timer__dial,
  .dial {
    inline-size: min(38vh, 22vw, 380px) !important;
    block-size: min(38vh, 22vw, 380px) !important;
  }
  .tt-timer__readout,
  .t-display {
    font-size: clamp(3rem, 7.5vh, 5rem) !important;
    line-height: 1 !important;
  }

  /* Real clearance above the day ribbon — it was touching. */
  .tt-timer__transport {
    margin-block-end: clamp(1rem, 3vh, 2.5rem) !important;
  }

  /* The ribbon belongs to the instrument, not the dock. */
  [class*="daystrip"] {
    inline-size: auto !important;
    margin-inline-end: 0 !important;
  }
}

/* ── 26b. Desktop corrections ───────────────────────────────────────────
   Three faults from §26, all caused by turning `.tt-timer` into a flex column
   without accounting for `.tt-timer__stage`, which holds the dial AND the
   readout together:

   1. the readout printed ON TOP of the dial — the stage had no layout of its
      own once its parent changed, so its children stacked
   2. the instrument sat off-centre in its column
   3. the ground texture at multiply darkened and greened the panel; a greige
      texture multiplied over a greige panel compounds instead of grains */
@media (min-width: 75rem) {
  .tt-timer__stage {
    display: flex !important;
    flex-direction: column !important;
    align-items: center !important;
    justify-content: flex-start !important;
    gap: clamp(0.5rem, 1.6vh, 1.25rem) !important;
    inline-size: auto !important;
    block-size: auto !important;
    position: static !important;
  }
  .tt-timer__dial,
  .tt-timer__stage > .dial {
    position: static !important;
    flex: 0 0 auto !important;
    margin: 0 !important;
  }
  .tt-timer__readout,
  .tt-timer__meta {
    position: static !important;
    flex: 0 0 auto !important;
    margin: 0 !important;
    text-align: center !important;
  }
  /* centre the instrument in the space the dock leaves */
  .mode-panel,
  .tt-timer {
    margin-inline: auto !important;
  }
}

/* The ground is GRAIN, not a colour layer. Soft-light keeps the panel's own
   value and lets only the texture through; multiply was compounding two
   greiges into a darker, greener surface. */
body {
  background-blend-mode: soft-light !important;
  background-size: 340px 340px !important;
}
html[data-theme="graphite"] body {
  background-blend-mode: overlay !important;
}

/* ── 25b. Caps at theme specificity ─────────────────────────────────────
   §25 set the photographed caps at `.tt-timer__transportside .btn` (0,2,0),
   but §23/§23b pin every key at `html[data-theme=...] body .btn` (0,3,2) with
   `background-image: none`. The theme pin won and the caps never appeared.
   Same specificity, applied per theme, so the photograph survives. */
html[data-theme="ceramic"] body .tt-timer__transportside .btn,
html[data-theme="ceramic"] body .btn--round {
  background-position: center !important;
  background-repeat: no-repeat !important;
  background-color: transparent !important;
  border-radius: 0 !important;
  box-shadow: none !important;
}
html[data-theme="graphite"] body .tt-timer__transportside .btn,
html[data-theme="graphite"] body .btn--round {
  background-position: center !important;
  background-repeat: no-repeat !important;
  background-color: transparent !important;
  border-radius: 0 !important;
  box-shadow: none !important;
}
@media (prefers-color-scheme: dark) {
  html:not([data-theme="ceramic"]) body .tt-timer__transportside .btn,
  html:not([data-theme="ceramic"]) body .btn--round {
    background-color: transparent !important;
    border-radius: 0 !important;
    box-shadow: none !important;
  }
}

/* ── 26c. Give the desktop stage a real measure ─────────────────────────
   `.tt-timer__stage` and `.tt-timer__dial` both measured ZERO width: the dial
   wrapper takes its size from its SVG child, which overflows it, so `auto`
   collapsed. A flex column then centred every child on a zero-width point and
   the readout spilled both ways — printing across the dial and wrapping the
   status line into a narrow ribbon.

   Same failure as landscape, same fix: an explicit measure, so nothing has to
   infer its width from a child that is inferring its width from the parent. */
@media (min-width: 75rem) {
  .tt-timer__stage {
    --instr: min(38vh, 22vw, 380px);
    inline-size: var(--instr) !important;
    max-inline-size: var(--instr) !important;
  }
  .tt-timer__dial,
  .tt-timer__stage > .dial {
    inline-size: var(--instr, min(38vh, 22vw, 380px)) !important;
    block-size: var(--instr, min(38vh, 22vw, 380px)) !important;
  }
  /* The readout and status take the instrument's full measure so they centre
     on the dial's axis instead of on a collapsed point. */
  .tt-timer__readout,
  .tt-timer__meta {
    inline-size: 100% !important;
    max-inline-size: none !important;
    white-space: nowrap !important;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   27. FINISH

   Not more effects — fewer, executed exactly. Everything here is either a
   response to something the user did, or a state they need to perceive.
   Nothing loops, nothing plays unprompted, nothing draws attention it has not
   earned. That restraint is the point: on an app for people who are easily
   pulled off-task, ambient motion is a defect.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Boot: the instrument settles into place, once ──────────────────────
   A single 520ms arrival, staggered so the eye lands on the dial first, then
   the readout, then the controls — the order you actually read them in. It
   never repeats, so it costs attention exactly once per session. */
@media (prefers-reduced-motion: no-preference) {
  @keyframes tt-arrive {
    from { opacity: 0; transform: translateY(6px) scale(0.994); }
    to   { opacity: 1; transform: none; }
  }
  .tt-timer__stage  { animation: tt-arrive 520ms cubic-bezier(0.16, 1, 0.3, 1) both; }
  .tt-timer__legend { animation: tt-arrive 520ms cubic-bezier(0.16, 1, 0.3, 1) 90ms both; }
  .tt-timer__transport { animation: tt-arrive 520ms cubic-bezier(0.16, 1, 0.3, 1) 150ms both; }
  .chassis { animation: tt-arrive 420ms cubic-bezier(0.16, 1, 0.3, 1) both; }
  [class*="daystrip"] { animation: tt-arrive 520ms cubic-bezier(0.16, 1, 0.3, 1) 210ms both; }
}

/* ── Pointer intent ─────────────────────────────────────────────────────
   The dial is the one thing you grab, so it says so. Everything else uses a
   plain pointer: a UI where everything looks draggable teaches nothing. */
.dial,
.dial svg { cursor: grab; }
.dial:active,
.dial.is-scrubbing,
.dial:active svg { cursor: grabbing; }
.btn, .chip, .segmented__key, [role="tab"] { cursor: pointer; }
.tt-timer__readout, .tt-timer__meta { cursor: default; user-select: none; }

/* ── Hover: a part lifts a fraction, and only on devices that hover ─────
   Guarded so a touch device never inherits a sticky hover state after a tap. */
@media (hover: hover) and (pointer: fine) {
  .btn:hover:not(:active) { filter: brightness(1.012); }
  .tt-timer__transportside .btn:hover:not(:active),
  .btn--round:hover:not(:active) { background-size: 102% 102% !important; }
  .segmented__key:not([aria-selected="true"]):hover { color: var(--ink) !important; }
}

/* ── Focus: visible, on-brand, and never removed ────────────────────────
   Two-tone so it reads on both the panel and a key. Offset so it never
   crowds the glyph. */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--signal-key, #d4330c) !important;
  outline-offset: 3px !important;
  border-radius: 4px;
}
.dial:focus-visible {
  outline: 2px solid var(--signal-key, #d4330c) !important;
  outline-offset: 6px !important;
  border-radius: 50%;
}

/* ── The readout: tabular, and it must not jitter ───────────────────────
   The colon is optically heavier than a digit gap, so it gets a hair of
   negative tracking to sit centred between the pairs. */
.tt-timer__readout,
.t-display {
  font-variant-numeric: tabular-nums !important;
  font-feature-settings: "tnum" 1, "zero" 1 !important;
  font-kerning: none;
}

/* ── Selection uses the signal, like everything else that means something */
::selection { background: color-mix(in oklab, var(--signal-key) 26%, transparent); }

/* ── The day ribbon: its now-marker is the only thing that should draw the
   eye, so the track recedes and the elapsed portion is quiet. */
[class*="daystrip"] { transition: opacity 200ms ease; }

/* ── Scrollbars inside the dock, so it does not look like a web page ──── */
.tt-panel__clip { scrollbar-width: thin; scrollbar-color: var(--ramp-400) transparent; }
.tt-panel__clip::-webkit-scrollbar { width: 8px; }
.tt-panel__clip::-webkit-scrollbar-thumb {
  background: var(--ramp-400); border-radius: 99px;
  border: 2px solid transparent; background-clip: content-box;
}
.tt-panel__clip::-webkit-scrollbar-track { background: transparent; }

/* ── Print: the instrument, not the chrome ─────────────────────────────── */
@media print {
  .chassis__tools, .tt-panel, .tt-timer__transport, .tt-tasknudge { display: none !important; }
  body { background: #fff !important; }
}

/* Reduced motion removes the arrival entirely — a considered entrance is
   still an entrance, and someone who asked for less movement meant it. */
@media (prefers-reduced-motion: reduce) {
  .tt-timer__stage, .tt-timer__legend, .tt-timer__transport,
  .chassis, [class*="daystrip"] { animation: none !important; }
}

/* ══════════════════════════════════════════════════════════════════════════
   28. THE DEVICE — a literal metaphor, 80% object / 20% screen

   Until now this was a web page with well-made controls on it. The brief is
   different: it should BE a Rams-designed analog device sitting on a desk.

   That reframes every layer:

     the viewport   = the desk surface (plaster, in shadow)
     .app-shell     = the CASE — a moulded body with real edges, a top
                      highlight where the light catches the lip, and a genuine
                      contact shadow on the desk beneath it
     the chassis    = the device's front panel
     the dial       = a mechanical part set into that panel
     the readout    = THE ONLY SCREEN. A small recessed display window, and
                      the entire 20% digital budget. Everything else is matter.

   The single most important consequence: a screen is a HOLE in an object, so
   it is inset, tinted, and slightly cooler than the warm body around it. That
   contrast — warm matte body, one cool recessed window — is what makes the
   body read as physical rather than as more interface.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── The desk ──────────────────────────────────────────────────────────── */
html {
  background-color: #d9d7cf !important;   /* the surface, a step darker than the case */
}
html[data-theme="graphite"] { background-color: #08080a !important; }
@media (prefers-color-scheme: dark) {
  html:not([data-theme="ceramic"]) { background-color: #08080a !important; }
}

body {
  background-color: transparent !important;
  background-image: url('../assets/textures/ground.jpg') !important;
  background-size: 360px 360px !important;
  background-repeat: repeat !important;
  background-blend-mode: soft-light !important;
  min-block-size: 100dvh;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  padding: clamp(0.5rem, 2.5vh, 2.25rem) clamp(0.5rem, 2vw, 2rem) !important;
  box-sizing: border-box !important;
}

/* ── The case ──────────────────────────────────────────────────────────── */
.app-shell {
  position: relative !important;
  inline-size: 100% !important;
  block-size: auto !important;
  min-block-size: 0 !important;
  max-block-size: 100% !important;
  margin-inline: auto !important;
  box-sizing: border-box !important;

  /* moulded body */
  background-color: var(--panel-tone, #e7e6de) !important;
  background-image: url('../assets/textures/panel.jpg') !important;
  background-size: 300px 300px !important;
  background-repeat: repeat !important;
  background-blend-mode: soft-light !important;

  border-radius: clamp(14px, 1.6vw, 22px) !important;
  padding: clamp(0.75rem, 2vh, 1.5rem) clamp(0.75rem, 2vw, 1.75rem) !important;

  /* the lip catches the light along its top edge; the body casts onto the desk */
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.5),
    inset 0 -1px 0 rgba(58, 54, 44, 0.07),
    0 1px 2px rgba(58, 54, 44, 0.10),
    0 10px 22px rgba(58, 54, 44, 0.10),
    0 26px 60px rgba(58, 54, 44, 0.10) !important;
  overflow: hidden !important;
}
html[data-theme="graphite"] .app-shell {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.07),
    inset 0 -1px 0 rgba(0, 0, 0, 0.6),
    0 1px 2px rgba(0, 0, 0, 0.5),
    0 10px 24px rgba(0, 0, 0, 0.45),
    0 30px 70px rgba(0, 0, 0, 0.4) !important;
}

/* ── THE SCREEN — the whole 20% ────────────────────────────────────────
   A hole in the object: inset, cooler than the body, with a hairline lip at
   the top where the cut edge catches light. Nothing else in the app is
   allowed to look like a screen. */
.tt-timer__readout,
.t-display {
  display: block !important;
  inline-size: auto !important;
  margin-inline: auto !important;
  padding: 0.25em 0.6em 0.3em !important;
  border-radius: 6px !important;
  background-color: #dcdcd3 !important;      /* LCD glass, cooler than the body */
  box-shadow:
    inset 0 2px 4px rgba(40, 44, 40, 0.22),
    inset 0 -1px 0 rgba(255, 255, 255, 0.5),
    0 1px 0 rgba(255, 255, 255, 0.45) !important;
  color: #23261f !important;                 /* LCD ink: near-black, faintly green */
  -webkit-text-fill-color: #23261f !important;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.28) !important;
}
html[data-theme="graphite"] body .tt-timer__readout,
html[data-theme="graphite"] body .t-display {
  background-color: #14150f !important;
  color: #cfe0b8 !important;                 /* backlit LCD green, dim */
  -webkit-text-fill-color: #cfe0b8 !important;
  text-shadow: 0 0 10px rgba(180, 220, 140, 0.16) !important;
  box-shadow:
    inset 0 2px 5px rgba(0, 0, 0, 0.8),
    inset 0 -1px 0 rgba(255, 255, 255, 0.05) !important;
}
@media (prefers-color-scheme: dark) {
  html:not([data-theme="ceramic"]) body .tt-timer__readout {
    background-color: #14150f !important;
    color: #cfe0b8 !important;
    -webkit-text-fill-color: #cfe0b8 !important;
  }
}

/* ── A seam where the panel meets the control zone ─────────────────────
   Real cases are assembled from parts, and the join is visible. */
.tt-timer__transport {
  position: relative !important;
  padding-block-start: clamp(0.6rem, 1.6vh, 1.1rem) !important;
}
.tt-timer__transport::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: -100vw;
  block-size: 1px;
  background: linear-gradient(
    90deg,
    transparent,
    rgba(58, 54, 44, 0.13) 12%,
    rgba(58, 54, 44, 0.13) 88%,
    transparent
  );
  pointer-events: none;
}
html[data-theme="graphite"] .tt-timer__transport::before {
  background: linear-gradient(90deg, transparent, rgba(0,0,0,.7) 12%, rgba(0,0,0,.7) 88%, transparent);
}

/* The day ribbon is printed ON the case, so it sits inside the body. */
[class*="daystrip"] {
  margin-block-start: clamp(0.5rem, 1.4vh, 1rem) !important;
}

/* ── 29b. The caps must sit ON the case, invisibly ──────────────────────
   The cap photographs were shot on a flat #E7E6DE ground so they could tile
   into the panel. Adding a texture blend to the case shifted its value a
   fraction, and every cap then showed as a lighter rectangle — the seam you
   are not supposed to be able to find.

   The case goes back to the exact flat tone the caps were shot on. The
   material still reads: the DESK keeps its plaster grain, and the contrast
   between a grained surface and a smooth moulded body is itself the tell that
   the body is plastic. */
.app-shell {
  background-image: none !important;
  background-color: #e7e6de !important;
}
html[data-theme="graphite"] .app-shell {
  background-image: none !important;
  background-color: var(--ramp-200) !important;
}
@media (prefers-color-scheme: dark) {
  html:not([data-theme="ceramic"]) .app-shell {
    background-image: none !important;
    background-color: var(--ramp-200) !important;
  }
}

/* Trim the ground the caps carry so less of it has to match. */
html[data-theme="ceramic"] body .tt-timer__transportside .btn,
html[data-theme="ceramic"] body .btn--round,
html[data-theme="graphite"] body .tt-timer__transportside .btn,
html[data-theme="graphite"] body .btn--round {
  background-size: 132% 132% !important;   /* zoom past the surplus panel */
  background-position: center !important;
}
html[data-theme="ceramic"] body .btn--primary,
html[data-theme="graphite"] body .btn--primary {
  background-size: 116% 128% !important;
  background-position: center !important;
}
html[data-theme="ceramic"] body .tt-timer__transportside .btn:active,
html[data-theme="graphite"] body .tt-timer__transportside .btn:active {
  background-size: 128% 128% !important;
  background-position: center 54% !important;
}
html[data-theme="ceramic"] body .btn--primary:active,
html[data-theme="graphite"] body .btn--primary:active {
  background-size: 113% 124% !important;
  background-position: center 56% !important;
}

/* ── 29c. Match the case to the MEASURED cap ground ─────────────────────
   The caps were asked for on #E7E6DE, but the generator rendered them on
   something else, so every key showed a faint lighter rectangle. Sampled the
   actual corner pixels from the shipped files rather than trusting the prompt:

       cap-light.jpg        #EBE6E0
       cap-pill-signal.jpg  #E7E4DD
       cap-dark.jpg         var(--ramp-150)

   The case takes #EAE5DF — within ~2/255 of both light grounds, which is
   below the threshold where an edge is visible. Dark takes var(--ramp-150) exactly.
   Measure the asset; do not trust the brief you gave the generator. */
html[data-theme="ceramic"] .app-shell,
.app-shell {
  background-color: var(--ramp-150) !important;
}
html[data-theme="graphite"] .app-shell {
  background-color: var(--ramp-150) !important;
}
@media (prefers-color-scheme: dark) {
  html:not([data-theme="ceramic"]) .app-shell { background-color: var(--ramp-150) !important; }
}

/* The tokens the rest of the panel derives from follow the case, so recesses
   and channels stay in the right relationship to it. */
html[data-theme="ceramic"] { --panel-tone: var(--ramp-150); }
html[data-theme="graphite"] { --panel-tone: var(--ramp-150); }
@media (prefers-color-scheme: dark) {
  html:not([data-theme="ceramic"]) { --panel-tone: var(--ramp-150); }
}

/* The desk sits a clear step darker than the case, so the object reads as
   resting on a surface rather than floating in the same material. */
html { background-color: #d6d3ca !important; }
html[data-theme="graphite"] { background-color: var(--ramp-300) !important; }
@media (prefers-color-scheme: dark) {
  html:not([data-theme="ceramic"]) { background-color: var(--ramp-300) !important; }
}

/* ══════════════════════════════════════════════════════════════════════════
   30. DARK MODE CORRECTIONS + THE METHODS LIST

   Audited graphite properly and found four real faults, three of them mine:

     readout      1.04:1  — invisible. Near-black LCD ink on a near-black LCD.
     mode idle    2.54:1  — clock/alarm labels below AA
     rocker idle  2.29:1  — the raised half's label below AA
     start key    used the LIGHT-ground pill photograph, so the one red control
                  sat in a pale rectangle on a near-black case

   Plus: "Pomodoro" was stacking one letter per line. That was my drawer fix —
   §13 set `overflow-wrap: anywhere` on panel modules, which breaks words at
   ANY character. It stops a long unbroken string overflowing, and it also
   shreds ordinary words the moment a column gets tight. Wrong tool: the
   problem was a flex row that would not wrap, not a word that would not break.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── The methods list ──────────────────────────────────────────────────── */
.tt-panel .module,
.tt-panel .tt-module,
.mod-methods,
.mod-methods * {
  overflow-wrap: normal !important;
  word-break: normal !important;
  hyphens: none !important;
}
/* The head is name + shape on one line; let the PAIR wrap, never the words. */
.mth-head {
  flex-wrap: wrap !important;
  row-gap: 0.125rem !important;
}
.mth-name {
  white-space: nowrap !important;
  flex: 0 1 auto !important;
  min-inline-size: 0 !important;
}
.mth-shape {
  white-space: nowrap !important;
  flex: 0 0 auto !important;
}
.mth-who,
.mth-note {
  overflow-wrap: break-word !important;   /* only long URLs/strings, not words */
  word-break: normal !important;
}
.mth {
  min-inline-size: 0 !important;
}

/* ── The readout in dark: LCD ink must clear LCD glass ─────────────────── */
html[data-theme="graphite"] body .tt-timer__readout,
html[data-theme="graphite"] body .tt-timer__readout *,
html[data-theme="graphite"] body .t-display {
  background-color: var(--ramp-300) !important;
  color: var(--ink) !important;
  -webkit-text-fill-color: var(--ink) !important;
  text-shadow: 0 0 12px rgba(190, 230, 150, 0.18) !important;
}
@media (prefers-color-scheme: dark) {
  html:not([data-theme="ceramic"]) body .tt-timer__readout,
  html:not([data-theme="ceramic"]) body .tt-timer__readout *,
  html:not([data-theme="ceramic"]) body .t-display {
    background-color: var(--ramp-300) !important;
    color: var(--ink) !important;
    -webkit-text-fill-color: var(--ink) !important;
  }
}

/* ── Idle labels in dark: readable, still clearly secondary ───────────── */
html[data-theme="graphite"] body .segmented__key:not([aria-selected="true"]),
html[data-theme="graphite"] body .tt-timer__legendgroup .chip:not(.is-selected) {
  color: #cbc8c1 !important;
  -webkit-text-fill-color: #cbc8c1 !important;
}
@media (prefers-color-scheme: dark) {
  html:not([data-theme="ceramic"]) body .segmented__key:not([aria-selected="true"]),
  html:not([data-theme="ceramic"]) body .tt-timer__legendgroup .chip:not(.is-selected) {
    color: #cbc8c1 !important;
    -webkit-text-fill-color: #cbc8c1 !important;
  }
}

/* ── The start key gets its own dark-ground photograph ─────────────────── */
html[data-theme="graphite"] body .btn--primary,
html[data-theme="graphite"] body .btn--primary:active {
}
@media (prefers-color-scheme: dark) {
}

/* ── The desk texture is a LIGHT plaster scan; on a near-black desk it
      lifts the blacks into grey mud. Dark gets grain from the blend alone. */
html[data-theme="graphite"] body,
html[data-theme="graphite"] {
  background-image: none !important;
}
@media (prefers-color-scheme: dark) {
  html:not([data-theme="ceramic"]) body { background-image: none !important; }
}

/* ══════════════════════════════════════════════════════════════════════════
   31. DESKTOP NEVER SCROLLS — the device fits the desk

   Measured 51px of overflow at 1394x783: the case was 795px inside a padded
   783px viewport. A physical instrument does not extend past the desk it sits
   on, and scrolling a device is a category error — you scroll documents, not
   objects.

   So the desktop viewport is LOCKED. html/body take the viewport exactly and
   hide overflow; the case fits inside the padding; the instrument column
   absorbs whatever is left after the chrome; and the dial — the one genuinely
   elastic part — is sized from the height that actually remains.

   The dock still scrolls INTERNALLY, because it is a list and lists scroll.
   The instrument does not.
   ══════════════════════════════════════════════════════════════════════════ */
@media (min-width: 75rem) {
  html,
  body {
    block-size: 100dvh !important;
    max-block-size: 100dvh !important;
    overflow: hidden !important;
  }

  .app-shell {
    block-size: 100% !important;
    max-block-size: 100% !important;
    overflow: hidden !important;
  }
  /* The column stack is the pre-§38 desktop. Once the Panel is docked the
     shell is a two-column grid instead, so this must not force flex. */
  html:not([data-dock="desktop"]) .app-shell {
    display: flex !important;
    flex-direction: column !important;
  }

  /* The chrome takes what it needs; the stage takes the rest and may not grow. */
  .chassis { flex: 0 0 auto !important; }
  [class*="daystrip"] { flex: 0 0 auto !important; }
  .app-stage {
    flex: 1 1 auto !important;
    min-block-size: 0 !important;
    overflow: hidden !important;
  }
  .mode-panel,
  .tt-timer {
    min-block-size: 0 !important;
    max-block-size: 100% !important;
  }

  /* The dial is the elastic part. Sized from remaining height, floored so it
     never becomes a token, capped so it never dominates. */
  .tt-timer__stage {
    --instr: clamp(180px, min(34vh, 20vw), 340px);
    inline-size: var(--instr) !important;
    max-inline-size: var(--instr) !important;
    flex: 0 1 auto !important;
    min-block-size: 0 !important;
  }
  .tt-timer__dial,
  .tt-timer__stage > .dial {
    inline-size: var(--instr) !important;
    block-size: var(--instr) !important;
  }
  .tt-timer__readout,
  .t-display {
    font-size: clamp(2.25rem, 6vh, 4rem) !important;
  }

  /* The dock is a list: it scrolls inside itself, never the page. */
  .tt-panel__clip { overflow: hidden auto !important; overscroll-behavior: contain; }
}

/* Short desktop windows (a laptop with a dock and a menu bar) compress
   further rather than spilling. */
@media (min-width: 75rem) and (max-height: 800px) {
  .tt-timer__stage { --instr: clamp(160px, min(28vh, 18vw), 260px); }
  .tt-timer__readout, .t-display { font-size: clamp(2rem, 5vh, 3rem) !important; }
}

/* ══════════════════════════════════════════════════════════════════════════
   32. ON A PHONE, THE PHONE IS THE DEVICE

   §28 made the app an object sitting on a desk: an inset case, a visible
   surface around it, a contact shadow. That is right on a monitor, where you
   are looking AT a desk — and wrong on a phone, where you are HOLDING the
   thing. A phone-shaped device with a margin around it is a picture of a
   device, not a device.

   So below the desktop breakpoint the case goes full-bleed: no desk, no
   inset, no shadow, no radius. The bezel of the handset becomes the case, and
   the safe-area insets do the work the padding was doing.
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 74.999rem) {
  html,
  body {
    block-size: 100dvh !important;
    min-block-size: 100dvh !important;
    max-block-size: 100dvh !important;
    overflow: hidden !important;
    background-image: none !important;
  }
  html { background-color: var(--panel-tone, var(--ramp-150)) !important; }
  html[data-theme="graphite"] { background-color: var(--ramp-150) !important; }
  @media (prefers-color-scheme: dark) {
    html:not([data-theme="ceramic"]) { background-color: var(--ramp-150) !important; }
  }

  body {
    display: block !important;
    padding: 0 !important;
    background-color: var(--panel-tone, var(--ramp-150)) !important;
  }
  html[data-theme="graphite"] body { background-color: var(--ramp-150) !important; }

  .app-shell {
    inline-size: 100% !important;
    max-inline-size: none !important;
    block-size: 100dvh !important;
    max-block-size: 100dvh !important;
    margin: 0 !important;
    border-radius: 0 !important;
    box-shadow: none !important;
    background-image: none !important;
    overflow: hidden !important;

    display: flex !important;
    flex-direction: column !important;

    /* the handset's own geometry replaces the case's padding */
    padding:
      max(env(safe-area-inset-top), 0.5rem)
      max(env(safe-area-inset-right), 0.75rem)
      max(env(safe-area-inset-bottom), 0.5rem)
      max(env(safe-area-inset-left), 0.75rem) !important;
    box-sizing: border-box !important;
  }

  .chassis { flex: 0 0 auto !important; }
  [class*="daystrip"] { flex: 0 0 auto !important; }
  .app-stage {
    flex: 1 1 auto !important;
    min-block-size: 0 !important;
    overflow: hidden !important;
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
  }
  .mode-panel,
  .tt-timer { min-block-size: 0 !important; max-block-size: 100% !important; inline-size: 100% !important; }

  /* The dial is the elastic part here too. */
  .tt-timer__stage {
    --instr: clamp(150px, min(38vh, 70vw), 340px);
    inline-size: var(--instr) !important;
    max-inline-size: var(--instr) !important;
    margin-inline: auto !important;
  }
  .tt-timer__dial,
  .tt-timer__stage > .dial {
    inline-size: var(--instr) !important;
    block-size: var(--instr) !important;
  }
}

/* Landscape phone keeps its own composition from §15 — it only needs the
   full-bleed case, which the block above already gives it. */

/* ── 33. THE READOUT MUST CLEAR THE DIAL'S ARTWORK, NOT ITS BOX ─────────
   Desktop showed the countdown printed over the dial's lower numerals: the
   readout's top sat 15px ABOVE the dial's visible bottom edge.

   Cause: `.tt-timer__dial` is a wrapper sized to --instr, but the SVG inside
   it draws its bezel and contact shadow OUTSIDE that box (deliberately — the
   shadow has to fall on the panel). Flex lays the readout against the wrapper,
   so the artwork overlaps it. Nothing is mispositioned; the box is simply
   smaller than the thing it contains.

   Fix: a real row-gap sized to the overflow, applied wherever the stage is a
   column. Cheaper and safer than clipping the dial, which would cut the very
   shadow that makes it read as a physical part. */
.tt-timer__stage {
  row-gap: clamp(0.75rem, 2.2vh, 1.5rem) !important;
}

@media (min-width: 75rem) {
  .tt-timer__stage {
    display: flex !important;
    flex-direction: column !important;
    align-items: center !important;
    row-gap: clamp(1rem, 2.6vh, 1.75rem) !important;
  }
  /* The wrapper must not clip the shadow, but it must not lie about its
     height either — the gap above absorbs the difference. */
  .tt-timer__dial,
  .tt-timer__stage > .dial {
    overflow: visible !important;
    flex: 0 0 auto !important;
  }
  .tt-timer__readout,
  .t-display { margin-block-start: 0 !important; position: relative !important; z-index: 1 !important; }
}

/* Same guarantee on the other breakpoints, so a future change to --instr
   cannot quietly reintroduce the overlap. */
@media (max-width: 74.999rem) {
  .tt-timer__stage {
    display: flex !important;
    flex-direction: column !important;
    align-items: center !important;
  }
}

/* ── 34. THE METHODS LIST — rows were being crushed by flex-shrink ──────
   Each method row rendered 44px tall while its content (name, shape, and a
   two-line description) needs about 90px. `.mth-list` is a flex column with a
   constrained height, so all six rows SHRANK to fit — and because the rows
   overflow visibly, every description printed on top of the row below it.
   That is the illegible stacking, and it was never a text-wrapping problem.

   Flex items shrink by default. A list row must not: it keeps its natural
   height and the LIST scrolls. */
.mth-list {
  flex: 1 1 auto !important;
  min-block-size: 0 !important;
  overflow-y: auto !important;
  overscroll-behavior: contain;
}
.mth {
  flex: 0 0 auto !important;      /* never shrink a row to fit the box */
  block-size: auto !important;
  min-block-size: 0 !important;
  overflow: hidden !important;    /* belt and braces: never bleed onto a sibling */
}
.mth-who,
.mth-note {
  display: block !important;
  min-block-size: 0 !important;
}

/* The same crush can hit any list inside the dock. Rows never shrink. */
.tt-panel [class*="-list"] > * ,
.tt-panel ul > li {
  flex: 0 0 auto !important;
}

/* ── 34b. Size the DIAL, let its wrapper hug it ─────────────────────────
   The readout still printed across the dial, and worse the more I padded the
   gap — because I was sizing BOTH `.tt-timer__dial` (the wrapper) and `.dial`
   (the artwork) to the same measure, while the artwork actually renders larger
   than the box. Flex then placed the readout against the box, and the dial
   overhung it by ~58px.

   Padding the gap was treating the symptom. The wrapper must simply hug what
   it contains: size the artwork, let the box follow. Then the gap is a gap
   again rather than a guess at how far the overflow reaches. */
.tt-timer__dial {
  block-size: auto !important;
  inline-size: auto !important;
  max-block-size: none !important;
  max-inline-size: none !important;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  flex: 0 0 auto !important;
  overflow: visible !important;
}

@media (min-width: 75rem) {
  .tt-timer__dial { block-size: auto !important; inline-size: auto !important; }
  .tt-timer__stage > .dial,
  .tt-timer__dial > .dial,
  .dial {
    inline-size: var(--instr) !important;
    block-size: var(--instr) !important;
    flex: 0 0 auto !important;
  }
}
@media (max-width: 74.999rem) {
  .tt-timer__dial { block-size: auto !important; inline-size: auto !important; }
  .tt-timer__stage > .dial,
  .tt-timer__dial > .dial,
  .dial {
    inline-size: var(--instr) !important;
    block-size: var(--instr) !important;
    flex: 0 0 auto !important;
  }
}

/* ── 35. KILL THE RECTANGLE AROUND EVERY PHOTOGRAPHED CAP ───────────────
   The caps were generated on a flat ground so they could tile into the panel,
   but a JPEG rectangle on a flat surface will always risk a visible edge:
   compression noise, a hair of vignette, and a colour that is 2/255 off are
   each enough. The result read as a pasted sticker — the "textures look like
   mistakes" problem, and it is a real one.

   Fix: mask each cap to a soft ellipse a little larger than the part. The cap
   and its shadow survive; the surplus ground fades to nothing, so there is no
   edge to notice and no colour match to get exactly right. */
html body .tt-timer__transportside .btn,
html body .btn--round {
  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 56%, rgba(0,0,0,.7) 66%, transparent 76%) !important;
  mask-image: radial-gradient(circle at 50% 50%, #000 56%, rgba(0,0,0,.7) 66%, transparent 76%) !important;
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

/* The pill: do NOT try to reshape it with a mask. Composing a stadium from
   two circles plus a band clipped its ends and left red slivers — the mask was
   fighting the photograph's own silhouette. All that is actually needed is to
   fade the surplus ground at the four edges, leaving the shape untouched. */
html body .btn--primary,
html body .btn--lg.btn--primary {
  -webkit-mask-image:
    linear-gradient(90deg, transparent 0, #000 7%, #000 93%, transparent 100%),
    linear-gradient(180deg, transparent 0, #000 12%, #000 88%, transparent 100%) !important;
  mask-image:
    linear-gradient(90deg, transparent 0, #000 7%, #000 93%, transparent 100%),
    linear-gradient(180deg, transparent 0, #000 12%, #000 88%, transparent 100%) !important;
  -webkit-mask-composite: source-in !important;
  mask-composite: intersect !important;
  -webkit-mask-repeat: no-repeat !important;
  mask-repeat: no-repeat !important;
  -webkit-mask-size: 100% 100% !important;
  mask-size: 100% 100% !important;
}

/* A masked element cannot also paint a focus ring (the mask clips it), so the
   ring moves to an outline, which is drawn outside the mask. */
html body .tt-timer__transportside .btn:focus-visible,
html body .btn--round:focus-visible,
html body .btn--primary:focus-visible {
  outline: 2px solid var(--signal-key, #d4330c) !important;
  outline-offset: 4px !important;
}

/* ══════════════════════════════════════════════════════════════════════════
   36. NOTHING SCROLLS. EVERYTHING IS VISIBLE.

   The dock had scroll boxes inside scroll boxes: presets clipped at "25/45",
   methods cut through "52 / 17", the sequence card truncated, and the dock
   itself running past the viewport. A panel you have to scroll to read is a
   panel that has not been designed to fit.

   Making it fit is a CONTENT decision, not a CSS one. The height was going
   into six two-line method descriptions permanently on screen. So:

     · every module sizes to its content — no inner scrollers anywhere
     · a method shows its NAME and its SHAPE always; its description appears
       only when it is the selected one, which is the only time it is being
       decided about
     · presets sit on one row instead of a 2x2 block

   That is progressive disclosure in the Rams sense: the panel states what each
   control IS, and explains only the one you are actually choosing.
   ══════════════════════════════════════════════════════════════════════════ */
@media (min-width: 75rem) {
  /* No scrollers. Anywhere. */
  .tt-panel__clip,
  .tt-panel__sheet,
  .tt-panel [class*="pane"],
  .tt-panel .module,
  .tt-panel .tt-module,
  .tt-mod__body,
  .mod-methods,
  .mth-list,
  .mod-presets,
  .mod-sequence,
  .mod-session-log,
  .mod-estimate {
    overflow: visible !important;
    max-block-size: none !important;
    block-size: auto !important;
  }
  .tt-panel__clip { overflow-y: auto !important; }  /* last resort only */

  /* The dock is a column that distributes what it has. */
  .tt-panel__sheet {
    display: flex !important;
    flex-direction: column !important;
    block-size: 100% !important;
    overflow: hidden !important;
  }
  .tt-panel [role="tabpanel"]:not([hidden]) {
    flex: 1 1 auto !important;
    min-block-size: 0 !important;
    display: flex !important;
    flex-direction: column !important;
    gap: 0.625rem !important;
    overflow: visible !important;
  }
  .tt-panel__host {
    display: flex !important;
    flex-direction: column !important;
    gap: 0.625rem !important;
    min-block-size: 0 !important;
  }

  /* ── Methods: name + shape always; description only when chosen ─────── */
  .mth-who,
  .mth-note {
    display: none !important;
  }
  .mth.is-on .mth-who,
  .mth.is-on .mth-note {
    display: block !important;
  }
  .mth {
    padding-block: 0.5rem !important;
    min-block-size: 0 !important;
  }
  .mth-list { gap: 2px !important; }

  /* ── Presets: one row ────────────────────────────────────────────────── */
  .mod-presets [class*="list"],
  .mod-presets ul,
  .mod-presets > div {
    display: flex !important;
    flex-wrap: nowrap !important;
    gap: 0.375rem !important;
    align-items: center !important;
  }
  .mod-presets [class*="list"] > * { flex: 1 1 0 !important; min-inline-size: 0 !important; }

  /* Tighter module chrome so three modules fit a column. */
  .tt-panel .module,
  .tt-panel .tt-module { padding: 0.625rem 0.75rem !important; }
  .tt-panel .tt-mod__head,
  .tt-panel [class*="__head"] { margin-block-end: 0.375rem !important; }
}

/* ── 37. RETIRED: the expanded desktop field ────────────────────────────
   Attempted: drop the tab bar on desktop and lay every module out across the
   width as one information field. The intent is right — tabs are a
   small-screen affordance and hiding two thirds of the data on a 1440px
   screen is a phone layout in disguise.

   It failed for a structural reason worth recording: `src/layout.js` owns
   module placement and positions each module with EXPLICIT grid coordinates
   inside its own grid. Imposing a second grid from CSS put two placement
   systems in charge of the same elements, and modules from different panes
   landed on top of each other — visibly worse than the tabbed dock.

   A genuine desktop-native layout therefore needs layout.js to be taught
   about a multi-column desktop field. That is a JS change, not a CSS
   override, and shipping the override would have meant shipping overlapping
   content. Retired to docs/retired/desktop-expanded-grid.css.retired.
   ────────────────────────────────────────────────────────────────────────── */

/* ══════════════════════════════════════════════════════════════════════════
   38. THE DESKTOP FIELD — done properly this time

   The first attempt (§37, retired) laid a second grid over the panel and the
   modules overlapped. The reason was specific and I missed it: src/layout.js
   assigns every module an explicit cell via INLINE style —

       mod.el.style.gridColumn = col + ' / span ' + w
       mod.el.style.gridRow    = row + ' / span ' + h

   Adding a grid without clearing those coordinates leaves the engine in
   charge of placement, so modules from different panes land in the same cell.
   Inline styles carry no !important, so a declaration that does will beat
   them — that single override is what the previous attempt was missing.

   With placement released, every module can flow into one field:

     ┌─────────────────────────────┬──────────────┬──────────────┐
     │            DIAL             │  sessions    │  method      │
     │           15:00             │  estimate    │  presets     │
     │      original · paused      │              │  sequence    │
     │   reset  START  +1  +5      │              │              │
     ├─────────────────────────────┴──────────────┴──────────────┤
     │ ▬▬▬▬▬▬▬▬▬▬▬▬ day ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬  6h 43m left  │
     └───────────────────────────────────────────────────────────┘

   No tabs, no drawer, no scrolling — every control visible at once, which is
   what a desk instrument does and what a phone cannot.
   ══════════════════════════════════════════════════════════════════════════ */

@media (min-width: 75rem) {
  /* THE DESKTOP FIELD.
     -------------------------------------------------------------------
     Not a wider drawer. src/main.js moves #panel-root INTO .app-shell at
     this breakpoint, so the shell becomes the whole device: a top rail, a
     bottom rail, and between them the instrument on the left with the field
     of modules on the right. Header and day strip span both columns because
     on a real instrument the rails run the full width of the case — that is
     the single change that stops this reading as a phone card with a drawer
     stuck to it. */
  html[data-dock="desktop"] .app-shell {
    display: grid !important;
    padding-inline-end: 0 !important;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr) auto;
    grid-template-areas:
      "rail-top"
      "instrument"
      "rail-btm";
    column-gap: clamp(0.75rem, 1.4vw, 1.5rem);
  }

  /* DRAWER SHUT: the instrument owns the case and the dial is dead centre.
     This is the resting state of the app — one enormous disc, readable from
     across a room, with nothing competing for attention. The modules are
     reference material, so they cost a deliberate pull rather than a third of
     the screen you did not ask for. */
  html[data-dock="desktop"]:not([data-field="open"]) .app-panel-mount {
    display: none !important;
  }

  /* DRAWER OPEN: the case splits and the field takes the right-hand column.
     The instrument asks for only what the dial needs here — every pixel it
     takes beyond that comes straight out of the field's column count, and at
     1280 a greedy instrument pushed the field down to columns too narrow to
     hold a module. Wide is what the CLOSED state is for. */
  html[data-dock="desktop"][data-field="open"] .app-shell {
    grid-template-columns: clamp(24rem, 32vw, 34rem) minmax(0, 1fr);
    grid-template-areas:
      "rail-top  rail-top"
      "instrument field"
      "rail-btm  rail-btm";
  }
  html[data-dock="desktop"] .chassis { grid-area: rail-top; }
  html[data-dock="desktop"] .app-stage {
    grid-area: instrument;
    min-inline-size: 0 !important;
    min-block-size: 0 !important;
    display: block !important;
    overflow: hidden !important;
  }
  html[data-dock="desktop"] .day-strip { grid-area: rail-btm; }
  html[data-dock="desktop"] .app-panel-mount {
    display: block !important;
    grid-area: field;
    min-inline-size: 0;
    min-block-size: 0;
  }

  /* An ordinary cell, so: no fixed positioning, no slide, no scrim. */
  html[data-dock="desktop"] .tt-panel,
  html[data-dock="desktop"] .tt-panel__clip {
    position: static !important;
    inset: auto !important;
    inline-size: 100% !important;
    block-size: 100% !important;
    max-block-size: none !important;
    overflow: hidden !important;
    transform: none !important;
    pointer-events: auto !important;
    visibility: visible !important;
    opacity: 1 !important;
  }
  html[data-dock="desktop"] .tt-panel__scrim,
  html[data-dock="desktop"] .app-scrim { display: none !important; }

  /* Tabs, close and grip are small-screen controls: everything is on show. */
  html[data-dock="desktop"] .tt-panel__tabs,
  html[data-dock="desktop"] .tt-panel [role="tablist"],
  html[data-dock="desktop"] .tt-panel__close,
  html[data-dock="desktop"] .tt-panel__grip,
  html[data-dock="desktop"] .tt-panel__head,
  html[data-dock="desktop"] .tt-panel__empty { display: none !important; }

  html[data-dock="desktop"] .tt-panel__sheet {
    position: static !important;
    inset: auto !important;
    inline-size: 100% !important;
    block-size: 100% !important;
    transform: none !important;
    display: flex !important;
    flex-direction: column !important;
    overflow: hidden !important;
    padding: 0 !important;
    border-radius: var(--radius-l) !important;
    border-inline-start: 0 !important;
  }

  /* ONE FIELD, NOT THREE BANDS.
     Three panes each running their own grid leaves three separate bands with
     ragged, half-empty rows — the tab structure showing through a layout that
     no longer has tabs. So .tt-panel__panes becomes the single grid and every
     wrapper between it and the modules is dissolved with `display: contents`,
     which puts all seven modules in one flow. (Safe here: these are bare
     containers. Never do this to the dial's wrapper — an SVG sizes against
     the box and the box is what `contents` removes.) */
  html[data-dock="desktop"] .tt-panel__panes {
    /* COLUMNS, NOT GRID. A grid row is as tall as its tallest module, so a
       386px methods card forces a 386px row and the three short cards beside
       it each waste 200px — which is what pushed the field off the bottom of
       the screen. Multi-column balances the modules by height instead, so the
       field packs to roughly total/columns and everything fits on one screen
       with no scroll box. This is the one layout mode CSS has that does what
       a dashboard actually wants. */
    display: block !important;
    /* column-width, not column-count: the browser fits as many columns as the
       field can actually hold, so this stays correct at 1280 and at 2560
       without a ladder of breakpoints guessing at it. */
    column-width: 15.5rem !important;
    column-gap: clamp(0.5rem, 1vh, 0.75rem) !important;
    column-fill: balance !important;
    flex: 1 1 auto !important;
    min-block-size: 0 !important;
    overflow: hidden !important;
    padding: 0 0.5rem !important;
    box-sizing: border-box !important;
  }

  /* layout.js keeps a sizing probe and a drop indicator in the grid. They are
     invisible but they are still boxes, and in the field one of them was
     holding a whole column open. */
  html[data-dock="desktop"] .tt-layout__probe,
  html[data-dock="desktop"] .tt-layout__drop { display: none !important; }

  /* THE VERTICAL BUDGET.
     A desk instrument has a bezel, and the case inset is what sells it — but
     on a laptop the bezel was spending ~46px of the only axis the field is
     short on. Trimmed on the block axis only: the side bezels are untouched,
     so the case still reads as a moulded body, and the field gets the height
     back. This is the difference between everything fitting on one screen and
     three modules falling off the bottom. */
  html[data-dock="desktop"] body {
    padding-block: 0.375rem !important;
  }
  html[data-dock="desktop"] .app-shell {
    padding-block: 0.25rem !important;
    row-gap: 0.375rem !important;
  }
  /* Mobile's 12px module padding is generous for a thumb. A pointer does not
     need it, and seven modules x 4px x 2 edges is another 56px of field. */
  html[data-dock="desktop"] .tt-panel .tt-mod__body {
    padding: 0.5rem !important;
  }
  html[data-dock="desktop"] .tt-panel__pane,
  html[data-dock="desktop"] .tt-panel__host,
  html[data-dock="desktop"] .tt-layout__grid {
    display: contents !important;
  }
  html[data-dock="desktop"] .tt-panel__pane,
  html[data-dock="desktop"] .tt-panel__pane[hidden] {
    visibility: visible !important;
    flex: 0 0 auto !important;
    block-size: auto !important;
    min-block-size: 0 !important;
    padding: 0 !important;
    /* THE OVERLAP. panel.js stacks the panes absolutely so one can crossfade
       into the next. Docked there is no crossfade — all three are on show at
       once — so they must return to flow or they render on top of each other. */
    position: static !important;
    inset: auto !important;
    transform: none !important;
    opacity: 1 !important;
    pointer-events: auto !important;
  }

  /* Release layout.js's per-module placement so the modules flow in the
     field grid. layout.js writes gridColumn/gridRow as inline styles with no
     !important, so this wins — and it only works because every wrapper down
     to the modules is dissolved above, making them the grid's own children.
     Release placement without that and each module lands in one of the
     engine's twelve 14px tracks. */
  html[data-dock="desktop"] .tt-panel .tt-mod {
    grid-column: auto !important;
    grid-row: auto !important;
    position: static !important;
    transform: none !important;
    --tt-dx: 0px !important;
    --tt-dy: 0px !important;
    inline-size: auto !important;
    min-inline-size: 0 !important;
    block-size: auto !important;
    max-block-size: none !important;
    overflow: hidden !important;
    /* A module must never be split down the middle by a column break. */
    break-inside: avoid !important;
    -webkit-column-break-inside: avoid !important;
    margin: 0 0 0.375rem !important;
  }

  /* Drag-to-arrange has no meaning once the coordinates are released. */
  html[data-dock="desktop"] .tt-mod__grip,
  html[data-dock="desktop"] .tt-mod__resize,
  html[data-dock="desktop"] .tt-panel__arrange { display: none !important; }

  /* THE DIAL IS THE PRODUCT.
     §31 sized it "capped so it never dominates", which is exactly backwards
     here: a depleting area you can read from across the room IS the feature,
     and every other control on this side exists to serve it. Docked, it takes
     the instrument column and the rest arranges underneath.

     Sized off vh first so it can never push the transport off the bottom,
     then off vw so it can never outgrow its own column. */
  html[data-dock="desktop"] .tt-timer__stage {
    /* The dial is sized from what is ACTUALLY left, not from a flat vh
       fraction. Measured on the real instrument: the rails and case padding
       take 134px of the window, and the chrome under the dial — readout,
       caption, session label, face row, transport and their gaps — takes a
       further 344px that does NOT scale with the window. So the largest
       honest dial is `100vh - 478px`, with 12px of slack for the readout's
       own clamp. A flat vh fraction cannot express that: it overflowed the
       transport off the bottom of a 900px screen and wasted room on a tall
       one. The vw term then keeps the disc inside its own column. */
    --instr: clamp(220px, min(calc(100vh - 490px), 34vw), 560px) !important;
    inline-size: var(--instr) !important;
    max-inline-size: var(--instr) !important;
    margin-inline: auto !important;
    /* §31 leaves this `flex: 0 1 auto`, so the stage shrank below its own
       content and the "ORIGINAL FACE · SET 25 MIN" caption spilled out of the
       bottom and landed on top of the session label. */
    flex: 0 0 auto !important;
    min-block-size: auto !important;
  }

  /* Drawer shut there is no field to stay clear of, so only height binds. */
  html[data-dock="desktop"]:not([data-field="open"]) .tt-timer__stage {
    --instr: clamp(220px, min(calc(100vh - 490px), 62vw), 760px) !important;
  }
  html[data-dock="desktop"] .tt-timer__dial,
  html[data-dock="desktop"] .tt-timer__stage > .dial {
    inline-size: var(--instr) !important;
    block-size: var(--instr) !important;
  }
  /* The readout is the dial's caption, so it grows with it. */
  html[data-dock="desktop"] .tt-timer__readout,
  html[data-dock="desktop"] .t-display {
    font-size: clamp(2.5rem, 7vh, 4.5rem) !important;
  }

  /* THE DAY IS THE RAIL.
     On a phone the day lives in a card because there is nowhere else to put
     it. On the desktop it is a permanent full-width rail across the bottom of
     the case, which states exactly what these two cards state:

       rail        "4h 38m of your day left · 08:00-23:00"
       day-window  "Your day 8:00 AM - 11:00 PM · 4h 38m of your day left"
       day-ribbon  the depleting ribbon — which the rail IS

     Repeating it costs 360px of the one screen, which is what forced the
     other five modules into columns too narrow to read. The information is
     not lost, it is promoted: the rail is always visible, where the cards
     were two thirds of a tab. Editing waking hours lives in Settings, in the
     top rail, which is likewise always reachable. */
  html[data-dock="desktop"] .tt-panel .tt-mod[data-tt-id="day-window"],
  html[data-dock="desktop"] .tt-panel .tt-mod[data-tt-id="day-ribbon"] {
    display: none !important;
  }

  /* A field column is narrower than a phone, so a method row cannot keep its
     name and its shape on one line: §23 pins .mth-shape to `nowrap` and
     `flex: 0 0 auto`, which made it 210px inside a 208px card and clipped the
     text. Stacked, each part gets the full width and wraps — correct at any
     column width rather than at one lucky one. */
  html[data-dock="desktop"] .tt-panel .mth {
    display: flex !important;
    flex-direction: column !important;
    align-items: flex-start !important;
    gap: 0.125rem !important;
  }
  html[data-dock="desktop"] .tt-panel .mth-name,
  html[data-dock="desktop"] .tt-panel .mth-shape {
    white-space: normal !important;
    flex: 0 1 auto !important;
    max-inline-size: 100% !important;
    text-align: start !important;
  }
}

/* A shorter viewport has less column height, so it needs MORE columns to hold
   the same content: height, not width, is what the field runs out of. Chasing
   the last few pixels of padding instead would be fragile — the session log
   grows as it is used, and the layout would silently start clipping again. */
@media (min-width: 75rem) and (max-height: 56rem) {
  html[data-dock="desktop"] .tt-panel__panes { column-width: 13rem !important; }
}

/* SHORT SCREENS GET DENSER, NOT NARROWER.
   Narrowing the columns to win height backfires: a narrower column wraps more
   text, which makes every module TALLER, which needs more columns again. It
   does not converge. What actually buys height is dropping the explainer
   prose — onboarding copy that has done its job by the time you are working,
   and that costs ~170px of a screen this short. The numbers, the controls and
   the lists all stay; only the sentences about them go, and only here. Phones
   and taller desktops keep the full copy. */
/* THE ONE FALLBACK. Below ~768px of viewport height the five modules do not
   physically fit, whatever the column count: a narrower column wraps more and
   grows taller, so there is no arrangement that converges. Clipping would hide
   information silently, which is the worse failure — so here, and only here,
   the field may scroll. Every mainstream desktop height (768, 800, 900, 982,
   1080, 1440) is verified to fit with no scroll at all. */
@media (min-width: 75rem) and (max-height: 48rem) {
  html[data-dock="desktop"] .tt-panel__panes {
    overflow-y: auto !important;
    overscroll-behavior: contain !important;
  }
}

@media (min-width: 75rem) and (max-height: 48rem) {
  html[data-dock="desktop"] .tt-panel .est-fine,
  html[data-dock="desktop"] .tt-panel .est-calnote,
  html[data-dock="desktop"] .tt-panel .sl-empty,
  html[data-dock="desktop"] .tt-panel .ps__hint,
  html[data-dock="desktop"] .tt-panel .mth__body,
  html[data-dock="desktop"] .tt-panel .mth-note {
    display: none !important;
  }
}

/* ── 39. The transport must fit the narrowest phone ─────────────────────
   At 390px the row measured reset(52) + start(160) + +1(52) + +5(52) with
   three 8px gaps = 340px, laid out from x=55, so the +5 key ran to 395 and
   was cut by the screen edge. A control you cannot press is worse than a
   cramped one, so the start key gives up its fixed width first — it is still
   by far the largest target in the row — and the gaps tighten only after
   that. */
@media (max-width: 26rem) {
  .tt-timer__transport {
    gap: 0.375rem !important;
    padding-inline: 0.375rem !important;
    box-sizing: border-box !important;
    max-inline-size: 100% !important;
  }
  /* §29 pins the photographed pill cap to `min-inline-size: 10rem` off a
     `html[data-theme] body .btn--lg.btn--primary` selector, so the override
     has to be at least as specific or it silently loses. The 3/1 aspect is
     kept — the cap is a photograph and stretching it would show. */
  html body .tt-timer__transport .btn--lg.btn--primary,
  html[data-theme="ceramic"] body .tt-timer__transport .btn--lg.btn--primary,
  html[data-theme="graphite"] body .tt-timer__transport .btn--lg.btn--primary {
    min-inline-size: 7rem !important;
    max-inline-size: 100% !important;
  }
  /* THE ACTUAL CAUSE. .tt-timer__transportmain is a grid of `91px 156px 91px`
     — symmetric sides so the start key sits dead centre. But the lead side
     holds one key (52px) and the trail side holds two (+1 and +5 = 112px), so
     the trail overflowed its 91px column by 21px and pushed +5 off the screen.
     Sizing the sides to their contents keeps every key on the glass; the start
     key still centres optically because it takes all the space left over. */
  .tt-timer__transportmain {
    grid-template-columns: auto minmax(0, 1fr) auto !important;
    min-inline-size: 0 !important;
    max-inline-size: 100% !important;
    justify-items: center !important;
  }
  .tt-timer__transportside {
    flex: 0 0 auto !important;
    min-inline-size: 0 !important;
  }
  .tt-timer__transportside .btn {
    min-inline-size: 2.625rem !important;
    flex: 0 0 auto !important;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   40. THE CONTROL PANEL — real keys, real switches, real domes

   This is the Braun/Rams control-panel reference implemented properly, and it
   REPLACES the photographed cap JPGs (§25 round caps, §29 the pill). Those
   were flat photographs stretched over live boxes: they could not take a
   press, could not tint for dark mode, and did not match the reference at any
   size. Everything here is drawn, so it scales, inverts and depresses.

   ONE LIGHT SOURCE, from above and slightly left. Every highlight sits on a
   top edge, every occlusion pools at a bottom edge, and every drop shadow
   falls down and a touch right. Nothing in this section may contradict that.

   The vocabulary, matching the reference plate:
     · a KEY      — a dished square cap in a recessed housing  (mode selector)
     · a DOME     — a round pushbutton, convex, rim-lit        (transport, tools)
     · the SIGNAL — the big orange dome                        (start)
     · a ROCKER   — an orange paddle in a bezel                (switches)
   ══════════════════════════════════════════════════════════════════════════ */

:root {
  /* A cap only reads as an OBJECT if its face travels far enough tonally:
     near-white where the light lands, clearly dark where it turns away. The
     first pass here spanned about 8% luminance and looked like a tinted
     rectangle. This spans nearly 30%. */
  --cp-cream-hi: #fffdf8;
  --cp-cream: var(--ramp-100);
  --cp-cream-lo: var(--ramp-300);
  --cp-cream-deep: #c6bda6;
  /* the collar the cap is seated in — always darker than the cap's midtone */
  --cp-collar-hi: #d5cebd;
  --cp-collar-lo: #9d947c;
  --cp-edge: rgba(120, 108, 84, 0.45);
  --cp-occlude: rgba(96, 82, 56, 0.42);
  --cp-cast: rgba(52, 44, 28, 0.30);
  --cp-lift: rgba(255, 255, 255, 1);
  --cp-signal-hi: #ff9166;
  --cp-signal: var(--signal);
  --cp-signal-lo: #96220a;
}

html[data-theme="graphite"] {
  --cp-cream-hi: #55555a;
  --cp-cream: var(--ramp-050);
  --cp-cream-lo: var(--ramp-250);
  --cp-cream-deep: #141416;
  --cp-collar-hi: #2e2e32;
  --cp-collar-lo: #0e0e10;
  --cp-edge: rgba(0, 0, 0, 0.7);
  --cp-occlude: rgba(0, 0, 0, 0.72);
  --cp-cast: rgba(0, 0, 0, 0.66);
  --cp-lift: rgba(255, 255, 255, 0.22);
  --cp-signal-hi: #ffa079;
  --cp-signal: var(--signal);
  --cp-signal-lo: #a82b06;
}

/* ── 40.1 THE KEY COMBO — clock / timer / alarm ────────────────────────
   A row of dished caps sunk into a moulded housing, exactly the cluster at
   the top left of the reference plate. The housing is RECESSED (dark at its
   top lip, light at its bottom) so the keys can sit proud of it. */
html body .segmented {
  background: linear-gradient(180deg, var(--cp-cream-lo), var(--cp-cream)) !important;
  background-image: linear-gradient(180deg, var(--cp-cream-lo), var(--cp-cream)) !important;
  border: 1px solid var(--cp-edge) !important;
  border-radius: 13px !important;
  padding: 4px !important;
  gap: 4px !important;
  box-shadow:
    inset 0 2px 4px var(--cp-occlude),
    inset 0 -1px 0 var(--cp-lift),
    0 1px 0 var(--cp-lift),
    0 2px 6px var(--cp-cast) !important;
}

html body .segmented__key {
  position: relative !important;
  background: linear-gradient(
    180deg,
    var(--cp-cream-hi) 0%,
    var(--cp-cream) 46%,
    var(--cp-cream-lo) 100%
  ) !important;
  background-image: linear-gradient(
    180deg,
    var(--cp-cream-hi) 0%,
    var(--cp-cream) 46%,
    var(--cp-cream-lo) 100%
  ) !important;
  border: 1px solid var(--cp-edge) !important;
  border-radius: 9px !important;
  /* The dish: a bright lip along the top, shade gathering at the bottom. */
  box-shadow:
    inset 0 2px 1px -1px var(--cp-lift),
    inset 0 -6px 8px -3px var(--cp-occlude),
    inset 0 -1px 0 rgba(255, 255, 255, 0.5),
    0 1px 1px rgba(52, 44, 28, 0.30),
    calc(var(--sh-x, -2px) * 0.8) calc(var(--sh-y, 3px) * 1.2)
      calc(var(--sh-blur, 12px) * 0.55)
      rgba(52, 44, 28, calc(var(--sh-alpha, 0.22) + 0.16)) !important;
  transition: box-shadow 90ms ease, transform 90ms ease, background 140ms ease !important;
}

/* Selected reads as PRESSED — the gradient inverts, the shadow moves inside,
   and the cap drops a pixel. A key that is down looks down. */
html body .segmented__key[aria-selected="true"],
html body .segmented__key.is-active {
  background: linear-gradient(
    180deg,
    var(--cp-cream-lo) 0%,
    var(--cp-cream) 62%,
    var(--cp-cream-hi) 100%
  ) !important;
  background-image: linear-gradient(
    180deg,
    var(--cp-cream-lo) 0%,
    var(--cp-cream) 62%,
    var(--cp-cream-hi) 100%
  ) !important;
  box-shadow:
    inset 0 3px 5px var(--cp-occlude),
    inset 0 -1px 0 var(--cp-lift),
    0 1px 0 var(--cp-lift) !important;
  transform: translateY(1px) !important;
}

html body .segmented__key:active {
  transform: translateY(1px) !important;
  box-shadow:
    inset 0 3px 6px var(--cp-occlude),
    inset 0 -1px 0 var(--cp-lift) !important;
}

/* The lamp. On the reference plate the live key carries a small orange dot;
   it is the only saturated thing in the cluster, which is what makes it
   readable at a glance. */
html body .segmented__key .segmented__dot,
html body .segmented__key > .dot {
  background: radial-gradient(circle at 38% 32%, var(--cp-signal-hi), var(--cp-signal) 62%, var(--cp-signal-lo)) !important;
  box-shadow:
    0 0 0 1px rgba(120, 40, 8, 0.30),
    0 0 6px 1px rgba(224, 72, 15, 0.45) !important;
}

/* ── 40.2 THE DOME — round pushbuttons (transport keys, tools) ─────────
   Convex, not flat: a radial gradient with its hot spot high and left, a rim
   of light around the top edge, occlusion pooling at the bottom, and a soft
   contact shadow under the whole cap. This is what replaces cap-light.jpg. */
html body .tt-timer__transportside .btn,
html body .tt-timer__transport .btn:not(.btn--primary),
html body .chassis__tools .btn--icon,
html body .btn--icon.btn--round {
  /* THE FACE IS ALMOST FLAT.
     Three rewrites went wrong the same way: piling contrast INTO the cap, so
     it turned into a pearl. Look at the reference — the face is one tone give
     or take a few percent. What makes it an object is the EDGE (a bright rim
     where the light lands, a dark one where it leaves) and the SHADOW it
     drops on the panel. Internal drama is what a drawing of a button has;
     restraint plus a real shadow is what a button has. */
  background-color: transparent !important;
  background-image:
    /* the faintest sheen high on the face — barely there on purpose */
    radial-gradient(
      ellipse 70% 46% at 44% 16%,
      rgba(255, 255, 255, 0.75) 0%,
      rgba(255, 255, 255, 0) 100%
    ),
    linear-gradient(178deg, #f7f3ea 0%, #f1ece0 52%, #e7e0d0 100%) !important;
  background-clip: padding-box, padding-box !important;
  background-repeat: no-repeat !important;
  border: 0 !important;
  border-radius: 50% !important;
  aspect-ratio: 1 / 1 !important;
  box-shadow:
    /* the rim: bright along the top, dark along the bottom, hairline both */
    inset 0 1.5px 0 -0.5px rgba(255, 255, 255, 1),
    inset 0 -1.5px 0 -0.5px rgba(120, 106, 78, 0.30),
    /* the turn of the edge, tight to the rim */
    inset 0 -5px 5px -4px rgba(96, 82, 56, 0.40),
    /* separation from the panel — the cap is lighter, so it needs a line */
    0 0 0 1px rgba(120, 108, 84, 0.16),
    /* contact, cast, ambient. This is where the realism actually lives. */
    0 1px 1px rgba(52, 44, 28, 0.20),
    0 3px 4px -1px rgba(52, 44, 28, 0.24),
    0 8px 12px -2px rgba(52, 44, 28, 0.20),
    0 16px 24px -6px rgba(52, 44, 28, 0.16) !important;
  transition: box-shadow 90ms ease, transform 90ms ease !important;
}

html body .tt-timer__transportside .btn:active,
html body .chassis__tools .btn--icon:active,
html body .btn--icon.btn--round:active {
  transform: translateY(2px) !important;
  background-image:
    radial-gradient(
      ellipse 70% 46% at 46% 26%,
      rgba(255, 255, 255, 0.30) 0%,
      rgba(255, 255, 255, 0) 100%
    ),
    linear-gradient(178deg, #e9e2d2 0%, #efe9dd 60%, #f4efe5 100%) !important;
  box-shadow:
    inset 0 2px 4px -1px rgba(96, 82, 56, 0.42),
    inset 0 -1px 0 rgba(255, 255, 255, 0.65),
    0 0 0 1px rgba(120, 108, 84, 0.18),
    0 1px 2px rgba(52, 44, 28, 0.22) !important;
}

/* ── 40.3 THE SIGNAL — the start key ───────────────────────────────────
   The reference's orange dome: a hot specular high on the face, deep shadow
   raked along the bottom, and a warm cast on the panel beneath. Replaces
   cap-pill-signal.jpg, which could not press and would not tint. */
html body .btn--lg.btn--primary,
html[data-theme="ceramic"] body .btn--lg.btn--primary,
html[data-theme="graphite"] body .btn--lg.btn--primary {
  position: relative !important;
  background-color: transparent !important;
  /* Same discipline as the cream caps, and for a bar the light runs ALONG it:
     a linear form, an even middle, rims top and bottom. The radial version
     stretched a sphere over 160px and left a seam down the centre. The sheen
     is baked into this gradient rather than laid on in a pseudo-element,
     which is what was producing the hard band across the face. */
  background-image: linear-gradient(
    178deg,
    #f4753f 0%,
    #ea5a22 18%,
    #e0480f 52%,
    #cc3b0b 82%,
    #b02f07 100%
  ) !important;
  background-repeat: no-repeat !important;
  border: 0 !important;
  border-radius: 999px !important;
  aspect-ratio: auto !important;
  overflow: visible !important;
  box-shadow:
    inset 0 1.5px 0 -0.5px rgba(255, 255, 255, 0.55),
    inset 0 -1.5px 0 -0.5px rgba(90, 22, 0, 0.45),
    /* NO blurred inset here. An inset shadow follows the border shape, so on
       a 999px pill it curls up both ends and paints dark caps on them — the
       shading has to come from the linear gradient instead, which only runs
       down the block axis. A circle can take one; a bar cannot. */
    0 0 0 1px rgba(120, 40, 8, 0.22),
    0 1px 1px rgba(60, 20, 4, 0.24),
    0 3px 4px -1px rgba(60, 20, 4, 0.28),
    0 8px 12px -2px rgba(60, 20, 4, 0.24),
    0 16px 26px -6px rgba(176, 55, 14, 0.28) !important;
  color: #fff !important;
  -webkit-text-fill-color: #fff !important;
  transition: box-shadow 90ms ease, transform 90ms ease !important;
}

/* The sheen is in the gradient now; this element caused the band. */
html body .btn--lg.btn--primary::after { content: none !important; }

html body .btn--lg.btn--primary:active {
  transform: translateY(1.5px) !important;
  box-shadow:
    inset 0 3px 7px rgba(88, 20, 0, 0.52),
    inset 0 -1px 0 rgba(255, 255, 255, 0.20),
    0 1px 2px rgba(60, 20, 4, 0.30) !important;
}

/* ── 40.4 THE ROCKER — switches ────────────────────────────────────────
   The reference's rocker is a paddle in a recessed bezel: squared off, not a
   lozenge, with the live side lit orange and the dead side sunk in shadow. */
html body .switch__track {
  background-color: var(--cp-cream-lo) !important;
  background-image: linear-gradient(180deg, var(--cp-cream-deep), var(--cp-cream-lo)) !important;
  border: 1px solid var(--cp-edge) !important;
  border-radius: 7px !important;
  box-shadow:
    inset 0 2px 4px var(--cp-occlude),
    inset 0 -1px 0 var(--cp-lift),
    0 1px 0 var(--cp-lift) !important;
  transition: background-image 140ms ease !important;
}

html body .switch__input:checked + .switch__track,
html body .switch__track[data-on="true"],
html body .switch.is-on .switch__track {
  background-color: var(--cp-signal) !important;
  background-image: linear-gradient(180deg, var(--cp-signal-lo), var(--cp-signal) 62%, var(--cp-signal-hi)) !important;
  border-color: rgba(120, 40, 8, 0.45) !important;
  box-shadow:
    inset 0 2px 5px rgba(88, 20, 0, 0.42),
    inset 0 -1px 0 rgba(255, 255, 255, 0.22),
    0 1px 0 var(--cp-lift) !important;
}

/* The paddle itself: a small dished cap, same family as the mode keys. */
html body .switch__thumb {
  background-image: linear-gradient(
    180deg,
    var(--cp-cream-hi) 0%,
    var(--cp-cream) 52%,
    var(--cp-cream-lo) 100%
  ) !important;
  border: 1px solid var(--cp-edge) !important;
  border-radius: 5px !important;
  box-shadow:
    inset 0 1px 0 var(--cp-lift),
    inset 0 -2px 3px var(--cp-occlude),
    0 1px 2px var(--cp-cast),
    0 3px 5px rgba(60, 50, 34, 0.16) !important;
}

/* ── 40.5 SMALL KEYS — face selector, presets, chips ───────────────────
   Same key, smaller. Consistency across the panel is the point: one light,
   one material, one press. */
html body .chip {
  background-image: linear-gradient(
    180deg,
    var(--cp-cream-hi) 0%,
    var(--cp-cream) 50%,
    var(--cp-cream-lo) 100%
  ) !important;
  border: 1px solid var(--cp-edge) !important;
  border-radius: 7px !important;
  box-shadow:
    inset 0 1px 0 var(--cp-lift),
    inset 0 -2px 4px var(--cp-occlude),
    0 1px 0 var(--cp-lift),
    0 2px 3px var(--cp-cast) !important;
  transition: box-shadow 90ms ease, transform 90ms ease !important;
}

html body .chip.is-selected,
html body .chip[aria-checked="true"],
html body .chip[aria-pressed="true"] {
  background-image: linear-gradient(
    180deg,
    var(--cp-cream-lo) 0%,
    var(--cp-cream) 64%,
    var(--cp-cream-hi) 100%
  ) !important;
  box-shadow:
    inset 0 3px 5px var(--cp-occlude),
    inset 0 -1px 0 var(--cp-lift) !important;
  transform: translateY(1px) !important;
}

html body .chip:active {
  transform: translateY(1px) !important;
  box-shadow:
    inset 0 3px 5px var(--cp-occlude),
    inset 0 -1px 0 var(--cp-lift) !important;
}

/* Reduced motion keeps the depth and drops the travel. */
@media (prefers-reduced-motion: reduce) {
  html body .segmented__key,
  html body .chip,
  html body .btn--lg.btn--primary,
  html body .tt-timer__transportside .btn,
  html body .btn--icon.btn--round { transition: none !important; }
  html body .segmented__key[aria-selected="true"],
  html body .chip.is-selected,
  html body .chip:active,
  html body .segmented__key:active,
  html body .btn--lg.btn--primary:active,
  html body .tt-timer__transportside .btn:active { transform: none !important; }
}

/* ══════════════════════════════════════════════════════════════════════════
   41. THE LAMPS — pilot lights as physical objects

   The tell of a real indicator is that it EXISTS WHEN IT IS OFF. A moulded
   lens is still a lens unlit: dark ruby glass sunk in a bezel, catching the
   room across its dome. §chassis faded the lamp to `opacity: 0`, so it left
   a hole in the panel and the whole thing read as a drawn dot appearing and
   disappearing. Every lamp here is present at all times; only its filament
   changes.

   The four analogue tropes, and nothing beyond them:

     1. THE LENS      a domed cap of coloured glass, darker at its rim where
                      it curves away, seated in a hairline bezel.
     2. THE SPECULAR  a hard little catchlight where the room reflects off
                      the dome. It tracks src/light.js's --hl-x / --hl-y, so
                      the lamps are lit by the same room as the case.
     3. THE BLOOM     lit glass spills onto the panel around it. Two shadows:
                      a tight hot one, and a wide faint one that is really
                      just the panel being lit from an inch away.
     4. THE WARM-UP   a filament does not snap on. The core rises fast, the
                      bloom follows behind it. No flicker — flicker is a
                      broken lamp, and this is a Braun, not a bar sign.
   ══════════════════════════════════════════════════════════════════════════ */

:root {
  --lamp-glass: #6e2a18;
  --lamp-glass-rim: #3a1509;
  --lamp-core: #fff0e4;
  --lamp-hot: #ff9152;
  --lamp-body: #e4380d;
  --lamp-deep: #a82705;
  --lamp-bloom: 224 72 15;
}

html[data-theme="graphite"] {
  /* Unlit glass reads darker than the panel in the light, and LIGHTER than
     it in the dark — otherwise the lens vanishes into a black case. */
  --lamp-glass: #55231a;
  --lamp-glass-rim: #201009;
  --lamp-body: #ff5a22;
  --lamp-deep: #c1330a;
}

/* ── 41.1 THE PILOT LAMP — the mode selector's lens ────────────────────── */
html body .segmented__led {
  position: relative !important;
  inline-size: 7px !important;
  block-size: 7px !important;
  border-radius: 50% !important;
  /* NEVER hidden. Unlit is a state of the lamp, not its absence. */
  opacity: 1 !important;
  background-color: transparent !important;
  background-image:
    /* the specular, riding the room light */
    radial-gradient(
      circle at calc(34% + var(--hl-x, 0%) * 0.35) calc(28% + var(--hl-y, 0%) * 0.35),
      rgba(255, 255, 255, 0.55) 0%,
      rgba(255, 255, 255, 0) 34%
    ),
    /* the glass: dark at the rim where the dome turns away */
    radial-gradient(
      circle at 38% 32%,
      var(--lamp-glass) 0%,
      var(--lamp-glass-rim) 78%
    ) !important;
  /* seated in a hairline bezel, with the panel shading its lower lip */
  box-shadow:
    inset 0 -0.5px 1px rgba(0, 0, 0, 0.45),
    0 0 0 0.5px rgba(58, 21, 9, 0.55),
    0 0.5px 0 var(--cp-lift, rgba(255, 255, 255, 0.9)) !important;
  transition:
    background-image 200ms cubic-bezier(0.2, 0, 0.2, 1),
    box-shadow 260ms cubic-bezier(0.2, 0, 0.2, 1) !important;
}

/* Lit. The core comes up hot and small, the bloom spreads behind it. */
html body .segmented__key[aria-selected="true"] .segmented__led,
html body .chassis .segmented__key[aria-selected="true"] .segmented__led {
  opacity: 1 !important;
  background-image:
    radial-gradient(
      circle at calc(34% + var(--hl-x, 0%) * 0.35) calc(28% + var(--hl-y, 0%) * 0.35),
      rgba(255, 255, 255, 0.92) 0%,
      rgba(255, 255, 255, 0) 30%
    ),
    radial-gradient(
      circle at 40% 34%,
      var(--lamp-core) 0%,
      var(--lamp-hot) 26%,
      var(--lamp-body) 58%,
      var(--lamp-deep) 100%
    ) !important;
  box-shadow:
    inset 0 -0.5px 1px rgba(120, 30, 6, 0.35),
    0 0 0 0.5px rgba(120, 30, 6, 0.40),
    /* the tight hot spill */
    0 0 4px 0.5px rgba(var(--lamp-bloom) / 0.62),
    /* the wide faint one — the panel lit from an inch away */
    0 0 11px 2px rgba(var(--lamp-bloom) / 0.26) !important;
}

/* ── 41.2 THE POWER LAMP — the case knows it is running ────────────────
   One lamp on the chassis, beside the nameplate, wired to the machine and
   not to any one control. Dark while idle, lit while a run is on, and
   holding a dimmed ember while paused — which is what a real device does
   with a latching pilot: it does not forget it is mid-cycle. */
html body .brand::after {
  content: '' !important;
  inline-size: 7px !important;
  block-size: 7px !important;
  border-radius: 50% !important;
  flex: 0 0 auto !important;
  margin-inline-start: 0.5rem !important;
  align-self: center !important;
  display: inline-block !important;
  vertical-align: middle !important;
  background-image:
    radial-gradient(
      circle at calc(34% + var(--hl-x, 0%) * 0.35) calc(28% + var(--hl-y, 0%) * 0.35),
      rgba(255, 255, 255, 0.55) 0%,
      rgba(255, 255, 255, 0) 34%
    ),
    radial-gradient(circle at 38% 32%, var(--lamp-glass) 0%, var(--lamp-glass-rim) 78%) !important;
  box-shadow:
    inset 0 -0.5px 1px rgba(0, 0, 0, 0.45),
    0 0 0 0.5px rgba(58, 21, 9, 0.55),
    0 0.5px 0 var(--cp-lift, rgba(255, 255, 255, 0.9)) !important;
  transition:
    background-image 200ms cubic-bezier(0.2, 0, 0.2, 1),
    box-shadow 260ms cubic-bezier(0.2, 0, 0.2, 1) !important;
}

html[data-run="running"] body .brand::after,
html[data-run="overtime"] body .brand::after {
  background-image:
    radial-gradient(
      circle at calc(34% + var(--hl-x, 0%) * 0.35) calc(28% + var(--hl-y, 0%) * 0.35),
      rgba(255, 255, 255, 0.92) 0%,
      rgba(255, 255, 255, 0) 30%
    ),
    radial-gradient(
      circle at 40% 34%,
      var(--lamp-core) 0%,
      var(--lamp-hot) 26%,
      var(--lamp-body) 58%,
      var(--lamp-deep) 100%
    ) !important;
  box-shadow:
    inset 0 -0.5px 1px rgba(120, 30, 6, 0.35),
    0 0 0 0.5px rgba(120, 30, 6, 0.40),
    0 0 4px 0.5px rgba(var(--lamp-bloom) / 0.62),
    0 0 11px 2px rgba(var(--lamp-bloom) / 0.26) !important;
}

/* Paused: the ember. Half the core, none of the wide bloom — a lamp on a
   dropped voltage, which is exactly what "held mid-cycle" looks like. */
html[data-run="paused"] body .brand::after {
  background-image:
    radial-gradient(
      circle at calc(34% + var(--hl-x, 0%) * 0.35) calc(28% + var(--hl-y, 0%) * 0.35),
      rgba(255, 255, 255, 0.62) 0%,
      rgba(255, 255, 255, 0) 32%
    ),
    radial-gradient(
      circle at 40% 34%,
      var(--lamp-hot) 0%,
      var(--lamp-body) 44%,
      var(--lamp-deep) 100%
    ) !important;
  box-shadow:
    inset 0 -0.5px 1px rgba(120, 30, 6, 0.40),
    0 0 0 0.5px rgba(120, 30, 6, 0.45),
    0 0 3px 0 rgba(var(--lamp-bloom) / 0.30) !important;
}

/* A lamp is a light, not an animation. Reduced motion keeps every state and
   simply stops the warm-up ramp. */
@media (prefers-reduced-motion: reduce) {
  html body .segmented__led,
  html body .brand::after { transition: none !important; }
}

/* ── 42. A SWITCH IS NOT A KEY ──────────────────────────────────────────
   `repeat` is a label with a switch beside it, but it inherits `.chip`, so
   §40 gave it a cap face, a collar and a cast shadow — a pressable key that
   cannot be pressed, wrapped around the only control that is actually live.
   Two objects claiming to be one. The chip carries no surface here; the
   switch is the control and the word is just its legend. */
/* `:has()` was the wrong tool: the switch is the chip's SIBLING, not its
   child, so the chip matched nothing and kept its cap. The repeat label is
   the one chip in the legend with no radio role — the face keys are the
   radios, the presets live in .ps-item. */
html body .tt-timer__legend > .chip:not([role]),
html body .chip:has(.switch),
html body .chip:has(input[type="checkbox"]),
html body .chip:has([role="switch"]) {
  background: none !important;
  background-image: none !important;
  border: 0 !important;
  box-shadow: none !important;
  transform: none !important;
  padding-inline: 0.25rem !important;
}

html body .tt-timer__legend > .chip:not([role]):active,
html body .chip:has(.switch):active,
html body .chip:has(input[type="checkbox"]):active {
  transform: none !important;
  box-shadow: none !important;
}

/* ── 43. THE CAP MASKS MUST GO ──────────────────────────────────────────
   §35 masked the edges of these controls to transparent. That was correct
   when they were PHOTOGRAPHS: the shots had square corners and a slab of
   backdrop around the cap, and the mask cropped it away.

   Nothing is photographed any more. §40 draws every cap, so the mask now
   eats the real edge instead of a fake one — it was fading the ends of the
   start key to transparent and letting the panel show through, which read as
   two dark caps on the ends of the bar and defeated three rewrites of the
   gradient underneath it. The border-radius is the shape now. */
html body .tt-timer__transportside .btn,
html body .tt-timer__transport .btn,
html body .chassis__tools .btn--icon,
html body .btn--icon.btn--round,
html body .btn--lg.btn--primary,
html[data-theme="ceramic"] body .btn--lg.btn--primary,
html[data-theme="graphite"] body .btn--lg.btn--primary,
html body .segmented__key,
html body .chip {
  -webkit-mask-image: none !important;
  mask-image: none !important;
  -webkit-mask-composite: unset !important;
  mask-composite: unset !important;
}

/* ── 44. THE PATINA MUST NOT PAINT THE BEZEL BLACK ──────────────────────
   css/wear.css turns .dial__wear into an isolated group filled with
   --wear-void (#000) and screens the whole GROUP onto the dial. On paper that
   is elegant and free: screen is associative, so screening four mattes onto
   black and the result onto the artwork equals screening each onto the
   artwork, and it collapses four per-frame blends into one cacheable layer.

   In practice the group's `mix-blend-mode: screen` is not compositing against
   the artwork here, so the black backdrop paints straight over the chrome and
   the instrument grows an opaque black ring. It only shows once `data-wear`
   flips to "on" — i.e. after the app has been USED — which is exactly when a
   patina feature must not be the thing that breaks the dial.

   wear.css documents the alternative itself (css/dial.css §10's original
   suggestion): hang the mattes straight in the box so each screens onto the
   artwork. Pixel-identical by its own argument, and it cannot fail this way,
   because no element is ever filled with opaque black. The mattes keep their
   own `screen` and their own --wear-void base, so the patina still accrues;
   only the group wrapper stands down. */
:root[data-wear="on"] .dial .dial__wear,
:root[data-wear="on"] .dial .tt-wear {
  background-color: transparent !important;
  isolation: auto !important;
  mix-blend-mode: normal !important;
}

/* ── 45. THE CAPS MUST FOLLOW THE THEME ─────────────────────────────────
   Rewriting §40's face to "almost flat" traded the themed tokens for literal
   cream hex values, which is fine until the case turns black: the transport
   kept its ivory face and its dark label vanished into it. The face is a
   token again, per theme, and the rim highlight flips from white-on-cream to
   a faint sheen — a dark cap catches far less of the room than a light one,
   so lifting it the same amount would look like plastic lit from inside. */
:root {
  --cp-face-hi: #f7f3ea;
  --cp-face-mid: #f1ece0;
  --cp-face-lo: #e7e0d0;
  --cp-face-rim-lo: rgba(120, 106, 78, 0.30);
  --cp-face-lift: rgba(255, 255, 255, 1);
  --cp-face-sheen: rgba(255, 255, 255, 0.75);
  --cp-face-ring: rgba(120, 108, 84, 0.16);
}

html[data-theme="graphite"] {
  --cp-face-hi: #3e3e43;
  --cp-face-mid: #333337;
  --cp-face-lo: #232327;
  --cp-face-rim-lo: rgba(0, 0, 0, 0.75);
  --cp-face-lift: rgba(255, 255, 255, 0.18);
  --cp-face-sheen: rgba(255, 255, 255, 0.10);
  --cp-face-ring: rgba(0, 0, 0, 0.55);
}

html body .tt-timer__transportside .btn,
html body .tt-timer__transport .btn:not(.btn--primary),
html body .chassis__tools .btn--icon,
html body .btn--icon.btn--round {
  background-image:
    radial-gradient(
      ellipse 70% 46% at 44% 16%,
      var(--cp-face-sheen) 0%,
      rgba(255, 255, 255, 0) 100%
    ),
    linear-gradient(
      178deg,
      var(--cp-face-hi) 0%,
      var(--cp-face-mid) 52%,
      var(--cp-face-lo) 100%
    ) !important;
  box-shadow:
    inset 0 1.5px 0 -0.5px var(--cp-face-lift),
    inset 0 -1.5px 0 -0.5px var(--cp-face-rim-lo),
    inset 0 -5px 5px -4px var(--cp-occlude),
    0 0 0 1px var(--cp-face-ring),
    0 1px 1px var(--cp-cast),
    0 3px 4px -1px var(--cp-cast),
    0 8px 12px -2px var(--cp-cast),
    0 16px 24px -6px var(--cp-cast) !important;
}

html body .tt-timer__transportside .btn:active,
html body .chassis__tools .btn--icon:active,
html body .btn--icon.btn--round:active {
  background-image:
    radial-gradient(
      ellipse 70% 46% at 46% 26%,
      var(--cp-face-sheen) 0%,
      rgba(255, 255, 255, 0) 100%
    ),
    linear-gradient(
      178deg,
      var(--cp-face-lo) 0%,
      var(--cp-face-mid) 60%,
      var(--cp-face-hi) 100%
    ) !important;
}

/* The segmented keys and chips share the same face, so they share the tokens. */
html body .segmented__key,
html body .chip {
  background-image: linear-gradient(
    178deg,
    var(--cp-face-hi) 0%,
    var(--cp-face-mid) 52%,
    var(--cp-face-lo) 100%
  ) !important;
}

html body .segmented__key[aria-selected="true"],
html body .segmented__key.is-active,
html body .chip.is-selected,
html body .chip[aria-checked="true"],
html body .chip[aria-pressed="true"] {
  background-image: linear-gradient(
    178deg,
    var(--cp-face-lo) 0%,
    var(--cp-face-mid) 62%,
    var(--cp-face-hi) 100%
  ) !important;
}

/* Legends sit on the case, not on a cap, so they take the case's ink. The
   FACE label was reading at about 2:1 against the black chassis. */
html[data-theme="graphite"] body .tt-timer__legend,
html[data-theme="graphite"] body .tt-timer__legend .t-label {
  color: #a8a49c !important;
  -webkit-text-fill-color: #a8a49c !important;
}

/* ══════════════════════════════════════════════════════════════════════════
   46. NOT A CALCULATOR

   Four passes produced keypad buttons: flat-topped caps, one thin even ring,
   all identical, sitting ON the panel. That is exactly what a calculator is,
   and it is not what the reference plate shows.

   What the reference actually has, and what a keypad never does:

     1. THE WELL      the panel is CUT AWAY around each control — a recess
                      with its own shading, dark where the light is blocked at
                      the top lip, catching light along the bottom. The cap
                      floats inside it. Two parts, not one.
     2. THE LENS      the cap is properly convex — a hot specular high and
                      left, a dark crescent raked low and right, and a warm
                      bounce underneath it where light kicks back off the
                      well. A keypad cap is flat with a straight side wall.
     3. DIFFERENCE    a keypad is one part repeated. An instrument is a
                      family: the signal is a glossy jewel standing proud, the
                      secondary keys are matte and sunk. They must not share a
                      finish.

   The well is a ::before behind the cap rather than a border, because a
   border is part of the same object and always reads as trim on one part.
   ══════════════════════════════════════════════════════════════════════════ */

html body .tt-timer__transportside .btn,
html body .chassis__tools .btn--icon,
html body .btn--icon.btn--round {
  position: relative !important;
  overflow: visible !important;
  /* the cap shrinks slightly so the well can show around it */
  background-clip: padding-box !important;
}

/* THE WELL — the panel cut back around the control. Shaded opposite to the
   cap: dark at the top lip where the cap overhangs, light along the bottom. */
html body .tt-timer__transportside .btn::before,
html body .chassis__tools .btn--icon::before,
html body .btn--icon.btn--round::before {
  content: '' !important;
  position: absolute !important;
  inset: -5px !important;
  border-radius: 50% !important;
  z-index: -1 !important;
  background: linear-gradient(
    168deg,
    var(--cp-well-lo) 0%,
    var(--cp-well-mid) 46%,
    var(--cp-well-hi) 100%
  ) !important;
  box-shadow:
    inset 0 2px 3px rgba(52, 44, 28, 0.30),
    inset 0 -1px 0 rgba(255, 255, 255, 0.55),
    0 1px 0 rgba(255, 255, 255, 0.65) !important;
  pointer-events: none !important;
}

:root {
  --cp-well-lo: #cfc7b4;
  --cp-well-mid: #ded7c7;
  --cp-well-hi: #efeadf;
  --cp-bounce: rgba(255, 246, 226, 0.55);
}

html[data-theme="graphite"] {
  --cp-well-lo: #0d0d0f;
  --cp-well-mid: #171719;
  --cp-well-hi: #2a2a2e;
  --cp-bounce: rgba(255, 255, 255, 0.07);
}

/* THE LENS — properly convex. Off-centre hot spot, raked dark crescent, and
   a bounce along the bottom from the well. */
html body .tt-timer__transportside .btn,
html body .chassis__tools .btn--icon,
html body .btn--icon.btn--round {
  background-image:
    /* the specular: small, high, left, and HARD — a soft one reads as matte */
    radial-gradient(
      ellipse 40% 26% at 36% 18%,
      rgba(255, 255, 255, 0.95) 0%,
      rgba(255, 255, 255, 0.30) 55%,
      rgba(255, 255, 255, 0) 100%
    ),
    /* the bounce: light kicking back off the well onto the cap's underside */
    radial-gradient(
      ellipse 60% 34% at 56% 96%,
      var(--cp-bounce) 0%,
      rgba(255, 255, 255, 0) 100%
    ),
    /* the body: a sphere lit from the upper left, not a flat top */
    radial-gradient(
      circle at 36% 26%,
      var(--cp-face-hi) 0%,
      var(--cp-face-mid) 42%,
      var(--cp-face-lo) 78%,
      var(--cp-cream-deep) 100%
    ) !important;
  box-shadow:
    inset 0 1px 0 -0.5px var(--cp-face-lift),
    /* the crescent, raked to the lower right, away from the light */
    inset -2px -5px 7px -4px var(--cp-occlude),
    0 1px 1px var(--cp-cast),
    0 3px 5px -1px var(--cp-cast),
    0 7px 12px -3px var(--cp-cast) !important;
}

html body .tt-timer__transportside .btn:active,
html body .chassis__tools .btn--icon:active,
html body .btn--icon.btn--round:active {
  transform: translateY(2px) scale(0.985) !important;
}
html body .tt-timer__transportside .btn:active::before,
html body .chassis__tools .btn--icon:active::before,
html body .btn--icon.btn--round:active::before {
  box-shadow:
    inset 0 3px 5px rgba(52, 44, 28, 0.42),
    inset 0 -1px 0 rgba(255, 255, 255, 0.45) !important;
}

/* THE SIGNAL stands proud and GLOSSY — the one part with a wet finish, so it
   cannot be mistaken for another key in the row. */
html body .btn--lg.btn--primary,
html[data-theme="ceramic"] body .btn--lg.btn--primary,
html[data-theme="graphite"] body .btn--lg.btn--primary {
  background-image:
    /* the gloss band: tight, bright, and confined to the top third */
    linear-gradient(
      180deg,
      rgba(255, 255, 255, 0.55) 0%,
      rgba(255, 255, 255, 0.16) 26%,
      rgba(255, 255, 255, 0) 46%
    ),
    /* the bounce off the panel */
    radial-gradient(
      ellipse 70% 40% at 50% 104%,
      rgba(255, 190, 150, 0.42) 0%,
      rgba(255, 255, 255, 0) 100%
    ),
    linear-gradient(
      178deg,
      #f4753f 0%,
      #e85520 22%,
      #d8420e 56%,
      #bb330a 84%,
      #9d2606 100%
    ) !important;
  box-shadow:
    inset 0 1.5px 0 -0.5px rgba(255, 255, 255, 0.62),
    inset 0 -2px 0 -1px rgba(80, 18, 0, 0.55),
    0 1px 1px rgba(60, 20, 4, 0.30),
    0 4px 6px -1px rgba(60, 20, 4, 0.30),
    0 10px 16px -4px rgba(60, 20, 4, 0.28),
    0 18px 30px -8px rgba(176, 55, 14, 0.34) !important;
}

/* ══════════════════════════════════════════════════════════════════════════
   47. HEIGHT IS THE WHOLE POINT

   "Tall buttons" was in the brief from the start and I kept solving for
   SHADING instead. That is why every pass came back reading as a calculator:
   a keypad cap is flat and short, and no amount of specular fixes a control
   with no side to it. A Braun key stands several millimetres off the panel
   and you can SEE the wall — that silhouette is what the eye reads as a
   physical control, long before any highlight.

   So the cap is extruded: a stack of hard 1px shadows steps down the side
   wall, darkening as it goes because the wall curves away from the light,
   and the cast shadow starts below the wall's base rather than the face's.
   Pressing collapses the stack — the cap travels DOWN its own wall, which is
   the motion a real key makes and the reason the press finally feels like
   one.
   ══════════════════════════════════════════════════════════════════════════ */

:root {
  --cp-wall-1: #e6dfcd;
  --cp-wall-2: #dcd4c0;
  --cp-wall-3: #d0c7b1;
  --cp-wall-4: #c4baa2;
  --cp-wall-5: #b8ad93;
  --cp-wall-6: #aca081;
}

html[data-theme="graphite"] {
  --cp-wall-1: #2c2c30;
  --cp-wall-2: #26262a;
  --cp-wall-3: #202024;
  --cp-wall-4: #1a1a1e;
  --cp-wall-5: #141418;
  --cp-wall-6: #0e0e12;
}

html body .tt-timer__transportside .btn,
html body .chassis__tools .btn--icon,
html body .btn--icon.btn--round {
  box-shadow:
    /* the top face's own lip */
    inset 0 1px 0 -0.5px var(--cp-face-lift),
    inset -2px -4px 6px -4px var(--cp-occlude),
    /* THE SIDE WALL — six steps down, each darker as it turns away */
    0 1px 0 var(--cp-wall-1),
    0 2px 0 var(--cp-wall-2),
    0 3px 0 var(--cp-wall-3),
    0 4px 0 var(--cp-wall-4),
    0 5px 0 var(--cp-wall-5),
    0 6px 0 var(--cp-wall-6),
    /* the cast, thrown from the BASE of the wall, not the top face */
    0 8px 6px -3px var(--cp-cast),
    0 12px 14px -4px var(--cp-cast),
    0 20px 26px -8px rgba(52, 44, 28, 0.20) !important;
}

/* Pressed: the cap travels down its own wall. Two steps left, not six. */
html body .tt-timer__transportside .btn:active,
html body .chassis__tools .btn--icon:active,
html body .btn--icon.btn--round:active {
  transform: translateY(4px) !important;
  box-shadow:
    inset 0 2px 3px -1px var(--cp-occlude),
    0 1px 0 var(--cp-wall-4),
    0 2px 0 var(--cp-wall-6),
    0 3px 4px -1px var(--cp-cast) !important;
}

/* The well sits at the wall's base, so it must not travel with the cap. */
html body .tt-timer__transportside .btn::before,
html body .chassis__tools .btn--icon::before,
html body .btn--icon.btn--round::before {
  inset: -4px -4px -10px !important;
}
html body .tt-timer__transportside .btn:active::before,
html body .chassis__tools .btn--icon:active::before,
html body .btn--icon.btn--round:active::before {
  inset: -4px -4px -6px !important;
  transform: translateY(-4px) !important;
}

/* THE SIGNAL is the tallest thing on the panel — it is the one you hit. */
html body .btn--lg.btn--primary,
html[data-theme="ceramic"] body .btn--lg.btn--primary,
html[data-theme="graphite"] body .btn--lg.btn--primary {
  box-shadow:
    inset 0 1.5px 0 -0.5px rgba(255, 255, 255, 0.62),
    inset 0 -2px 0 -1px rgba(80, 18, 0, 0.45),
    0 1px 0 #cf3d0c,
    0 2px 0 #c23809,
    0 3px 0 #b53307,
    0 4px 0 #a72e06,
    0 5px 0 #992905,
    0 6px 0 #8b2404,
    0 7px 0 #7d1f03,
    0 10px 8px -3px rgba(60, 20, 4, 0.34),
    0 15px 18px -5px rgba(60, 20, 4, 0.30),
    0 24px 32px -10px rgba(176, 55, 14, 0.34) !important;
}

html body .btn--lg.btn--primary:active {
  transform: translateY(5px) !important;
  box-shadow:
    inset 0 2px 4px -1px rgba(80, 18, 0, 0.55),
    0 1px 0 #a72e06,
    0 2px 0 #8b2404,
    0 4px 5px -1px rgba(60, 20, 4, 0.34) !important;
}

/* The mode keys are tall too — shorter than the transport, because they are
   selectors rather than actions, but never flat. */
html body .segmented__key {
  box-shadow:
    inset 0 1px 0 -0.5px var(--cp-face-lift),
    inset 0 -3px 5px -3px var(--cp-occlude),
    0 1px 0 var(--cp-wall-2),
    0 2px 0 var(--cp-wall-4),
    0 3px 0 var(--cp-wall-6),
    0 5px 5px -2px var(--cp-cast),
    0 9px 12px -4px rgba(52, 44, 28, 0.18) !important;
}

html body .segmented__key[aria-selected="true"],
html body .segmented__key.is-active {
  transform: translateY(3px) !important;
  box-shadow:
    inset 0 2px 4px -1px var(--cp-occlude),
    inset 0 -1px 0 var(--cp-face-lift),
    0 1px 0 var(--cp-wall-6),
    0 2px 3px -1px var(--cp-cast) !important;
}

/* ══════════════════════════════════════════════════════════════════════════
   48. THE CONTROL STUDY — rebuilt on the reference implementation

   Supersedes the surface work in §40, §46 and §47. Those failed on four
   specific counts, all visible the moment the study is put beside them:

   1. LIGHT DIRECTION. Mine ran `linear-gradient(178deg, …)` — straight down.
      A cap lit from directly above has no side that faces the light, which is
      precisely what a calculator key looks like. The study fixes one
      world-space key at 135deg and every gradient runs along it. That single
      angle is most of the difference.

   2. THE BOTTOM EDGE IS LIGHT, NOT DARK. I raked a shadow across the bottom
      of every cap. A real object sitting on a pale panel catches a warm
      BOUNCE on its underside — `inset 0 -1.5px 1.5px rgba(255,250,242,.42)`
      — and the occlusion goes to the lower RIGHT, away from the key. I had
      the darkest value where the study has one of the lightest.

   3. WALL DEPTH. Six steps of extrusion reads as a cartoon. The study uses
      two, then a tight contact shadow, then one wide ambient. Height comes
      from the contact shadow, not from stacking wall segments.

   4. SHADOWS ARE WARM. rgb(58,48,38), never neutral black, because the light
      is warm and the panel is warm. Neutral shadows on a cream panel read as
      printed rather than lit.

   Plus two details that do disproportionate work: panel grain to kill
   gradient banding, and seam occlusion between adjacent keys.
   ══════════════════════════════════════════════════════════════════════════ */

:root {
  --k: 135deg;             /* the world-space key light. Never override. */
  --sh: 58, 48, 38;        /* warm shadow, matching a warm light */
  --cap-hi: #fdfcf9;
  --cap-mid: #f2efe8;
  --cap-lo: #e2ded6;
  --cap-bounce: rgba(255, 250, 242, 0.42);
  --cap-rim: rgba(255, 255, 255, 0.95);
  --cap-wall-1: #d3cfc6;
  --cap-wall-2: #c6c2b9;
  --lamp-sig: #f0501e;
  --lamp-sig-hot: #ff7a4a;
  --lamp-sig-lo: #c83d14;
}

html[data-theme="graphite"] {
  --sh: 0, 0, 0;
  --cap-hi: #46464b;
  --cap-mid: #35353a;
  --cap-lo: #26262b;
  --cap-bounce: rgba(255, 250, 242, 0.10);
  --cap-rim: rgba(255, 255, 255, 0.22);
  --cap-wall-1: #1d1d21;
  --cap-wall-2: #131316;
}

/* ── 48.1 ROUND PUSH CAPS — transport and tools ───────────────────────── */
html body .tt-timer__transportside .btn,
html body .tt-timer__transport .btn:not(.btn--primary),
html body .chassis__tools .btn--icon,
html body .btn--icon.btn--round {
  border: 0 !important;
  border-radius: 50% !important;
  background-color: transparent !important;
  background-image: linear-gradient(
    var(--k),
    var(--cap-hi) 0%,
    var(--cap-mid) 45%,
    var(--cap-lo) 100%
  ) !important;
  box-shadow:
    inset 0 1.5px 0.5px var(--cap-rim),
    inset 0 -1.5px 1.5px var(--cap-bounce),
    inset -1.5px -1.5px 3.5px rgba(var(--sh), 0.17),
    0 3px 0 var(--cap-wall-1),
    0 4px 0 var(--cap-wall-2),
    0 5px 3px rgba(var(--sh), 0.42),
    0 10px 22px rgba(var(--sh), 0.16) !important;
  transition: transform 100ms cubic-bezier(0.3, 0.7, 0.4, 1), box-shadow 100ms !important;
}

html body .tt-timer__transportside .btn::before,
html body .chassis__tools .btn--icon::before,
html body .btn--icon.btn--round::before { content: none !important; }

html body .tt-timer__transportside .btn:active,
html body .chassis__tools .btn--icon:active,
html body .btn--icon.btn--round:active {
  transform: translateY(3px) !important;
  box-shadow:
    inset 0 1px 0.5px rgba(255, 255, 255, 0.7),
    inset 0 2px 4px rgba(var(--sh), 0.2),
    0 1px 0 var(--cap-wall-1),
    0 1px 2px rgba(var(--sh), 0.35) !important;
}

/* ── 48.2 THE SIGNAL — the lamp cap ───────────────────────────────────── */
html body .btn--lg.btn--primary,
html[data-theme="ceramic"] body .btn--lg.btn--primary,
html[data-theme="graphite"] body .btn--lg.btn--primary {
  border: 0 !important;
  border-radius: 999px !important;
  background-color: transparent !important;
  background-image:
    radial-gradient(
      ellipse 68% 150% at 34% 22%,
      var(--lamp-sig-hot) 0%,
      rgba(255, 122, 74, 0) 62%
    ),
    linear-gradient(
      var(--k),
      #f4703c 0%,
      var(--lamp-sig) 46%,
      var(--lamp-sig-lo) 100%
    ) !important;
  box-shadow:
    inset 0 1.5px 0.5px rgba(255, 190, 160, 0.85),
    inset 0 -2px 4px rgba(120, 30, 0, 0.40),
    0 3px 0 #a83313,
    0 4px 0 #8f2a0e,
    0 5px 3px rgba(90, 30, 10, 0.45),
    0 10px 26px rgba(240, 80, 30, 0.30) !important;
  transition: transform 100ms cubic-bezier(0.3, 0.7, 0.4, 1), box-shadow 100ms !important;
}

html body .btn--lg.btn--primary:active {
  transform: translateY(3px) !important;
  box-shadow:
    inset 0 1px 0.5px rgba(255, 190, 160, 0.7),
    inset 0 2px 4px rgba(120, 30, 0, 0.35),
    0 1px 0 #8f2a0e,
    0 1px 2px rgba(90, 30, 10, 0.4) !important;
}

/* ── 48.3 THE KEY TRAY — clock / timer / alarm ────────────────────────── */
html body .segmented {
  padding: 4px !important;
  gap: 2px !important;
  border: 0 !important;
  border-radius: 5px !important;
  background-image: linear-gradient(180deg, #cfcbc2, #e6e2da) !important;
  box-shadow:
    inset 0 2px 3px rgba(var(--sh), 0.42),
    inset 0 -1px 0 rgba(255, 255, 255, 0.7),
    0 1px 0 rgba(255, 255, 255, 0.8) !important;
}

html[data-theme="graphite"] body .segmented {
  background-image: linear-gradient(180deg, #17171a, #2a2a2e) !important;
}

html body .segmented__key {
  position: relative !important;
  border: 0 !important;
  border-radius: 4px !important;
  background-image: linear-gradient(
    var(--k),
    var(--cap-hi) 0%,
    var(--cap-mid) 46%,
    var(--cap-lo) 100%
  ) !important;
  box-shadow:
    inset 0 1.5px 0.5px var(--cap-rim),
    inset 1px 0 1px rgba(255, 255, 255, 0.5),
    inset 0 -1.5px 1.5px var(--cap-bounce),
    inset -1.5px -1.5px 3px rgba(var(--sh), 0.16),
    0 2px 0 var(--cap-wall-1),
    0 3px 0 var(--cap-wall-2),
    0 4px 2px rgba(var(--sh), 0.45),
    0 8px 16px rgba(var(--sh), 0.16) !important;
  transition: transform 110ms cubic-bezier(0.3, 0.7, 0.4, 1), box-shadow 110ms !important;
}

/* Seam occlusion — the shadow one key casts into the gap beside it. Small,
   and it is a large part of why a row reads as separate physical parts. */
html body .segmented__key::after {
  content: '' !important;
  position: absolute !important;
  top: 2px !important;
  bottom: 2px !important;
  right: -3px !important;
  width: 3px !important;
  background: linear-gradient(90deg, rgba(28, 27, 25, 0.34), rgba(28, 27, 25, 0)) !important;
  pointer-events: none !important;
}
html body .segmented__key:last-child::after { display: none !important; }

html body .segmented__key[aria-selected="true"],
html body .segmented__key.is-active {
  transform: translateY(3px) !important;
  background-image: linear-gradient(
    var(--k),
    #efebe3 0%,
    #e4e0d7 55%,
    #d6d2c9 100%
  ) !important;
  box-shadow:
    inset 0 2px 3px rgba(var(--sh), 0.34),
    inset 0 -1px 0 rgba(255, 255, 255, 0.5),
    0 1px 0 #cfcbc2,
    0 1px 2px rgba(var(--sh), 0.32) !important;
}

html[data-theme="graphite"] body .segmented__key[aria-selected="true"],
html[data-theme="graphite"] body .segmented__key.is-active {
  background-image: linear-gradient(var(--k), #1e1e22 0%, #26262a 55%, #2e2e33 100%) !important;
}

/* ── 48.4 THE LAMP LENS — grey glass unlit, not dark red ──────────────── */
html body .segmented__led {
  inline-size: 7px !important;
  block-size: 7px !important;
  border-radius: 50% !important;
  opacity: 1 !important;
  background-image: radial-gradient(circle at 38% 32%, #6d6a64, #3c3a36) !important;
  box-shadow:
    inset 0 1px 1px rgba(0, 0, 0, 0.5),
    0 1px 0 rgba(255, 255, 255, 0.6) !important;
  transition: background-image 140ms, box-shadow 140ms !important;
}

html body .segmented__key[aria-selected="true"] .segmented__led,
html body .chassis .segmented__key[aria-selected="true"] .segmented__led {
  background-image: radial-gradient(
    circle at 38% 30%,
    var(--lamp-sig-hot),
    var(--lamp-sig) 55%,
    var(--lamp-sig-lo)
  ) !important;
  box-shadow:
    inset 0 -1px 2px rgba(120, 30, 0, 0.5),
    0 0 7px rgba(240, 80, 30, 0.75),
    0 1px 0 rgba(255, 255, 255, 0.4) !important;
}

/* The power lamp on the chassis takes the same glass. */
html body .brand::after {
  background-image: radial-gradient(circle at 38% 32%, #6d6a64, #3c3a36) !important;
  box-shadow:
    inset 0 1px 1px rgba(0, 0, 0, 0.5),
    0 1px 0 rgba(255, 255, 255, 0.6) !important;
}
html[data-run="running"] body .brand::after,
html[data-run="overtime"] body .brand::after {
  background-image: radial-gradient(
    circle at 38% 30%,
    var(--lamp-sig-hot),
    var(--lamp-sig) 55%,
    var(--lamp-sig-lo)
  ) !important;
  box-shadow:
    inset 0 -1px 2px rgba(120, 30, 0, 0.5),
    0 0 7px rgba(240, 80, 30, 0.75),
    0 1px 0 rgba(255, 255, 255, 0.4) !important;
}

/* ── 48.5 CHIPS — the same cap, smaller ───────────────────────────────── */
html body .chip {
  border: 0 !important;
  border-radius: 4px !important;
  background-image: linear-gradient(
    var(--k),
    var(--cap-hi) 0%,
    var(--cap-mid) 46%,
    var(--cap-lo) 100%
  ) !important;
  box-shadow:
    inset 0 1.5px 0.5px var(--cap-rim),
    inset 0 -1.5px 1.5px var(--cap-bounce),
    inset -1.5px -1.5px 3px rgba(var(--sh), 0.16),
    0 2px 0 var(--cap-wall-1),
    0 3px 2px rgba(var(--sh), 0.38),
    0 7px 14px rgba(var(--sh), 0.14) !important;
  transition: transform 110ms cubic-bezier(0.3, 0.7, 0.4, 1), box-shadow 110ms !important;
}

html body .chip.is-selected,
html body .chip[aria-checked="true"],
html body .chip[aria-pressed="true"],
html body .chip:active {
  transform: translateY(2px) !important;
  box-shadow:
    inset 0 2px 3px rgba(var(--sh), 0.34),
    inset 0 -1px 0 rgba(255, 255, 255, 0.5),
    0 1px 0 var(--cap-wall-1),
    0 1px 2px rgba(var(--sh), 0.30) !important;
}

/* The repeat legend still carries no cap. */
html body .tt-timer__legend > .chip:not([role]) {
  background-image: none !important;
  box-shadow: none !important;
  transform: none !important;
}

/* ── 48.6 THE ROCKER — switches ───────────────────────────────────────── */
html body .switch__track {
  border: 0 !important;
  border-radius: 4px !important;
  background-image: linear-gradient(180deg, #b33512, #8e2a0e) !important;
  background-color: #5a1b08 !important;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.5) !important;
}

html body .switch__input:checked + .switch__track,
html body .switch.is-on .switch__track {
  background-image: linear-gradient(180deg, #ff8250, #ee5020) !important;
  background-color: #ee5020 !important;
}

html body .switch__thumb {
  border: 0 !important;
  border-radius: 3px !important;
  background-image: linear-gradient(var(--k), #fbf9f5, #edeae3 50%, #d9d5cc) !important;
  box-shadow:
    inset 0 1.5px 0.5px rgba(255, 255, 255, 0.95),
    inset 0 -1px 1px rgba(255, 250, 242, 0.4),
    inset -1px -1px 2px rgba(var(--sh), 0.18),
    0 2px 2px rgba(var(--sh), 0.42),
    0 5px 10px rgba(var(--sh), 0.16) !important;
}

/* ── 48.7 PANEL GRAIN ─────────────────────────────────────────────────
   Kills gradient banding and sells moulded plastic. Cheap, static, and the
   single highest-yield detail in the whole study. */
html body .app-shell::after {
  content: '' !important;
  position: absolute !important;
  inset: 0 !important;
  pointer-events: none !important;
  z-index: 0 !important;
  opacity: 0.05 !important;
  mix-blend-mode: overlay !important;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E") !important;
}
html body .app-shell { position: relative !important; }

@media (prefers-reduced-motion: reduce) {
  html body .segmented__key,
  html body .chip,
  html body .btn--lg.btn--primary,
  html body .tt-timer__transportside .btn,
  html body .btn--icon.btn--round { transition: none !important; }
}

/* ══════════════════════════════════════════════════════════════════════════
   49. SPEC COMPLIANCE — the six things §48 still had wrong

   Checked against the replication spec. §48 got the light angle, the warm
   shadow tint, the bounce and the seam occlusion right. These six it did not:

   1. THE RIM IS AN ARC, NOT A BAND. `inset 0 1.5px …` lights the entire top
      edge evenly, which is a band. The spec is explicit: upper-LEFT arc only,
      roughly 8 o'clock to 1 o'clock, never a full ring. A diagonal offset
      (`inset 1.4px 1.4px`) puts the highlight where the light actually is and
      lets it fall off around the curve on its own.

   2. NO PURE WHITE. rgba(255,255,255,.95) on warm plastic reads as blue-white
      and instantly plastic-in-the-bad-sense. Plastic edges catch WARMTH:
      255,252,245.

   3. THE CHAMFER WAS MISSING. Face and wall met with nothing between them.
      The 2–3px ring between the two is what makes an edge look machined
      rather than blurred.

   4. THE LAMP DID NOT BLEED. A bulb behind translucent plastic throws light
      onto the panel around it. Without that, the cap is a coloured shape.

   5. LABELS WERE OVERLAID, NOT SILKSCREENED. A 1px light line under the
      glyph is what puts ink ON the panel rather than above it.

   6. FOCUS BROKE THE MATERIAL. A ring drawn inside the housing cuts the cap.
      It belongs outside, where it is chrome rather than surface.
   ══════════════════════════════════════════════════════════════════════════ */

:root {
  --rim-warm: rgba(255, 252, 245, 0.92);
  --rim-bounce: rgba(255, 252, 245, 0.35);
  --chamfer-hi: rgba(255, 252, 245, 0.55);
  --chamfer-lo: rgba(58, 48, 38, 0.22);
}

html[data-theme="graphite"] {
  --rim-warm: rgba(255, 250, 240, 0.20);
  --rim-bounce: rgba(255, 250, 240, 0.09);
  --chamfer-hi: rgba(255, 250, 240, 0.12);
  --chamfer-lo: rgba(0, 0, 0, 0.55);
}

/* 49.1 — round caps: arc rim, chamfer, warm values */
html body .tt-timer__transportside .btn,
html body .tt-timer__transport .btn:not(.btn--primary),
html body .chassis__tools .btn--icon,
html body .btn--icon.btn--round {
  box-shadow:
    /* the rim, offset up-LEFT so it reads as an arc from 8 to 1 o'clock */
    inset 1.4px 1.4px 0 -0.4px var(--rim-warm),
    /* the chamfer: a machined ring between face and wall */
    inset 0 0 0 1px var(--chamfer-hi),
    inset -1px -1px 0 0 var(--chamfer-lo),
    /* the bounce off the panel, on the underside */
    inset 0 -1.5px 1.5px var(--rim-bounce),
    /* the turn away from the light, lower right */
    inset -1.5px -1.5px 3.5px rgba(var(--sh), 0.17),
    /* wall, two steps */
    0 3px 0 var(--cap-wall-1),
    0 4px 0 var(--cap-wall-2),
    /* contact — tight, dark, anchors the object */
    0 5px 3px rgba(var(--sh), 0.42),
    /* ambient occlusion */
    0 10px 22px rgba(var(--sh), 0.16) !important;
}

html body .tt-timer__transportside .btn:active,
html body .chassis__tools .btn--icon:active,
html body .btn--icon.btn--round:active {
  transform: translateY(3px) !important;
  transition: transform 110ms cubic-bezier(0.2, 0, 0.2, 1) !important;
  box-shadow:
    inset 1px 1px 0 -0.4px rgba(255, 252, 245, 0.6),
    inset 0 0 0 1px var(--chamfer-lo),
    inset 0 2px 4px rgba(var(--sh), 0.22),
    0 0 1px rgba(var(--sh), 0.5),
    0 1px 2px rgba(var(--sh), 0.35) !important;
}

/* 49.2 — the key row: same corrections, shallower wall */
html body .segmented__key {
  box-shadow:
    inset 1.4px 1.4px 0 -0.4px var(--rim-warm),
    inset 0 0 0 1px var(--chamfer-hi),
    inset -1px -1px 0 0 var(--chamfer-lo),
    inset 0 -1.5px 1.5px var(--rim-bounce),
    inset -1.5px -1.5px 3px rgba(var(--sh), 0.16),
    0 2px 0 var(--cap-wall-1),
    0 3px 0 var(--cap-wall-2),
    0 4px 2px rgba(var(--sh), 0.45),
    0 8px 16px rgba(var(--sh), 0.16) !important;
}

html body .segmented__key[aria-selected="true"],
html body .segmented__key.is-active {
  box-shadow:
    inset 1px 1px 0 -0.4px rgba(255, 252, 245, 0.5),
    inset 0 0 0 1px var(--chamfer-lo),
    inset 0 2px 3px rgba(var(--sh), 0.34),
    inset 0 -1px 0 var(--rim-bounce),
    0 0 1px rgba(var(--sh), 0.5),
    0 1px 2px rgba(var(--sh), 0.32) !important;
}

/* 49.3 — the signal: bleeds onto the panel, hotter desaturated core */
html body .btn--lg.btn--primary,
html[data-theme="ceramic"] body .btn--lg.btn--primary,
html[data-theme="graphite"] body .btn--lg.btn--primary {
  background-image:
    radial-gradient(
      ellipse 66% 150% at 32% 20%,
      #ff6a3a 0%,
      rgba(255, 106, 58, 0) 60%
    ),
    linear-gradient(var(--k), #f4703c 0%, #f0501e 46%, #d8431a 100%) !important;
  box-shadow:
    inset 1.4px 1.4px 0 -0.4px rgba(255, 214, 190, 0.85),
    inset 0 0 0 1px rgba(255, 180, 150, 0.25),
    inset -1px -1px 0 0 rgba(120, 30, 0, 0.30),
    inset 0 -2px 4px rgba(120, 30, 0, 0.40),
    0 3px 0 #a83313,
    0 4px 0 #8f2a0e,
    /* contact, warmed toward red because the object throws colour */
    0 5px 3px rgba(90, 30, 10, 0.45),
    0 10px 26px rgba(var(--sh), 0.18),
    /* THE BLEED — a bulb behind translucent plastic lights the panel */
    0 0 34px 10px rgba(240, 80, 30, 0.13) !important;
}

/* 49.4 — labels are silkscreened onto the panel, not laid over it */
html body .lbl,
html body .tt-timer__legend,
html body .tt-timer__meta,
html body .segmented__key,
html body .chip,
html body .tt-timer__transportside .btn,
html body .chassis__tools .btn--icon {
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.55) !important;
}
html[data-theme="graphite"] body .lbl,
html[data-theme="graphite"] body .tt-timer__legend,
html[data-theme="graphite"] body .tt-timer__meta,
html[data-theme="graphite"] body .segmented__key,
html[data-theme="graphite"] body .chip,
html[data-theme="graphite"] body .tt-timer__transportside .btn,
html[data-theme="graphite"] body .chassis__tools .btn--icon {
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.55) !important;
}
/* The signal's own label sits on orange, so it takes a dark press instead. */
html body .btn--lg.btn--primary {
  text-shadow: 0 1px 0 rgba(120, 30, 0, 0.45) !important;
}

/* 49.5 — focus lives OUTSIDE the housing, so it never cuts the material */
html body .segmented__key:focus-visible,
html body .chip:focus-visible,
html body .btn:focus-visible,
html body .btn--icon:focus-visible {
  outline: 2px solid #6e9bc4 !important;
  outline-offset: 5px !important;
}

/* 49.6 — the panel is warm and turns away at its margins */
html body .app-shell::after { border-radius: inherit !important; }

/* ══════════════════════════════════════════════════════════════════════════
   50. SPEC AUDIT — every remaining deviation, corrected

   Went through the brief clause by clause against what was actually shipping.
   Ten deviations. Several were me approximating a number the spec states
   exactly, which is the same as ignoring it.

     §1  grain was 5% at baseFrequency .85   → spec says 1.5% at .8
     §1  panel was #EAE5DF flat              → #EFEBE3 → #DFDAD1, edge-shaded
     §1  cap shade was #E2DED6               → #E4E0D8
     §2  recessed housing lit from the TOP   → top-LEFT, matching the key
     §4  seam occlusion at .34               → .85 at the seam, per spec
     §5  chamfer was a uniform 1px ring      → §9 forbids exactly that
     §6  lamp bleed at 13%                   → ~6%
     §7  silkscreen underline at .55         → .4
     §7  labels were uppercase               → lowercase, .06em, #2B2A27
     §8  press travelled 3px                 → 1–2px, contact collapsed

   The chamfer one matters most. §9 forbids "uniform 1px borders standing in
   for gaps or bevels", and a full even ring is precisely that — a drawn line
   pretending to be a machined edge. A real chamfer is BRIGHT on the top-left
   arc and darkens as it wraps past 10 o'clock toward 4, so it has to be two
   opposed directional insets, never one ring.
   ══════════════════════════════════════════════════════════════════════════ */

:root {
  --panel-base: var(--ramp-150);
  --panel-shade: var(--ramp-250);
  --cap-hi: #fdfcf9;
  --cap-mid: #f2efe8;
  --cap-lo: #e4e0d8;          /* spec value, was #e2ded6 */
  --collar: #1c1b19;          /* warm black, never #000 */
  /* --ink REMOVED. This block is unscoped `:root`, so it redefined the
     SEMANTIC ink token for every theme at once, and it sits after tokens.css
     at equal specificity — so it won. In ceramic #2b2a27 was close enough to
     pass unnoticed; in graphite it made every consumer of --ink a near-black
     on a near-black ground. The dial numerals measured 1.21:1 against the
     face: present in the DOM, invisible on screen.
     --ink belongs to tokens.css, where it is `var(--ramp-900)` and flips with
     the theme. A theme-blind literal must never shadow it. */
  --seam: rgba(28, 27, 25, 0.85);
}

/* §1 — the panel. Warm, gradient to its shade, darker at the outer margins
   as if the surface curves away. */
html[data-theme="ceramic"] body .app-shell,
html:not([data-theme="graphite"]) body .app-shell {
  background-color: var(--panel-base) !important;
  background-image: radial-gradient(
    120% 90% at 22% 8%,
    color-mix(in srgb, var(--ramp-150) 92%, #fff) 0%,
    var(--panel-base) 48%,
    var(--panel-shade) 100%
  ) !important;
}

/* §1 — grain at the stated 1.5% and baseFrequency 0.8. At 5% it was visible
   as texture, which is louder than "kills banding" asks for. */
html body .app-shell::after {
  opacity: 0.015 !important;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E") !important;
}

/* §2 — RECESSED housings invert the model: dark inner wall on the top-LEFT,
   light on the bottom-right, and no drop shadow at all. The key tray was
   lit from straight above, which is the one thing §2 says collapses it. */
html body .segmented {
  box-shadow:
    inset 2px 2px 3px rgba(var(--sh), 0.42),
    inset -1px -1px 0 rgba(255, 252, 245, 0.70),
    0 1px 0 rgba(255, 252, 245, 0.80) !important;
}

/* §5 + §9 — the chamfer is DIRECTIONAL, never a ring. */
html body .tt-timer__transportside .btn,
html body .tt-timer__transport .btn:not(.btn--primary),
html body .chassis__tools .btn--icon,
html body .btn--icon.btn--round {
  box-shadow:
    /* rim: 1px, upper-left arc only, warm — never blue-white */
    inset 1.4px 1.4px 0 -0.4px rgba(255, 252, 245, 0.90),
    /* chamfer: bright top-left, dark past 10 o'clock toward 4 */
    inset 1px 1px 0 -0.5px var(--chamfer-hi),
    inset -1px -1px 0 -0.5px var(--chamfer-lo),
    /* bounce off the panel onto the underside */
    inset 0 -1.5px 1.5px rgba(255, 252, 245, 0.35),
    /* the turn away from the light */
    inset -1.5px -1.5px 3.5px rgba(var(--sh), 0.17),
    /* side wall, two steps */
    0 3px 0 var(--cap-wall-1),
    0 4px 0 var(--cap-wall-2),
    /* §3 contact — tight, dark, almost no blur */
    0 5px 2px rgba(var(--sh), 0.45),
    /* §3 ambient occlusion — wide, soft, low */
    0 10px 20px rgba(var(--sh), 0.14) !important;
}

html body .segmented__key {
  box-shadow:
    inset 1.4px 1.4px 0 -0.4px rgba(255, 252, 245, 0.90),
    inset 1px 1px 0 -0.5px var(--chamfer-hi),
    inset -1px -1px 0 -0.5px var(--chamfer-lo),
    inset 0 -1.5px 1.5px rgba(255, 252, 245, 0.35),
    inset -1.5px -1.5px 3px rgba(var(--sh), 0.16),
    0 2px 0 var(--cap-wall-1),
    0 3px 0 var(--cap-wall-2),
    0 4px 2px rgba(var(--sh), 0.45),
    0 8px 20px rgba(var(--sh), 0.14) !important;
}

/* §4 — the seam is a shadowed VOID at .85, falling off within ~3px, with a
   light catch on the left key's right edge where it turns to the key. */
html body .segmented__key::after {
  right: -3px !important;
  width: 3px !important;
  background: linear-gradient(
    90deg,
    var(--seam) 0%,
    rgba(28, 27, 25, 0.30) 45%,
    rgba(28, 27, 25, 0) 100%
  ) !important;
  box-shadow: inset 1px 0 0 rgba(255, 252, 245, 0.45) !important;
}

/* §8 — press is TRAVEL: 2px down, contact collapsed to almost nothing, the
   ambient shrunk, the seam deepened, the top-face highlight dimmed. */
html body .tt-timer__transportside .btn:active,
html body .chassis__tools .btn--icon:active,
html body .btn--icon.btn--round:active,
html body .segmented__key:active {
  transform: translateY(2px) !important;
  transition: transform 100ms cubic-bezier(0.2, 0, 0.2, 1), box-shadow 100ms !important;
  box-shadow:
    inset 1px 1px 0 -0.5px rgba(255, 252, 245, 0.45),
    inset 1.5px 1.5px 4px rgba(var(--sh), 0.28),
    0 0 1px rgba(var(--sh), 0.45),
    0 2px 5px rgba(var(--sh), 0.12) !important;
}

html body .segmented__key[aria-selected="true"],
html body .segmented__key.is-active {
  transform: translateY(2px) !important;
  box-shadow:
    inset 1px 1px 0 -0.5px rgba(255, 252, 245, 0.45),
    inset 1.5px 1.5px 4px rgba(var(--sh), 0.30),
    0 0 1px rgba(var(--sh), 0.45),
    0 2px 5px rgba(var(--sh), 0.12) !important;
}

/* §6 — the lamp bleeds onto the panel at ~6%, not 13%. */
html body .btn--lg.btn--primary,
html[data-theme="ceramic"] body .btn--lg.btn--primary,
html[data-theme="graphite"] body .btn--lg.btn--primary {
  box-shadow:
    inset 1.4px 1.4px 0 -0.4px rgba(255, 214, 190, 0.85),
    inset 1px 1px 0 -0.5px rgba(255, 190, 160, 0.35),
    inset -1px -1px 0 -0.5px rgba(120, 30, 0, 0.35),
    inset 0 -2px 4px rgba(120, 30, 0, 0.40),
    0 3px 0 #a83313,
    0 4px 0 #8f2a0e,
    /* contact, warmed toward red — the object throws its own colour */
    0 5px 2px rgba(90, 30, 10, 0.45),
    0 10px 20px rgba(var(--sh), 0.14),
    0 0 40px 12px rgba(240, 80, 30, 0.06) !important;
}

html body .btn--lg.btn--primary:active {
  transform: translateY(2px) !important;
  box-shadow:
    inset 1px 1px 0 -0.5px rgba(255, 214, 190, 0.5),
    inset 1.5px 1.5px 4px rgba(120, 30, 0, 0.40),
    0 0 1px rgba(90, 30, 10, 0.5),
    0 2px 5px rgba(90, 30, 10, 0.14),
    0 0 30px 8px rgba(240, 80, 30, 0.05) !important;
}

/* §7 — labels: lowercase, 9–10px, .06em, #2B2A27, never bold, silkscreened
   with a 1px light line UNDER the glyph at .4. */
html body .t-label,
html body .tt-timer__meta,
html body .tt-timer__legend,
html body .segmented__key,
html body .chip,
html body .day-strip {
  text-transform: lowercase !important;
  letter-spacing: 0.06em !important;
  font-weight: 400 !important;
  color: var(--ink) !important;
  -webkit-text-fill-color: var(--ink) !important;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4) !important;
}

html[data-theme="graphite"] body .t-label,
html[data-theme="graphite"] body .tt-timer__meta,
html[data-theme="graphite"] body .tt-timer__legend,
html[data-theme="graphite"] body .segmented__key,
html[data-theme="graphite"] body .chip,
html[data-theme="graphite"] body .day-strip {
  color: #b4b0a8 !important;
  -webkit-text-fill-color: #b4b0a8 !important;
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.5) !important;
}

/* The signal's label sits on orange, so its silkscreen presses dark. */
html body .btn--lg.btn--primary {
  text-transform: lowercase !important;
  letter-spacing: 0.06em !important;
  font-weight: 400 !important;
  color: #fff !important;
  -webkit-text-fill-color: #fff !important;
  text-shadow: 0 1px 0 rgba(120, 30, 0, 0.45) !important;
}

/* §9 — no bold anywhere on the panel. */
html body .segmented__key,
html body .chip,
html body .tt-timer__transportside .btn,
html body .chassis__tools .btn--icon { font-weight: 400 !important; }

/* ══════════════════════════════════════════════════════════════════════════
   51. THE ROOM LIGHTS THE PANEL — one moving source, every component

   §48–50 fixed the key at 135deg because the spec says so, and that was the
   right call against a static brief. But it left src/light.js computing a
   real sun position every tick that NOTHING read — an engine running with
   its output disconnected.

   The resolution keeps what the spec actually cares about. Read §2 closely:
   the requirement is that the direction never varies BETWEEN COMPONENTS —
   "if the knob is lit from upper-left and the rocker from the top, the
   illusion collapses." It is a consistency rule, not a stillness rule. A
   real panel on a real desk is lit by a room, and the room moves.

   So: one source, overhead-dominant, shared by everything. light.js puts the
   sun straight overhead at noon (azimuth 0, gradients at a flat 180deg) and
   leans it to ±52deg by dawn and dusk. Every rim, chamfer, turn and shadow
   on the panel reads the SAME published vector, so no two controls can ever
   disagree — which is the failure §2 exists to prevent — while the whole
   surface turns with the day.

   Fallbacks are the spec's static values, so a browser without the script,
   or the moment before first paint, gets a correct 135deg panel.
   ══════════════════════════════════════════════════════════════════════════ */

/* Round caps — transport and tools */
html body .tt-timer__transportside .btn,
html body .tt-timer__transport .btn:not(.btn--primary),
html body .chassis__tools .btn--icon,
html body .btn--icon.btn--round {
  background-image:
    radial-gradient(
      ellipse 46% 30% at calc(50% + var(--hl-x, -8%) * 0.5)
                          calc(34% + var(--hl-y, -10%) * 0.5),
      rgba(255, 252, 245, calc(var(--rim-a, 0.9) * 0.55)) 0%,
      rgba(255, 252, 245, 0) 100%
    ),
    linear-gradient(
      var(--light-angle, 135deg),
      var(--cap-hi) 0%,
      var(--cap-mid) 45%,
      var(--cap-lo) 100%
    ) !important;
  box-shadow:
    /* rim, on the side facing the light */
    inset var(--rim-x, 1.4px) var(--rim-y, 1.4px) 0 -0.4px
      rgba(255, 252, 245, var(--rim-a, 0.9)),
    /* chamfer: lit half toward the source, dark half opposite */
    inset calc(var(--rim-x, 1px) * 0.7) calc(var(--rim-y, 1px) * 0.7) 0 -0.5px
      var(--chamfer-hi),
    inset calc(var(--turn-x, -1px) * 0.7) calc(var(--turn-y, -1px) * 0.7) 0 -0.5px
      var(--chamfer-lo),
    /* bounce off the panel, always on the underside */
    inset 0 -1.5px 1.5px rgba(255, 252, 245, 0.35),
    /* the turn away from the light */
    inset var(--turn-x, -1.5px) var(--turn-y, -1.5px) 3.5px
      rgba(var(--sh), var(--turn-a, 0.17)),
    /* side wall */
    0 3px 0 var(--cap-wall-1),
    0 4px 0 var(--cap-wall-2),
    /* contact — tight, barely moves with the sun */
    0 5px var(--ao-blur, 2px) rgba(var(--sh), calc(var(--ao-alpha, 0.3) + 0.15)),
    /* cast — thrown by the sun, so it swings and lengthens with it */
    calc(var(--sh-x, 0px) * 1.2) calc(5px + var(--sh-y, 3px) * 1.4)
      calc(var(--sh-blur, 14px) * 1.3)
      rgba(var(--sh), calc(var(--sh-alpha, 0.16) - 0.02)) !important;
}

/* The key row takes the same light, shallower wall */
html body .segmented__key {
  background-image: linear-gradient(
    var(--light-angle, 135deg),
    var(--cap-hi) 0%,
    var(--cap-mid) 46%,
    var(--cap-lo) 100%
  ) !important;
  box-shadow:
    inset var(--rim-x, 1.4px) var(--rim-y, 1.4px) 0 -0.4px
      rgba(255, 252, 245, var(--rim-a, 0.9)),
    inset calc(var(--rim-x, 1px) * 0.7) calc(var(--rim-y, 1px) * 0.7) 0 -0.5px
      var(--chamfer-hi),
    inset calc(var(--turn-x, -1px) * 0.7) calc(var(--turn-y, -1px) * 0.7) 0 -0.5px
      var(--chamfer-lo),
    inset 0 -1.5px 1.5px rgba(255, 252, 245, 0.35),
    inset var(--turn-x, -1.5px) var(--turn-y, -1.5px) 3px
      rgba(var(--sh), var(--turn-a, 0.16)),
    0 2px 0 var(--cap-wall-1),
    0 3px 0 var(--cap-wall-2),
    0 4px var(--ao-blur, 2px) rgba(var(--sh), calc(var(--ao-alpha, 0.3) + 0.15)),
    calc(var(--sh-x, 0px) * 1.1) calc(4px + var(--sh-y, 3px) * 1.2)
      calc(var(--sh-blur, 14px) * 1.2)
      rgba(var(--sh), calc(var(--sh-alpha, 0.16) - 0.02)) !important;
}

/* The tray is RECESSED, so its inner shading is the exact inverse: the dark
   inner wall is on the side facing the light, because the near lip blocks it. */
html body .segmented {
  background-image: linear-gradient(
    var(--light-angle, 180deg), #cfcbc2, #e6e2da
  ) !important;
  box-shadow:
    inset var(--rim-x, 2px) var(--rim-y, 2px) 3px rgba(var(--sh), 0.42),
    inset var(--turn-x, -1px) var(--turn-y, -1px) 0 rgba(255, 252, 245, 0.70),
    0 1px 0 rgba(255, 252, 245, 0.80) !important;
}
html[data-theme="graphite"] body .segmented {
  background-image: linear-gradient(var(--light-angle, 180deg), #17171a, #2a2a2e) !important;
}

/* The signal */
html body .btn--lg.btn--primary,
html[data-theme="ceramic"] body .btn--lg.btn--primary,
html[data-theme="graphite"] body .btn--lg.btn--primary {
  background-image:
    radial-gradient(
      ellipse 66% 150% at calc(46% + var(--hl-x, -8%) * 0.4)
                          calc(28% + var(--hl-y, -10%) * 0.4),
      #ff6a3a 0%,
      rgba(255, 106, 58, 0) 60%
    ),
    linear-gradient(
      var(--light-angle, 135deg),
      #f4703c 0%, #f0501e 46%, #d8431a 100%
    ) !important;
  box-shadow:
    inset var(--rim-x, 1.4px) var(--rim-y, 1.4px) 0 -0.4px rgba(255, 214, 190, 0.85),
    inset calc(var(--rim-x, 1px) * 0.7) calc(var(--rim-y, 1px) * 0.7) 0 -0.5px
      rgba(255, 190, 160, 0.35),
    inset calc(var(--turn-x, -1px) * 0.7) calc(var(--turn-y, -1px) * 0.7) 0 -0.5px
      rgba(120, 30, 0, 0.35),
    inset 0 -2px 4px rgba(120, 30, 0, 0.40),
    0 3px 0 #a83313,
    0 4px 0 #8f2a0e,
    0 5px var(--ao-blur, 2px) rgba(90, 30, 10, calc(var(--ao-alpha, 0.3) + 0.15)),
    calc(var(--sh-x, 0px) * 1.2) calc(5px + var(--sh-y, 3px) * 1.4)
      calc(var(--sh-blur, 14px) * 1.3)
      rgba(var(--sh), calc(var(--sh-alpha, 0.16) - 0.02)),
    0 0 40px 12px rgba(240, 80, 30, 0.06) !important;
}

/* The panel itself catches the room: its sheen sits under the source, and the
   warm tint of the hour mixes in at low strength. */
html[data-theme="ceramic"] body .app-shell,
html:not([data-theme="graphite"]) body .app-shell {
  background-image: radial-gradient(
    120% 90% at calc(30% + var(--hl-x, -8%) * 1.2) calc(14% + var(--hl-y, -10%) * 0.8),
    color-mix(in srgb, var(--ramp-150) 92%, #fff) 0%,
    var(--panel-base) 48%,
    var(--panel-shade) 100%
  ) !important;
}

/* The lamps keep their catchlight on the lit side too. */
html body .segmented__led,
html body .brand::after {
  background-position: calc(38% + var(--hl-x, 0%) * 0.3) calc(32% + var(--hl-y, 0%) * 0.3) !important;
}

/* ══════════════════════════════════════════════════════════════════════════
   52. THE END TIME, THE LAST MINUTE, AND THE POWER-ON

   Three things the audit found missing. None of them changes the dial's size
   or style — the disc is exactly what it was.

   52.1 THE END TIME. A duration has to be added to the clock before it means
        anything, and that addition is the arithmetic time blindness breaks.
        Quiet, one line under the legend, never competing with the readout.

   52.2 THE LAST MINUTE. The app's climax was unmarked: the disc got small and
        nothing acknowledged it. One escalation — the lamp goes from steady to
        a slow breath, the end time steps up in weight, the disc's rim catches
        a little more light. Anticipation, not alarm. It stops the instant the
        session does.

   52.3 THE POWER-ON. A device that has just been switched on settles. Once
        per load, ~900ms: the lamp strikes, the caps rise onto their walls,
        the panel resolves. It never repeats, so it cannot become a tax.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── 52.1 the end time ───────────────────────────────────────────────
   It lives in the legend line (see src/modes/timer.js), so it needs no box
   of its own — only tabular figures, so the minute digit does not jitter the
   line every sixty seconds. */
html body .tt-timer__meta {
  font-variant-numeric: tabular-nums !important;
  transition: color 220ms ease !important;
}

/* ── 52.2 the last minute ─────────────────────────────────────────────── */
@keyframes tt-lamp-breathe {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}

/* The instrument marks it; the case marks it. Nothing else changes. */
html body .tt-timer[data-final="1"] .tt-timer__meta {
  color: var(--lamp-sig, #f0501e) !important;
  -webkit-text-fill-color: var(--lamp-sig, #f0501e) !important;
  font-weight: 500 !important;
}

html[data-final="1"] body .brand::after,
html[data-final="1"] body .segmented__key[aria-selected="true"] .segmented__led {
  animation: tt-lamp-breathe 2s ease-in-out infinite !important;
}

/* ── 52.3 the power-on ────────────────────────────────────────────────── */
@keyframes tt-strike {
  0%   { opacity: 0; }
  40%  { opacity: 1; }
  55%  { opacity: 0.4; }
  100% { opacity: 1; }
}
@keyframes tt-rise {
  0%   { transform: translateY(3px); }
  100% { transform: translateY(0); }
}
@keyframes tt-panel-settle {
  0%   { opacity: 0; }
  100% { opacity: 1; }
}

html[data-boot="on"] body .app-shell {
  animation: tt-panel-settle 420ms cubic-bezier(0.2, 0, 0.2, 1) both !important;
}
html[data-boot="on"] body .tt-timer__transportside .btn,
html[data-boot="on"] body .chassis__tools .btn--icon,
html[data-boot="on"] body .btn--lg.btn--primary,
html[data-boot="on"] body .segmented__key:not([aria-selected="true"]) {
  animation: tt-rise 480ms cubic-bezier(0.2, 0, 0.2, 1) both !important;
}
/* the caps come up in sequence, left to right, like a mechanism settling */
html[data-boot="on"] body .tt-timer__transportside--lead .btn { animation-delay: 180ms !important; }
html[data-boot="on"] body .btn--lg.btn--primary { animation-delay: 240ms !important; }
html[data-boot="on"] body .tt-timer__transportside--trail .btn { animation-delay: 300ms !important; }

html[data-boot="on"] body .segmented__led,
html[data-boot="on"] body .brand::after {
  animation: tt-strike 620ms ease-out both !important;
  animation-delay: 380ms !important;
}

/* A power-on is a flourish, and a flourish is the first thing reduced motion
   should lose. The states it animates TO are the resting states, so removing
   the animation removes nothing but the movement. */
@media (prefers-reduced-motion: reduce) {
  html[data-boot="on"] body .app-shell,
  html[data-boot="on"] body .tt-timer__transportside .btn,
  html[data-boot="on"] body .chassis__tools .btn--icon,
  html[data-boot="on"] body .btn--lg.btn--primary,
  html[data-boot="on"] body .segmented__key,
  html[data-boot="on"] body .segmented__led,
  html[data-boot="on"] body .brand::after { animation: none !important; }
  html[data-final="1"] body .brand::after,
  html[data-final="1"] body .segmented__key[aria-selected="true"] .segmented__led {
    animation: none !important;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   53. THE DISC IS THE DISPLAY — give it the room

   The dial kept coming out small and I kept treating the formula as the
   problem. It was not. `100vh - 490px` is arithmetic, and it was correct:
   490px really was the chrome under the disc. The dial was small because the
   CHROME WAS BIG, and the biggest piece of it was a digital readout set at up
   to 72px — a number so large it competes with the very thing it captions.

   On the object this is modelled on, the disc IS the display and the digits
   are a small confirmation beside it. Sizing the numerals like a hero and
   then wondering why the disc had no room was the error.

   Readout down to a caption, gaps tightened, and the freed height goes
   straight into the dial: the constant drops 490 → 372, which is +118px of
   disc at every viewport height.
   ══════════════════════════════════════════════════════════════════════════ */

@media (min-width: 75rem) {
  /* The numerals become a caption, not a headline. */
  html[data-dock="desktop"] body .tt-timer__readout,
  html[data-dock="desktop"] body .t-display {
    font-size: clamp(1.625rem, 3.6vh, 2.5rem) !important;
    line-height: 1.05 !important;
    padding-block: clamp(0.25rem, 0.7vh, 0.5rem) !important;
    padding-inline: clamp(0.75rem, 1.6vh, 1.25rem) !important;
  }

  /* The stack tightens; none of these rows needed the air they had. */
  html[data-dock="desktop"] body .tt-timer {
    gap: clamp(0.375rem, 0.9vh, 0.75rem) !important;
  }
  html[data-dock="desktop"] body .tt-timer__stage {
    gap: clamp(0.25rem, 0.7vh, 0.625rem) !important;
  }

  /* THE PAYOFF. 372px of chrome instead of 490px, so every pixel of the
     difference lands in the disc. */
  /* TWO HEIGHT BOUNDS, not one. `100vh - C` assumes the chrome under the disc
     is a constant, and it is not: the readout, the gaps and the legend are all
     clamped against vh, so they grow on a tall screen and the single-constant
     model overshot by ~40px at 1080 and clipped the transport. The `60vh` term
     is the proportional half — whichever binds first wins, so it is correct at
     800 and at 1440 without a ladder of breakpoints. */
  html[data-dock="desktop"] .tt-timer__stage {
    --instr: clamp(260px, min(calc(100vh - 400px), 60vh, 40vw), 620px) !important;
  }
  html[data-dock="desktop"]:not([data-field="open"]) .tt-timer__stage {
    --instr: clamp(260px, min(calc(100vh - 400px), 60vh, 64vw), 820px) !important;
  }
}

/* The phone has the same problem and the same answer. */
@media (max-width: 74.99rem) {
  html body .tt-timer__readout,
  html body .t-display {
    font-size: clamp(1.75rem, 8vw, 3rem) !important;
    line-height: 1.05 !important;
  }
}

/* ── 54. THE READOUT IS A WINDOW, SO IT MUST FIT ITS TYPE ───────────────
   §53 dropped the numerals to a caption but left the recessed LCD window
   sized for the headline they used to be, so the digits floated in a box far
   bigger than themselves — which is what made it read as a leftover rather
   than a display. The window now hugs the type, and the `+` on an overtime
   count keeps tabular alignment so the line does not jump when it appears. */
@media (min-width: 75rem) {
  html[data-dock="desktop"] body .tt-timer__readout {
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    inline-size: auto !important;
    min-inline-size: 4.5em !important;
    margin-inline: auto !important;
    border-radius: 0.25rem !important;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   55. ONE DIAL SIZE, EVERY MODE

   §53 sized `.tt-timer__stage` and stopped there, so CLOCK mode — which is a
   different file with a different stage class, `.tt-clock__stage` — never got
   any of it and kept the old small dial. Switching modes visibly resized the
   instrument, which is the one thing a physical device never does: the face
   is the same piece of glass whatever it is showing.

   Every rule below therefore names BOTH stages together. That is the fix and
   also the guard: a future change to one is a change to the other, because
   they are the same declaration.
   ══════════════════════════════════════════════════════════════════════════ */

@media (min-width: 75rem) {
  html[data-dock="desktop"] .tt-timer__stage,
  html[data-dock="desktop"] .tt-clock__stage {
    /* Eased back ~7%: the constant goes 400 → 430 and the proportional cap
       60vh → 56vh, so the disc breathes against the case instead of pressing
       on it. Both stages share this declaration, so the two modes cannot
       drift apart again. */
    --instr: clamp(250px, min(calc(100vh - 430px), 56vh, 38vw), 580px) !important;
    inline-size: var(--instr) !important;
    max-inline-size: var(--instr) !important;
    margin-inline: auto !important;
    flex: 0 0 auto !important;
    min-block-size: auto !important;
  }
  html[data-dock="desktop"]:not([data-field="open"]) .tt-timer__stage,
  html[data-dock="desktop"]:not([data-field="open"]) .tt-clock__stage {
    --instr: clamp(250px, min(calc(100vh - 430px), 56vh, 60vw), 760px) !important;
  }

  html[data-dock="desktop"] .tt-timer__dial,
  html[data-dock="desktop"] .tt-clock__dial,
  html[data-dock="desktop"] .tt-timer__stage > .dial,
  html[data-dock="desktop"] .tt-clock__stage > .dial {
    inline-size: var(--instr) !important;
    block-size: var(--instr) !important;
  }

  /* The clock's digital line is the same caption as the timer's readout, so
     it is set the same. Two modes, one typographic voice. */
  html[data-dock="desktop"] body .tt-clock__digital {
    font-size: clamp(1.625rem, 3.6vh, 2.5rem) !important;
    line-height: 1.05 !important;
  }

  html[data-dock="desktop"] body .tt-clock,
  html[data-dock="desktop"] body .tt-timer {
    gap: clamp(0.375rem, 0.9vh, 0.75rem) !important;
  }
  html[data-dock="desktop"] body .tt-clock__stage,
  html[data-dock="desktop"] body .tt-timer__stage {
    gap: clamp(0.25rem, 0.7vh, 0.625rem) !important;
  }
}

/* Phones too — the face does not change size between modes there either. */
@media (max-width: 74.99rem) {
  html body .tt-clock__digital {
    font-size: clamp(1.75rem, 8vw, 3rem) !important;
    line-height: 1.05 !important;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   56. THE MODE SWITCH SITS ON THE DIAL'S AXIS

   Two separate reasons it was off, and both had to go:

   1. The chassis is `auto minmax(0,1fr) auto` — brand, modes, tools. The side
      columns size to their contents, and the brand (~120px) and the tools
      (~180px) are not the same width, so the middle column's centre is NOT
      the case's centre. The switch was sitting ~56px right of the axis at
      1920 and reading as a misalignment nobody could name.

   2. With the drawer open the instrument only owns the left column, so even
      a perfectly centred header would be centred on the wrong thing. The
      switch belongs over the DIAL, not over the page.

   `1fr auto 1fr` fixes the first by construction: the middle is centred no
   matter what the sides weigh. The second is a translate by exactly half the
   difference between the instrument column and the full case.
   ══════════════════════════════════════════════════════════════════════════ */

@media (min-width: 75rem) {
  :root { --instr-col: clamp(24rem, 32vw, 34rem); }

  html[data-dock="desktop"] .chassis {
    grid-template-columns: 1fr auto 1fr !important;
    grid-template-areas: "brand modes tools" !important;
  }
  html[data-dock="desktop"] .chassis .brand,
  html[data-dock="desktop"] .chassis__brand {
    grid-area: brand !important;
    justify-self: start !important;
  }
  html[data-dock="desktop"] .chassis .modes,
  html[data-dock="desktop"] .chassis nav[aria-label="Mode switcher"] {
    grid-area: modes !important;
    justify-self: center !important;
    transition: transform 260ms cubic-bezier(0.2, 0, 0.2, 1) !important;
  }
  html[data-dock="desktop"] .chassis__tools {
    grid-area: tools !important;
    justify-self: end !important;
  }

  /* Drawer open: float the SWITCH, not the nameplate.
     Taking the brand out of flow collapsed the header around the switch and
     dropped the nameplate underneath it. The switch is the thing that needs
     to leave the flow — it is the only element whose position is defined by
     something other than the header's own edges. Brand and tools stay put,
     the header keeps its natural height, and the switch is placed at the
     exact centre of the instrument column: half that column, minus half of
     itself. No percentage-of-the-wrong-box arithmetic. */
  html[data-dock="desktop"] .chassis {
    position: relative !important;
    grid-template-columns: 1fr auto !important;
    /* ONE ROW. The chassis kept its original three-row track list
       (`52px 0px 18px`) after being given a single-row area map, so it stayed
       106px tall — and the switch, centred at 50% of THAT, landed below the
       nameplate instead of beside it. Areas do not shrink a track list; the
       track list has to be replaced too. */
    grid-template-rows: auto !important;
    grid-template-areas: "brand tools" !important;
    align-items: center !important;
  }
  html[data-dock="desktop"] .chassis .brand,
  html[data-dock="desktop"] .chassis__brand {
    grid-area: brand !important;
    justify-self: start !important;
    position: static !important;
    transform: none !important;
  }
  html[data-dock="desktop"] .chassis__tools {
    grid-area: tools !important;
    justify-self: end !important;
  }
  html[data-dock="desktop"] .chassis .modes,
  html[data-dock="desktop"] .chassis nav[aria-label="Mode switcher"] {
    position: absolute !important;
    inset-block-start: 50% !important;
    inset-inline-start: 50% !important;
    transform: translate(-50%, -50%) !important;
    margin: 0 !important;
    z-index: 2 !important;
  }
  /* Drawer open the instrument owns only the left column, so the switch's
     centre is half of that column rather than half of the case. */
  html[data-dock="desktop"][data-field="open"] .chassis .modes,
  html[data-dock="desktop"][data-field="open"] .chassis nav[aria-label="Mode switcher"] {
    inset-inline-start: calc(var(--instr-col) / 2) !important;
  }
}

@media (prefers-reduced-motion: reduce) {
  html[data-dock="desktop"] .chassis .modes,
  html[data-dock="desktop"] .chassis nav[aria-label="Mode switcher"] {
    transition: none !important;
  }
}

/* ── 57. THE CASE MUST BE SYMMETRICAL ───────────────────────────────────
   Everything inside the instrument measured dead centre on the dial's axis —
   readout, legend, task, face row, transport, mode switch, all at 0px offset.
   The CASE was the thing that was off: 13px, because §38 zeroed
   `padding-inline-end` to reclaim the old drawer's reserved width and never
   touched `padding-inline-start`, which kept its 27.88px bezel.

   So the shell had a left edge and no right edge, and every child centred
   inside that lopsided box sat half the difference to the right of where the
   case looked like its middle was. Nothing was misaligned relative to
   anything else, which is exactly why it was hard to name — the whole centre
   column was correct, and sitting in a frame that wasn't.

   The body already supplies the case inset on both sides, so the shell needs
   none of its own. */
@media (min-width: 75rem) {
  html[data-dock="desktop"] body .app-shell {
    padding-inline: 0 !important;
  }
}

/* ── 58. THE LAST NINE PIXELS, AND THE RAIL'S OWN MARGIN ────────────────
   Making the case symmetrical moved the content column left by 13px and cost
   nothing vertically — but the bigger dial from §53/§55 still leaves the
   transport 9px past the stage at 800px height. The dial does not pay for it:
   it is the product, and it has been shrunk enough times in this file.

   It comes out of the session field instead. 44px is a thumb target, and a
   desktop pointer does not need one — 38px is still comfortable to click and
   is 6px of disc. The rest comes from the chassis, whose keys are compact now
   that §56 stopped stretching the mode column.

   The day strip also gained a margin: with the case's own padding zeroed, its
   text was starting exactly on the case edge and reading as clipped. */
@media (min-width: 75rem) {
  html[data-dock="desktop"] body .tt-timer__task input,
  html[data-dock="desktop"] body .tt-timer__task .field,
  html[data-dock="desktop"] body .tt-timer__task textarea {
    min-block-size: 2.375rem !important;
    block-size: 2.375rem !important;
    padding-block: 0 !important;
  }
  html[data-dock="desktop"] body .chassis {
    padding-block: 0.375rem !important;
  }
  html[data-dock="desktop"] body .day-strip {
    padding-inline: clamp(0.75rem, 1.6vw, 1.5rem) !important;
    box-sizing: border-box !important;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   59. CLOCK MODE WAS FORGOTTEN

   Side by side with TIMER the difference is not subtle, and it is three
   separate faults rather than one:

   1. THE READOUT WINDOW WRAPPED A FRAGMENT. The LCD window is keyed off
      `t-display`, and in TIMER the whole readout carries that class. In CLOCK
      the markup is three parts — `.tt-clock__hm` (9:47), `.tt-clock__sec`
      (:03) and `.tt-clock__suffix` (pm) — and only the FIRST has it. So the
      window closed around the hours and minutes and left the seconds and the
      meridiem sitting outside it on the panel. The window belongs to the
      group, not to one of its three children.

   2. NOTHING WAS CENTRED. `.tt-clock` and its rows all inherit `text-align:
      start`, so the day block, its heading and its scale ran to the left edge
      of a 736px column while the dial sat centred above them. TIMER reads as
      balanced because every row shares the dial's axis; CLOCK shared it with
      exactly one row.

   3. THE COLUMN DID NOT FILL ITS HEIGHT. CLOCK has fewer rows than TIMER, so
      the stack sat at the top and left a void of dead panel beneath it. It is
      the same instrument in the same case — it should occupy it.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── 59.1 the window belongs to the whole readout ─────────────────────── */
html body .tt-clock__digital {
  display: inline-flex !important;
  align-items: baseline !important;
  justify-content: center !important;
  gap: 0.125rem !important;
  margin-inline: auto !important;
  padding-block: clamp(0.25rem, 0.7vh, 0.5rem) !important;
  padding-inline: clamp(0.75rem, 1.6vh, 1.25rem) !important;
  border-radius: 0.25rem !important;
  background-color: var(--lcd, var(--bg-sunk)) !important;
  box-shadow:
    inset 0 2px 3px rgba(var(--sh, 58, 48, 38), 0.30),
    inset 0 -1px 0 rgba(255, 252, 245, 0.55),
    0 1px 0 rgba(255, 252, 245, 0.70) !important;
  font-variant-numeric: tabular-nums !important;
}

/* the fragment gives its window back */
html body .tt-clock__hm {
  background: none !important;
  background-color: transparent !important;
  box-shadow: none !important;
  padding: 0 !important;
  border-radius: 0 !important;
}

html[data-theme="graphite"] body .tt-clock__digital {
  background-color: var(--ramp-300) !important;
  box-shadow:
    inset 0 2px 3px rgba(0, 0, 0, 0.6),
    inset 0 -1px 0 rgba(255, 255, 255, 0.06) !important;
}
html[data-theme="graphite"] body .tt-clock__digital,
html[data-theme="graphite"] body .tt-clock__hm,
html[data-theme="graphite"] body .tt-clock__sec,
html[data-theme="graphite"] body .tt-clock__suffix {
  color: var(--ink) !important;
  -webkit-text-fill-color: var(--ink) !important;
}

/* ── 59.2 every row on the dial's axis ────────────────────────────────── */
html body .tt-clock,
html body .tt-clock__stage,
html body .tt-clock__day,
html body .tt-clock__date {
  text-align: center !important;
}
html body .tt-clock__stage,
html body .tt-clock__day {
  margin-inline: auto !important;
  justify-items: center !important;
}
html body .tt-clock__day {
  display: flex !important;
  flex-direction: column !important;
  align-items: center !important;
  inline-size: 100% !important;
}
/* The day block is a panel of its own, so it gets a readable measure rather
   than the full width of the case. */
html body .tt-clock__ribbon,
html body .tt-ribbonfb {
  inline-size: min(100%, 46rem) !important;
  margin-inline: auto !important;
  text-align: start !important;   /* its own internals are a scale, not prose */
}

/* ── 59.3 the column fills the case ───────────────────────────────────── */
@media (min-width: 75rem) {
  html[data-dock="desktop"] body .tt-clock {
    block-size: 100% !important;
    display: flex !important;
    flex-direction: column !important;
    align-items: center !important;
    justify-content: center !important;
    gap: clamp(0.75rem, 2.2vh, 2rem) !important;
  }
  html[data-dock="desktop"] body .tt-clock__stage {
    flex: 0 0 auto !important;
  }
  html[data-dock="desktop"] body .tt-clock__day {
    flex: 0 0 auto !important;
  }
}

/* ── 60. THE DAY BLOCK IS A CAPTION, NOT A SECOND HEADLINE ──────────────
   CLOCK carries a row TIMER does not — the day ribbon — and after §59 filled
   the case properly that row ran 21px past the stage and clipped its own hour
   scale. The dial does not pay for it: both modes show the same face at the
   same size, which was the point of §55.

   It comes out of the ribbon's own heading. "1h 11m" was set at display size,
   competing with the clock immediately above it for the same glance — two
   headlines, one screen. Dropped to a size that reads as the caption it is,
   which both fixes the overflow and stops the ribbon shouting over the face. */
@media (min-width: 75rem) {
  /* The real markup is the day-ribbon module's: .dr > .dr__head / __lead /
     __rail / __ticks. My first pass guessed at .tt-ribbonfb__* — the CLOCK
     FALLBACK's classes — and matched nothing, which is why the overflow did
     not move. */
  html[data-dock="desktop"] body .tt-clock__day .dr__lead,
  html[data-dock="desktop"] body .tt-clock__day .dr__lead * {
    font-size: clamp(1.125rem, 2.4vh, 1.625rem) !important;
    line-height: 1.15 !important;
  }
  html[data-dock="desktop"] body .tt-clock__day .dr {
    gap: clamp(0.1875rem, 0.6vh, 0.5rem) !important;
  }
  html[data-dock="desktop"] body .tt-clock__day .dr__head {
    min-block-size: 0 !important;
  }
  html[data-dock="desktop"] body .tt-clock__day {
    gap: clamp(0.25rem, 0.8vh, 0.625rem) !important;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   61. THE DIAL MUST FIT ITS COLUMN, AND THE CASE NEEDS AN INTERIOR

   Two faults, both visible the moment the drawer is open on a wide screen.

   1. THE VW CAP MEASURED THE WRONG BOX. `--instr` capped the disc at `38vw`
      — a fraction of the VIEWPORT. But with the drawer open the instrument
      only owns `clamp(24rem, 32vw, 34rem)` of it, so on a 2000px screen the
      cap resolved to 760px inside a 544px column and the disc ran straight
      off the left edge of the case. A cap has to measure the box the thing
      actually lives in, so it is now derived from `--instr-col`.

   2. THE CASE HAD NO INTERIOR. Everything in the instrument column sat flush
      against the moulding — dial, day ribbon, the "your day" heading all
      starting at the case edge, while the field's cards carried proper
      padding. A case with no inside margin reads as a crop, not a case.
   ══════════════════════════════════════════════════════════════════════════ */

@media (min-width: 75rem) {
  /* The disc is bounded by its own column now, with room to breathe inside it. */
  html[data-dock="desktop"][data-field="open"] .tt-timer__stage,
  html[data-dock="desktop"][data-field="open"] .tt-clock__stage {
    --instr: clamp(
      220px,
      min(calc(100vh - 430px), 56vh, calc(var(--instr-col) - 6rem)),
      580px
    ) !important;
  }

  /* THE INTERIOR. The case gets a real inside margin, so nothing touches the
     moulding. Applied to the instrument column only — the field already has
     its own padding, and doubling it there would just make the cards narrow. */
  html[data-dock="desktop"] body .app-stage {
    padding-inline: clamp(1.25rem, 2.2vw, 2.5rem) !important;
    box-sizing: border-box !important;
  }

  /* The day block is part of the instrument, so it takes the same interior
     rather than running the full width of the column. */
  html[data-dock="desktop"] body .tt-clock__day,
  html[data-dock="desktop"] body .tt-clock__ribbon {
    inline-size: 100% !important;
    max-inline-size: 100% !important;
  }

  /* The rails are the case's own edges, so they keep their full width but
     gain the same interior for their type. */
  html[data-dock="desktop"] body .chassis {
    padding-inline: clamp(1rem, 1.8vw, 2rem) !important;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   62. THE CASE GETS ITS INTERIOR BACK — all four edges

   §50 and §58 shaved the block padding and §38 zeroed the inline padding,
   every time to buy a few pixels for the disc. Each cut looked small on its
   own; together they left the instrument pressed against the moulding on all
   four sides — brand 15px from the top-left corner, the field's last card
   15px from the right, the day rail hard against the bottom.

   That is the opposite of the reference. A Braun front panel is mostly margin:
   the parts sit in generous air and the air is what makes them read as placed
   rather than packed. "As little design as possible" is not "as little space
   as possible".

   The disc pays for it, and can afford to — it was eased 7% last change and
   this costs it ~40px more. A slightly smaller disc in a case that breathes
   is the better object, and it is the trade the reference makes everywhere.
   ══════════════════════════════════════════════════════════════════════════ */

@media (min-width: 75rem) {
  /* The desk around the device. */
  html[data-dock="desktop"] body {
    padding-block: clamp(0.75rem, 1.6vh, 1.5rem) !important;
    padding-inline: clamp(1rem, 2vw, 2.25rem) !important;
  }

  /* The case's own interior — real margin on every edge. */
  html[data-dock="desktop"] body .app-shell {
    padding-block: clamp(0.75rem, 1.5vh, 1.375rem) !important;
    padding-inline: clamp(1rem, 1.8vw, 2rem) !important;
    row-gap: clamp(0.625rem, 1.4vh, 1.125rem) !important;
    column-gap: clamp(1rem, 1.8vw, 2rem) !important;
  }

  /* The rails sit inside that interior, not on top of it, so they no longer
     need to fake their own. */
  html[data-dock="desktop"] body .chassis,
  html[data-dock="desktop"] body .day-strip {
    padding-inline: 0 !important;
  }

  /* The instrument keeps a little of its own so the disc never touches the
     field's edge either. */
  html[data-dock="desktop"] body .app-stage {
    padding-inline: clamp(0.5rem, 1.2vw, 1.25rem) !important;
  }

  /* The field's cards get the same air between them and the case. */
  html[data-dock="desktop"] body .tt-panel__panes {
    padding: clamp(0.25rem, 0.8vh, 0.5rem) !important;
  }

  /* And the disc comes down to pay for it. */
  html[data-dock="desktop"] .tt-timer__stage,
  html[data-dock="desktop"] .tt-clock__stage {
    --instr: clamp(240px, min(calc(100vh - 470px), 54vh, 36vw), 560px) !important;
  }
  html[data-dock="desktop"][data-field="open"] .tt-timer__stage,
  html[data-dock="desktop"][data-field="open"] .tt-clock__stage {
    --instr: clamp(
      220px,
      min(calc(100vh - 470px), 54vh, calc(var(--instr-col) - 7rem)),
      560px
    ) !important;
  }
  html[data-dock="desktop"]:not([data-field="open"]) .tt-timer__stage,
  html[data-dock="desktop"]:not([data-field="open"]) .tt-clock__stage {
    --instr: clamp(240px, min(calc(100vh - 470px), 54vh, 56vw), 720px) !important;
  }
}

/* ── 63. HOLD TO RESET — the ring that fills ────────────────────────────
   The hold needs to SHOW that it is happening, or the first press just feels
   broken. A conic sweep around the cap fills over the hold duration and
   vanishes the instant the finger lifts — the same feedback a physical
   latching control gives by moving.

   Drawn on ::after so it sits above the cap's own face, and behind nothing:
   the label stays legible throughout because the sweep is a rim, not a fill. */
html body .tt-timer__transportside .btn[data-holding="1"] {
  position: relative !important;
}
html body .tt-timer__transportside .btn[data-holding="1"]::after {
  content: '' !important;
  position: absolute !important;
  inset: -3px !important;
  border-radius: 50% !important;
  pointer-events: none !important;
  background: conic-gradient(
    var(--lamp-sig, #f0501e) var(--hold-turn, 0turn),
    rgba(0, 0, 0, 0) 0
  ) !important;
  -webkit-mask: radial-gradient(closest-side, transparent 78%, #000 80%) !important;
  mask: radial-gradient(closest-side, transparent 78%, #000 80%) !important;
  animation: tt-hold-sweep var(--hold-ms, 620ms) linear forwards !important;
}

@keyframes tt-hold-sweep {
  from { --hold-turn: 0turn; }
  to   { --hold-turn: 1turn; }
}

/* The custom property has to be registered or it cannot be animated — an
   unregistered property jumps from 0 to 1 with no interpolation, which would
   make the ring appear fully-drawn on the last frame and never sweep. */
@property --hold-turn {
  syntax: '<angle>';
  initial-value: 0turn;
  inherits: false;
}

@media (prefers-reduced-motion: reduce) {
  html body .tt-timer__transportside .btn[data-holding="1"]::after {
    animation: none !important;
    background: conic-gradient(var(--lamp-sig, #f0501e) 1turn, rgba(0,0,0,0) 0) !important;
    opacity: 0.35 !important;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   64. THE SAME FACE ON THE PHONE TOO

   §55 gave CLOCK and TIMER one shared dial size and I scoped the whole thing
   to `min-width: 75rem`. On a phone the fix therefore did not exist: TIMER's
   dial measured 273px and CLOCK's 128px — less than half — and the clock's
   stage collapsed to zero width, which left it 86px off the case's axis as
   well. Switching modes on a phone visibly resized and MOVED the instrument.

   TIMER's phone sizing is `clamp(150px, min(38vh, 70vw), 340px)`, and there
   was never a reason for CLOCK to have a different one. Same declaration,
   both stages, so they cannot drift again — the same guard §55 was supposed
   to provide, now applied at the size where most people will actually meet
   this app. */
@media (max-width: 74.99rem) {
  html body .tt-clock__stage,
  html body .tt-timer__stage {
    --instr: clamp(150px, min(38vh, 70vw), 340px) !important;
    inline-size: var(--instr) !important;
    max-inline-size: var(--instr) !important;
    margin-inline: auto !important;
  }
  html body .tt-clock__dial,
  html body .tt-clock__stage > .dial {
    inline-size: var(--instr) !important;
    block-size: var(--instr) !important;
  }

  /* The clock's rows sit on the dial's axis, as the timer's already do. */
  html body .tt-clock {
    display: flex !important;
    flex-direction: column !important;
    align-items: center !important;
    justify-content: center !important;
    inline-size: 100% !important;
  }
  html body .tt-clock__stage,
  html body .tt-clock__day {
    inline-size: 100% !important;
    max-inline-size: 100% !important;
  }
  html body .tt-clock__stage {
    display: flex !important;
    flex-direction: column !important;
    align-items: center !important;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   65. THE START KEY'S LABEL — a real contrast failure, on the one control
       that matters most

   Measured, not assumed: white on the primary key runs 2.9:1 at the top of
   its gradient and 4.43:1 at the bottom. AA wants 4.5:1 for normal text, so
   EVERY stop failed — and the top failed even the 3:1 large-text floor. On an
   app built for ADHD and autistic users, the least legible thing on screen
   should not be the button you press to begin.

   (The earlier audit missed it because it walked up the tree for a background
   COLOUR, and this key's face is a gradient over `background-color:
   transparent` — so it compared white against the panel and reported a pass.
   Sampling the gradient's actual stops is what found it.)

   THE FIX, and why this one:
     · Darkening to reach 4.5:1 at normal size costs the orange its life —
       the signal is the only saturated thing on the panel and it is doing
       the emotional work of the whole design.
     · Dark ink on orange measures 4.01:1. Also fails, and reads as a warning
       label rather than a control.
     · Large text needs only 3:1. Setting the label at 24px clears it at every
       stop once the gradient's lightest end comes down slightly — and a
       bigger, unmissable primary action is the right answer for this
       audience anyway. The spec forbids BOLD, not large.

   Worst-case composite after this change (specular over the lightest stop)
   is 3.10:1 against a 3.0 requirement. Verified by computing the blend, not
   by eye.
   ══════════════════════════════════════════════════════════════════════════ */

html body .btn--lg.btn--primary,
html[data-theme="ceramic"] body .btn--lg.btn--primary,
html[data-theme="graphite"] body .btn--lg.btn--primary {
  /* Large text: 24px at weight 400. No bold — §9 of the brief still holds. */
  font-size: 1.5rem !important;
  line-height: 1.1 !important;
  letter-spacing: -0.01em !important;

  background-image:
    /* the specular, pulled back so it cannot blow the top out past 3:1 */
    radial-gradient(
      ellipse 66% 150% at calc(46% + var(--hl-x, -8%) * 0.4)
                          calc(28% + var(--hl-y, -10%) * 0.4),
      rgba(255, 150, 110, 0.22) 0%,
      rgba(255, 150, 110, 0) 60%
    ),
    /* the face, with its lightest end brought down from #f4703c */
    linear-gradient(
      var(--light-angle, 135deg),
      #ee5a28 0%,
      #e0480f 46%,
      #b8320a 100%
    ) !important;
}

/* The pill grows with the type so the label keeps its air. */
@media (min-width: 75rem) {
  html[data-dock="desktop"] body .btn--lg.btn--primary {
    min-block-size: 3.5rem !important;
    padding-inline: 1.75rem !important;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   66. THE HEADER IS ONE LINE — and the grille was never earning its row

   The mode switch sat 59px below the nameplate and the tools. Brand and tools
   both centred at y=60; the switch at y=119. The cause was not the switch:
   the chassis was still resolving THREE grid rows (`52px 0px 18px`) despite a
   one-row area map, because `.chassis__grille` carries `grid-area: grille` —
   a name that appears in no area map, so the grid invents an implicit row to
   hold it. §66's predecessor set `grid-template-rows: auto` and that changed
   nothing, because an implicit row is not a template row.

   THE GRILLE GOES, rather than being worked around.

   It is a fluted decorative band beside the wordmark. It carries no
   information, has no state, is not a control, and cannot be interacted with.
   It was there to evoke a speaker grille on a Braun front panel — which is a
   reference, not a function. Rams's actual test is whether a mark is doing
   physical or informational work; this one was doing neither, and it was
   costing the header its alignment. "As little design as possible" is not a
   licence to add texture that means nothing.

   With it gone the chassis is genuinely one row, and the nameplate, the mode
   switch and the tools share a single centre line — which is what a front
   panel looks like.
   ══════════════════════════════════════════════════════════════════════════ */

html body .chassis__grille {
  display: none !important;
}

@media (min-width: 75rem) {
  html[data-dock="desktop"] .chassis {
    grid-template-rows: minmax(0, auto) !important;
    align-items: center !important;
    min-block-size: 0 !important;
  }
  /* The switch centres on the row it is actually in, now that there is one. */
  html[data-dock="desktop"] .chassis .modes,
  html[data-dock="desktop"] .chassis nav[aria-label="Mode switcher"] {
    inset-block-start: 50% !important;
    transform: translate(-50%, -50%) !important;
  }
}
