/*
 * The Plan screen's own styling (#62). Static layout — everything that
 * changes with the plan is data, never a class this file has to add. The
 * two variables written by the measuring rind (`measure.mjs`) are the only
 * numbers this file does not own outright: `--plan-columns` and
 * `--diagram-height`.
 */

/*
 * The look (#142, decided at #15): one design in two modes — **light is
 * Bronze** (parchment, bronze), **dark is Foil** (black, gold) — and without a
 * choice the mode follows the device. The tokens are the prototype's
 * (`prototypes/cockpit.prototype.html` on `prototype/rank-distribution`,
 * `:root` and `body[data-mode="dark"]`), taken over as values, not as code.
 *
 * The form language is the same in both modes and lives in `:root` alone:
 * sharp corners (2 px, the round button 3), **no borders** on tile, button
 * and counter — the edge comes from the shadow, and in the dark a thin gold
 * glow lies over it as an inner rim, or it would be black on black. What is
 * chosen glows harder. A line remains only where it separates **data**: the
 * derivation steps and the line over each group title on `Details`.
 *
 * Every colour in this file is one of these tokens. Only colours and depth
 * turn with the mode.
 *
 * The mode is chosen (#144, ADR 0011): System, light or dark, by the button
 * in the head of `Details` (`theme.mjs`). System is no attribute at all and
 * follows the device exactly as before; a choice is `data-theme` on `<html>`,
 * set in `<head>` before the first paint. So the dark set has two ways in —
 * a dark device not told light, and dark chosen anywhere — and CSS cannot
 * join a media query and an attribute in one rule: the set stands **twice**,
 * below and after the media block, and `test/ui-look.test.mjs` holds the two
 * copies equal. Change one, change both. `color-scheme` follows the choice
 * too, so native controls and scrollbars turn with the tokens.
 */
:root {
  color-scheme: light dark;

  --accent: #8c5a2b;
  --accent-soft: #f0e4d1;
  --on-accent: #fff8ee;
  --ink: #241c12;
  --muted: #7a6a55;
  --line: #e2d5be; /* data separators only, never an edge */
  --bg: #ece1cb;
  --paper: #fffdf8; /* the raised surface: tile, button, sheet */
  --warn: #96140f;
  --warn-soft: #ffe1dd;
  --pin: #7a4a8c;
  --pin-soft: #f1e7f3;
  --gold: #b8860b;
  --on-gold: #4a3600;
  --pack: #46617a;
  --on-pack: #fff;
  --unserved: #ded2bb;
  --floorfill: #c2a271;
  --reserve: #8a5f9c;
  --reserve-soft: #f3ecf6;
  --reserve-ink: #6b3f80;
  --shape: #b08a55;
  --scrim: rgba(36, 28, 18, 0.35); /* behind the one overlay */

  /* Depth instead of edges: a drop shadow in the light; in the dark the same
     shadow plus a glow as a hairline inner rim. */
  --tile-shadow: 0 1px 2px rgba(58, 42, 22, 0.16), 0 6px 16px rgba(58, 42, 22, 0.16);
  --btn-shadow: 0 1px 2px rgba(58, 42, 22, 0.22);
  --panel-shadow: 0 8px 24px rgba(58, 42, 22, 0.18);
  --chip-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
  --glow: 0 1px 3px rgba(90, 56, 20, 0.45), 0 3px 14px rgba(140, 90, 43, 0.42);
  --tile-fill: var(--paper);

  /* ── The form language, the same in both modes ─────────────────────── */
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  --font-head: var(--font);
  --font-mono: ui-monospace, monospace;
  --r-tile: 2px;
  --r-ctl: 2px;
  --r-pill: 2px;
  --r-fab: 3px;
  --head-tr: uppercase;
  --head-ls: 0.14em;
  --slot-num: 600;

  /* The smallest hit area of anything that can be pressed (#142): 44 × 44,
     the area and not necessarily the drawing (`.hit` below). */
  --hit: 44px;

  /* The type of every field one types or picks in (#146): iOS zooms into a
     focused field under 16 px and does not zoom back out. The cure is the
     type, not the viewport — `maximum-scale` would take pinch zoom from
     everyone. */
  --field-size: 16px;
}

/* Light chosen on a dark device: the tokens above stand, the media block
   below does not apply, and native controls are drawn light. */
:root[data-theme='light'] {
  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --accent: #c9992e;
    --accent-soft: #2a2620;
    --on-accent: #14110c;
    --ink: #f2ece0;
    --muted: #9a9384;
    --line: #35312a;
    --bg: #131210;
    --paper: #1e1b16;
    --warn: #ff9f90;
    --warn-soft: #3d1b18;
    --pin: #b48ee8;
    --pin-soft: #2b2338;
    --gold: #e8c352;
    --pack: #7ea6d4;
    --unserved: #3a362e;
    --floorfill: #6b5f3c;
    --reserve: #a888c8;
    --reserve-soft: #2a2338;
    --reserve-ink: #c8aee4;
    --shape: #8a7a4a;
    --scrim: rgba(0, 0, 0, 0.55);

    /* The glow: an inner rim of gold, far too faint to be called a line — it
       is what makes the edge visible in the dark at all. */
    --tile-shadow: inset 0 0 0 1px rgba(214, 168, 58, 0.3), 0 8px 20px rgba(0, 0, 0, 0.6);
    --btn-shadow: inset 0 0 0 1px rgba(214, 168, 58, 0.34), 0 2px 6px rgba(0, 0, 0, 0.5);
    --panel-shadow: 0 10px 28px rgba(0, 0, 0, 0.62);
    --chip-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
    --glow: 0 0 0 1px rgba(240, 205, 100, 0.75), 0 0 16px rgba(201, 153, 46, 0.55);
    --tile-fill: linear-gradient(#221e18, #1a1713);
  }
}

/* Dark chosen on any device: the same set as in the media block above, word
   for word (held equal by `test/ui-look.test.mjs`), and native controls
   drawn dark. */
:root[data-theme='dark'] {
  color-scheme: dark;
  --accent: #c9992e;
  --accent-soft: #2a2620;
  --on-accent: #14110c;
  --ink: #f2ece0;
  --muted: #9a9384;
  --line: #35312a;
  --bg: #131210;
  --paper: #1e1b16;
  --warn: #ff9f90;
  --warn-soft: #3d1b18;
  --pin: #b48ee8;
  --pin-soft: #2b2338;
  --gold: #e8c352;
  --pack: #7ea6d4;
  --unserved: #3a362e;
  --floorfill: #6b5f3c;
  --reserve: #a888c8;
  --reserve-soft: #2a2338;
  --reserve-ink: #c8aee4;
  --shape: #8a7a4a;
  --scrim: rgba(0, 0, 0, 0.55);

  /* The glow: an inner rim of gold, far too faint to be called a line — it
     is what makes the edge visible in the dark at all. */
  --tile-shadow: inset 0 0 0 1px rgba(214, 168, 58, 0.3), 0 8px 20px rgba(0, 0, 0, 0.6);
  --btn-shadow: inset 0 0 0 1px rgba(214, 168, 58, 0.34), 0 2px 6px rgba(0, 0, 0, 0.5);
  --panel-shadow: 0 10px 28px rgba(0, 0, 0, 0.62);
  --chip-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
  --glow: 0 0 0 1px rgba(240, 205, 100, 0.75), 0 0 16px rgba(201, 153, 46, 0.55);
  --tile-fill: linear-gradient(#221e18, #1a1713);
}

/*
 * The safe areas (#158, F5 a): `viewport-fit=cover` lets every background
 * run to the screen's edge, and the content is inset by what iOS reports —
 * left and right alike, top for the status bar of the standalone app. No
 * value by hand and no script reading which way the phone is turned.
 *
 * The insets go on the **edge children**, never as padding on `.app`: each
 * page carries them as a transparent border, so the strip is the page's own
 * surface (Prepare and Details paper, Plan the ground), and the foot as
 * padding in its own paper. `attachStage()` (`measure.mjs`) reads the same
 * four properties: the sides and the top come off the app's box before the
 * fold sees it, the bottom goes to `fold()` as `insetBottom`. They are
 * registered as lengths so that read gives pixels, and they live on `:root`,
 * where a test can set them — Chromium reports every inset as 0.
 *
 * The bottom inset is applied **upright only** (#158, K4): a strip in the
 * foot's paper under the foot, so the home indicator lies below its entries
 * and not over them. It is the foot's own bottom padding, and the turned foot
 * of the flat stage sets it back to 0 — on the flat stage the pages run to
 * the bottom edge as before and lose no height. The flat stage is the fold's
 * word (`data-flat`), so no script reads which way the phone is turned; the
 * fold reckons the same strip into `stripBottom` (`fold.mjs`). Fullscreen
 * hides the foot, and its strip with it.
 */
@property --inset-top {
  syntax: '<length>';
  inherits: true;
  initial-value: 0px;
}

@property --inset-right {
  syntax: '<length>';
  inherits: true;
  initial-value: 0px;
}

@property --inset-left {
  syntax: '<length>';
  inherits: true;
  initial-value: 0px;
}

@property --inset-bottom {
  syntax: '<length>';
  inherits: true;
  initial-value: 0px;
}

:root {
  --inset-top: env(safe-area-inset-top, 0px);
  --inset-right: env(safe-area-inset-right, 0px);
  --inset-left: env(safe-area-inset-left, 0px);
  --inset-bottom: env(safe-area-inset-bottom, 0px);
}

* {
  box-sizing: border-box;
  /* No flash and no blue under a tapped control (#158, 10b; proto:105). The
     keyboard's focus ring is the browser's `:focus-visible` and stays. */
  -webkit-tap-highlight-color: transparent;
}

html,
body {
  height: 100%;
  margin: 0;
  /* The document holds still (#146): it neither scrolls nor rubber-bands —
     `.app` is the screen at 100dvh, and only the pages scroll, each
     containing its own overscroll (`.page-*` below). */
  overflow: hidden;
  overscroll-behavior: none;
  /* The prototype's family; size and line height stay the browser's, since
     the fold's measured sums (`PLAN_FIXED` 178, the foot's 56) stand on them. */
  font-family: var(--font);
  background: var(--bg);
  color: var(--ink);
}

/*
 * The icons (#142): Lucide, inlined by `icon()` in `views/icon.php`. As big as
 * the text they stand in and its colour, so a button keeps the size its
 * glyph gave it; the few that are drawn at a fixed size say so where they are.
 */
.icon {
  display: inline-block;
  flex: none;
  width: 1em;
  height: 1em;
  vertical-align: -0.125em;
}

.icon-swap {
  display: contents;
}

/*
 * Hit areas (#142, decision 4): everything that can be pressed takes at least
 * 44 × 44 — the **area**, not necessarily the drawing. A target drawn smaller
 * gets an invisible `::after` that grows it to 44 on each axis where it is
 * smaller, centred, so the layout and the fold's measured sums do not move.
 * Neighbours keep a pitch of at least 44 (their gaps say so where they are),
 * so no area lies over another target's drawing.
 *
 * The counter's ± grow outward only, never over the field between them, and
 * the field itself takes its 44 from a transparent border (`.counter-value`).
 */
:where(
  .theme-toggle,
  .grid-fullscreen-toggle,
  .bubble-close,
  .counter > button,
  .step-chip,
  .curve-step,
  .set-chip,
  .info,
  .pin-reset,
  .plan-reset,
  .plan-share,
  .share-copy,
  .link-field-close,
  .set-info a,
  .about a,
  .legend-handle,
  .raffle-trigger,
  .raffle-close,
  .raffle-range,
  .raffle-scroller button,
  .raffle-chip,
  .drop-go,
  .drop-no,
  .notice-fold,
  .notice-close,
  .notice-action,
  .notice-chip,
  .link-report-exit,
  .sheet-check
) {
  position: relative;
}

:where(
  .theme-toggle,
  .grid-fullscreen-toggle,
  .bubble-close,
  .counter > button,
  .step-chip,
  .curve-step,
  .set-chip,
  .info,
  .pin-reset,
  .plan-reset,
  .plan-share,
  .share-copy,
  .link-field-close,
  .set-info a,
  .about a,
  .legend-handle,
  .raffle-trigger,
  .raffle-close,
  .raffle-range,
  .raffle-scroller button,
  .raffle-chip,
  .drop-go,
  .drop-no,
  .notice-fold,
  .notice-close,
  .notice-action,
  .notice-chip,
  .link-report-exit,
  .sheet-check
)::after {
  content: '';
  position: absolute;
  top: min(0px, calc(50% - var(--hit) / 2));
  bottom: min(0px, calc(50% - var(--hit) / 2));
  left: min(0px, calc(50% - var(--hit) / 2));
  right: min(0px, calc(50% - var(--hit) / 2));
}

/*
 * No double-tap zoom on anything that can be pressed (#154). iOS zooms on a
 * quick second tap unless `touch-action` says otherwise — at the ± of a
 * counter, at a head button pressed twice — and a zoomed page is what left
 * the bubbles hanging over the visible edge. `manipulation` keeps panning and
 * **pinch** zoom, which #146 leaves open on purpose; it only drops the
 * double tap. Every control is real markup (#61, "Keyboard and focus"), so
 * the elements are the list; the counter and the bubbles are added because a
 * second tap that misses its ± by a hair lands on them.
 */
:where(button, a, input, select, label, .counter, .bubble, .drop-bubble, .share-bubble, .set-info) {
  touch-action: manipulation;
}

.counter > button:first-child::after {
  left: min(0px, calc(100% - var(--hit)));
  right: 0;
}

.counter > button:last-child::after {
  left: 0;
  right: min(0px, calc(100% - var(--hit)));
}

/* A bubble's ✕ sits in its top corner over the first counter: its hit area
   grows up, out of the bubble, and not down onto the `+` below it. */
.bubble-close::after {
  top: auto;
  bottom: 0;
  height: var(--hit);
}

/* The legend row is lower than 44 and stands 8 above the tile grid: its two
   targets reach down to the grid's edge and no further, and take the rest
   from the diagram's side (the `.plan-stage` gap). */
.plan-legend :where(.legend-handle, .grid-fullscreen-toggle)::after {
  top: min(0px, calc(100% + 8px - var(--hit)));
  bottom: -8px;
}

button,
input,
select {
  font-family: inherit;
}

/* Every field at the field type (#146); a rule that styles a field further
   down keeps it, or iOS zooms on focus again. */
input,
select,
textarea {
  font-size: var(--field-size);
}

/*
 * The fold (#71). The app is the stage: a column of the folded pages over the
 * foot, or — on the flat stage — a row of them beside a foot turned to the
 * right edge. Above the deck it stops growing and gets margins (`--deck`).
 * Every size below comes from `fold()` in `fold.mjs` as a custom property on
 * this element (`foldProperties()`), so the numbers have one home.
 */
.app {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100dvh;
  /* A hit area at the screen's edge reaches past it (`.hit` rules above);
     past the edge there is nothing to touch, and it must not make the page
     scroll sideways. Fixed layers are not clipped by this. */
  overflow-x: clip;
  max-width: var(--deck, none);
  margin: 0 auto;
  /* What the window has beside the deck, on either side. The layer is fixed
     to the viewport, so its insets are counted from the window's edge and
     the deck's margin has to be added to them. */
  --deck-margin: max(0px, (100vw - var(--deck, 100vw)) / 2);
}

.app[data-flat] {
  flex-direction: row;
}

.fold {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  display: flex;
}

/* The columns keep their first widths; everything between the breakpoints
   goes to the `Plan`. At two columns `Prepare` takes the `Plan`'s place, so
   it is the left slot's width there, and its own first width only at three. */
.fold > * {
  flex: 1 1 0;
  min-width: 0;
}

.app[data-columns='2'] .page-details,
.app[data-columns='3'] .page-details {
  /* Its first width plus the right inset it carries at the screen edge (#158). */
  flex: 0 0 calc(var(--col-details) + var(--inset-right));
  box-shadow: var(--panel-shadow);
  /* The curve's seven steps measure their width against this column (#142). */
  container-type: inline-size;
}

.app[data-columns='3'] .page-prepare {
  flex: 0 0 calc(var(--col-prepare) + var(--inset-left));
  box-shadow: var(--panel-shadow);
}

/* The fullscreen is the fold the other way: the `Plan` alone, and it takes
   everything — the shadows of the columns go with them. */
.app[data-fullscreen] .fold > * {
  flex: 1 1 0;
}

/*
 * The three pages (#63): only the active one (`x-show`) ever takes flex
 * space, so `.foot` always sees exactly one sibling above it. `.page-plan`
 * plays the role `.app` itself played before #63 — a column that gives its
 * stage all the room the foot and (when `Plan` is active) the controls
 * rail don't need.
 */
/*
 * Until Alpine runs, every page is plain markup and all three paint at once.
 * `Plan` is the page the shell boots into (#61, "Seite `Plan`, Vollbild aus"),
 * so it is the two others that are covered until `x-show` takes over — which
 * makes the state before the first paint the same state as after it, rather
 * than a flash of all three.
 */
[x-cloak] {
  display: none !important;
}

/* What scrolls is a page, and its overscroll stays in it (#146): at its end
   nothing passes on to the document. `.page-plan` scrolls on the cramped
   stage only (`.app[data-cramped]` below). */
.page-plan,
.page-prepare,
.page-details {
  overscroll-behavior: contain;
  /* The safe areas (#158): a transparent border, so the page's own surface
     runs into the strip, and outside the scrollport, so nothing scrolls
     under the status bar and the sticky heads stick below it. */
  border-style: solid;
  border-color: transparent;
  border-width: var(--inset-top) var(--inset-right) 0 var(--inset-left);
  /* A page that comes to the front fades in (#158, 10a; proto:831-837): 150 ms
     of opacity and no movement, since a switch has no direction (#63). An
     animation and not a transition, because `x-show` brings the page back from
     `display: none`, which starts animations and no transitions. The page
     leaving goes at once; under it is the ground the arriving one fades over. */
  animation: page-fade 150ms linear;
}

@keyframes page-fade {
  from {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .page-plan,
  .page-prepare,
  .page-details {
    animation: none;
  }
}

/* Only the outermost column keeps a side inset; the edges between columns,
   and the one beside the turned foot, have none. Fullscreen is the `Plan`
   alone, edge to edge, at every column count. */
.app[data-columns='2']:not([data-fullscreen]) .page-plan,
.app[data-columns='2']:not([data-fullscreen]) .page-prepare,
.app[data-columns='3']:not([data-fullscreen]) .page-plan,
.app[data-columns='3']:not([data-fullscreen]) .page-prepare,
.app[data-flat]:not([data-fullscreen]) .fold > * {
  border-right-width: 0;
}

.app[data-columns='2']:not([data-fullscreen]) .page-details,
.app[data-columns='3']:not([data-fullscreen]) .page-details,
.app[data-columns='3']:not([data-fullscreen]) .page-plan {
  border-left-width: 0;
}

.page-plan {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.page-prepare,
.page-details {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 8px;
  background: var(--paper);
}

/*
 * The column head (#71): one form, used three times. A title line — the title
 * at 17px and beside it the column's facts, small and still — and for the
 * `Plan` alone a second line (`.col-sub`), because its output line is a fact
 * and not a caption (#61, "The Plan page"). The common measure of the three is
 * the title line, which is why it carries a fixed height of 22: the title
 * stands on every fold, the phone included, beside the foot's word where
 * there is one (#156, F1 a), and only fullscreen goes without — the head
 * keeps its shape in both. `height`, not `min-height`: the title (17px) and
 * the facts (0.8rem) sit on one baseline, and with a minimum only the line
 * box grew past it, to 24, as soon as the title stood (#156, measured 42
 * for the `Plan` head with it, 40 without).
 *
 * Nothing in it wraps, ever: where the line runs out, the facts give way
 * first and the title after them, each cut with an ellipsis (#156, at 320
 * and beside Share and Reset at 360–393).
 *
 * The two scrolling columns hold their head inside the scroller and stick it
 * to the top; the `Plan`'s stands outside its scroller anyway. Same form, same
 * behaviour (prototype, `.colhead`).
 */
.col-head {
  flex: 0 0 auto;
}

.col-titleline {
  display: flex;
  align-items: baseline;
  gap: 8px;
  height: 22px;
  line-height: 22px;
  white-space: nowrap;
  overflow: hidden;
}

.col-title {
  flex: 0 1 auto;
  min-width: 0;
  margin: 0;
  font-size: 17px;
  line-height: 22px;
  letter-spacing: -0.01em;
  overflow: hidden;
  text-overflow: ellipsis;
}

.col-facts {
  flex: 0 1000 auto; /* gives way long before the title does */
  min-width: 0;
  font-size: 0.8rem;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
}

/* `Weekly · 40` before *everything the pool holds* on `Prepare` (#156,
   proto:2257): two facts the prototype sets 8 apart, as its bar's gap. Here
   they are one cut line, so the second is spaced off by its margin. */
.col-facts-type {
  margin-right: 4px;
}

.col-sub {
  margin: 0;
}

.page-details > .col-head,
.page-prepare > .col-head {
  position: sticky;
  top: -8px; /* the page's own top padding, so the head sits flush at the edge */
  z-index: 2;
  margin: -8px -8px 0;
  padding: 8px 8px 6px;
  background: var(--paper);
}

/*
 * The mode button (#144): top right in the head of `Details`, icon only. It is
 * one of the three head buttons — Share, Reset, mode — and they are one family
 * with one form (#144, K-B1): a raised card drawn 32 × 32 on paper with the
 * button shadow, the icon in ink at 17 px, as `.plan-share` has it (#143). It
 * lies over the head's right end rather than in the title line, which clips;
 * the head is sticky and so already the box it is placed in. It is inset from
 * the edge by the head's own padding and centred on the head's height, which
 * it leaves as it was (36 on every fold since #156; 38 / 36 before). The
 * facts give way to it on the right: its 32 plus the inset plus a gap of 8.
 *
 * Its hit area is the rule above's 44, centred across but not down: the page
 * scrolls, and a scroller clips whatever reaches past it, so an area centred
 * on the drawing would lose its top rows above the head's edge. The drawing
 * sits 2–3 px below that edge, so the area starts 2 px above the drawing and
 * takes the rest downward, over the head's lower edge.
 */
.page-details > .col-head {
  padding-right: 48px;
}

.theme-toggle {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 8px;
  margin-block: auto;
  display: inline-grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: none;
  border-radius: var(--r-ctl);
  background: var(--paper);
  box-shadow: var(--btn-shadow);
  color: var(--ink);
  font-size: 17px;
  cursor: pointer;
}

.theme-toggle::after {
  top: -2px;
  bottom: calc(100% + 2px - var(--hit));
}

.plan-stage {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px 8px 0;
  /* The bubble is placed in the stage's own coordinates (#66, `bubble.mjs`),
     so the stage is what its `left`/`top` are measured from. */
  position: relative;
}

.plan-head,
.plan-participation,
.plan-diagram,
.plan-legend,
.plan-rest,
.plan-ranktotal {
  flex: 0 0 auto; /* the fixed parts never shrink; only .plan-grid may */
}

/*
 * The plan head carries Share and Reset-all top right (#143), beside its two
 * lines and not in the title line: `.col-titleline` clips, and is 22 high. The
 * buttons are drawn 32 × 32 — inside the head's 40, so the head and with it
 * `PLAN_FIXED` keep their height — and take 44 × 44 through the hit rule,
 * reaching 6 past the head into the stage's padding above and the gap below.
 * Side by side at a pitch of 44 (32 + 12), so no area lies over the other
 * button's drawing.
 */
.plan-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: 8px;
  align-items: center;
}

.plan-head > .col-titleline,
.plan-head > .col-sub {
  grid-column: 1;
  min-width: 0;
}

.plan-head-actions {
  grid-column: 2;
  grid-row: 1 / span 2;
  display: flex;
  gap: 12px;
}

.plan-reset,
.plan-share {
  display: inline-grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: none;
  border-radius: var(--r-ctl);
  background: var(--paper);
  box-shadow: var(--btn-shadow);
  color: var(--ink);
  font-size: 17px;
  cursor: pointer;
}

/* The full reach is a pin matter, so it wears the pin's colours, as the chip
   it replaces did; the count rides on its corner (#67 AC 6). */
.plan-reset {
  background: var(--pin-soft);
  box-shadow: var(--glow);
  color: var(--pin);
}

.plan-reset-count {
  position: absolute;
  top: -5px;
  right: -6px;
  min-width: 15px;
  padding: 0 3px;
  border-radius: var(--r-pill);
  background: var(--pin);
  color: var(--paper);
  font-size: 0.6rem;
  font-weight: 700;
  line-height: 15px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* The bubble at Share (#155, the desktop branch): a sentence and `Copy link`,
   and below them the link field when no copy could be made. Hidden while
   `shareOpen` is false; placed by `placeShare()` in the visible viewport, like
   the drop question. The width is set rather than shrunk to fit, as the ⓘ
   bubble's is (#154): a fixed box shrinks to the room right of where it last
   stood, and the first placing would measure the width of the previous
   place — measured on #154 at 87 instead of 92, 5 px over the visible edge. */
.share-bubble {
  position: fixed;
  z-index: 40;
  display: none;
  width: min(292px, calc(100vw - 16px));
  padding: 9px 10px;
  border-radius: var(--r-tile);
  background: var(--paper);
  box-shadow: var(--tile-shadow), var(--panel-shadow);
  color: var(--ink);
  font-size: 0.78rem;
}

.share-bubble.is-open {
  display: grid;
  gap: 8px;
  justify-items: start;
}

.share-bubble-note {
  margin: 0;
}

/* `Copy link` in the accent, and `Copied` in its place while the button
   carries `data-link-copied` (`flashCopied()`, #72 AC 3). Both words sit in
   one grid cell, so the button keeps the width of the longer one and does
   not jump under the pointer. */
.share-copy {
  display: inline-grid;
  min-height: 32px;
  padding: 0 12px;
  border: none;
  border-radius: var(--r-ctl);
  background: var(--paper);
  box-shadow: var(--btn-shadow);
  color: var(--accent);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

.share-copy > span {
  grid-area: 1 / 1;
  align-self: center;
}

.share-copy-done {
  visibility: hidden;
}

.share-copy[data-link-copied] .share-copy-done {
  visibility: visible;
}

.share-copy[data-link-copied] .share-copy-idle {
  visibility: hidden;
}

.plan-output {
  font-size: 0.8rem;
  line-height: 18px;
  font-weight: 600;
  /* One line, and worded short enough to stand whole at 393 beside both head
     buttons (#143, K-B3: `packs`, not `tournament packs`): the first height
     counts this line once (`PLAN_FIXED`). K6 below stays the net for the
     narrower phones. */
  /* And it never wraps at all (#129, run 15, K6, closes A10): at 320–340 the
     line is wider than the column, and where it wrapped the head grew from 40
     to 58 and the fixed part outgrew its 178. It scrolls sideways instead —
     dragged on the phones where it happens, no scrollbar to add height. */
  white-space: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
}

.plan-output::-webkit-scrollbar {
  display: none;
}

.plan-participation {
  display: flex;
  justify-content: space-between;
  font-size: 0.85rem;
  color: var(--muted);
}

.plan-diagram {
  height: var(--diagram-height, 120px);
  display: flex;
  align-items: flex-end;
  gap: 1px;
  overflow: hidden;
}

.plan-diagram .bar {
  flex: 1 1 0;
  height: 100%;
  display: flex;
  flex-direction: column-reverse;
  min-width: 1px;
}

.plan-diagram .bar-unserved {
  opacity: 0.35;
}

.seg-reservation {
  background: var(--reserve);
}

.seg-floor {
  background: var(--floorfill);
}

.seg-shaped {
  background: var(--accent);
}

.plan-legend {
  display: flex;
  gap: 16px;
  font-size: 0.75rem;
  color: var(--muted);
}

.plan-legend .legend-item {
  display: flex;
  align-items: center;
  gap: 4px;
}

/* Not positioned (#168): the grid reaches 8 px up into the stage gap with its
   top padding, and a positioned wrap would paint it over the legend's hit
   areas, which reach down into that same gap (#142). */
.plan-grid-wrap {
  flex: 1 1 auto;
  min-height: 113px; /* the two-row master floor, MIN_ROWS in geometry.mjs */
  display: flex;
}

.plan-grid {
  flex: 1 1 auto;
  overflow-y: auto;
  display: grid;
  /* Up to 54 px a column, and narrower where six do not fit: under 365 px
     (6 × 54 + 5 × 5 + 16) the tiles give way in width, never the six-column
     floor (#73, run 14, K3). The rows stay 54, so every height sum stands. */
  grid-template-columns: repeat(var(--plan-columns, 6), minmax(0, 54px));
  /* Rows are the same 54 px as the columns, written out because a reserved
     tile spans two of them and an `auto` row would size itself off the taller
     tile's own content instead (#66). */
  grid-auto-rows: 54px;
  gap: 5px;
  justify-content: center;
  align-content: start;
  /* The scroller clips at its padding box, and the open tile's ring (3 px)
     lies outside the tile: with no padding it was cut at the top row and the
     outer columns (#168). (The hit's pulse reaches further than any padding
     here could, and is drawn outside the scroller instead: `.tile-pulse`.) So the padding sits on the scroller
     with a negative margin against it, the prototype's `.gridwin`
     (proto:748-755) — the tiles stand where they stood, only the clip edge
     lies further out. As far as the stage gives: 8 up into the stage gap, 8
     into the stage's side padding, which lies inside the safe-area border
     (#158) — the prototype's 12 would reach past the stage. The bottom keeps
     its own 4 and no margin: its edge is what the raffle bar's sums read.
     Longhands, because `applyRafflePadding()` writes `padding-bottom` inline. */
  margin-top: -8px;
  margin-right: -8px;
  margin-left: -8px;
  padding-top: 8px;
  padding-right: 8px;
  padding-bottom: 4px;
  padding-left: 8px;
}

/* At the end of the legend row (prototype `slotBlock()`, #73 B5), and no
   taller than the row: the legend enters `PLAN_FIXED` 178 at 21.9 px. */
.grid-fullscreen-toggle {
  margin-left: auto;
  width: 28px;
  height: 20px;
  flex: 0 0 auto;
  align-self: center;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: var(--r-ctl);
  background: var(--paper);
  box-shadow: var(--btn-shadow);
  color: var(--ink);
  font-size: 0.95rem;
  line-height: 1;
  cursor: pointer;
}

/* The tile is a button, because it is the grip (#66): the whole 54px square
   opens the bubble, and the same square closes it. */
.tile {
  position: relative;
  width: 100%; /* the column's, at most 54 px (K3) */
  height: 54px;
  border: none;
  border-radius: var(--r-tile);
  background: var(--tile-fill);
  box-shadow: var(--tile-shadow);
  color: var(--ink);
  font: inherit;
  padding: 0;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.tile[aria-expanded='true'] {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.tile-unserved {
  opacity: 0.45;
}

/*
 * A Display is not a corner mark but a share of the surface: the tile takes
 * four places instead of one. Fixed size from the *first* Display on and
 * independent of the number — so the count stands as a line inside it, because
 * two tiles would otherwise be the same size while one carries twice as much
 * (prototype, `.slot.big`; #61).
 */
.tile-reserved {
  grid-column: span 2;
  grid-row: span 2;
  width: auto;
  height: auto;
  gap: 5px;
}

.tile-reserved .tile-booster {
  font-size: 30px;
}

.tile-displays {
  font-size: 0.65rem;
  font-weight: 600;
  color: var(--reserve);
}

/* Settled: the Rank falls out of the curve and gets exactly its Displays. The
   tone says it without needing a word for it. */
.tile-settled {
  background: var(--reserve-soft);
}

.tile-settled .tile-displays {
  color: var(--reserve-ink);
}

/*
 * The tile carries the conflict itself, and that is load-bearing: minimised,
 * the ConflictNotice is a chip that says *that* — the marked tiles are the
 * only thing left saying *where*, and without them a minimised conflict would
 * claim a valid plan (#61, ADR 0002).
 */
.tile-flagged {
  box-shadow: var(--tile-shadow), inset 0 0 0 2px var(--warn);
}

.tile-flagged .tile-rank {
  font-weight: 700;
  background: var(--warn);
  color: var(--warn-soft);
}

.tile-rank {
  position: absolute;
  top: 3px;
  left: 3px;
  min-width: 16px;
  height: 16px;
  padding: 0 3px;
  border-radius: 8px;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 10px;
  line-height: 16px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.tile-booster {
  font-size: 21px;
  font-weight: var(--slot-num);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.mark {
  position: absolute;
  font-size: 10.5px;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.mark.dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
}

.mark.circle {
  min-width: 17px;
  height: 17px;
  padding: 0 3px;
  border-radius: 9px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.mark-winner {
  top: 4px;
  right: 4px;
  background: var(--gold);
  color: var(--on-gold);
}

.mark-pack {
  bottom: 4px;
  right: 4px;
  background: var(--pack);
  color: var(--on-pack);
}

/*
 * The one bubble (#66). It is **not** an overlay — no backdrop, nothing
 * disabled behind it, and it covers only what it stands on: the app has
 * exactly one overlay and that is the LinkMigration report. Its `left`/`top`
 * come from `bubble.mjs` through `placeBubble()`; nothing here positions it.
 *
 * `max-width` is the prototype's, and it was measured: without a ceiling the
 * bubble ran to the width of its longest sentence and past the column it hangs
 * off. `min-width` keeps the two counters on one line each.
 */
.bubble {
  position: absolute;
  z-index: 38;
  min-width: 190px;
  max-width: 292px;
  padding: 9px 10px;
  border-radius: var(--r-tile);
  background: var(--paper);
  box-shadow: var(--tile-shadow), var(--panel-shadow);
}

.bubble-head {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 12px; /* the ✕ is drawn 6 past the head; the `+` reaches 5 up */
  font-size: 0.75rem;
}

.bubble-by {
  color: var(--muted);
}

.bubble-close {
  display: inline-grid;
  place-items: center;
  min-width: 28px;
  min-height: 28px;
  margin: -6px -6px -6px auto; /* drawn 28 for the finger, laid out as before */
  padding: 0;
  border: none;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 0.95rem;
  line-height: 1;
  cursor: pointer;
}

/* Title left, counter right, both rows on the same edge — otherwise the two
   tasks read as two bubbles under one another (prototype, `.popctl`). */
.bubble-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.bubble-label {
  flex: 1;
  font-size: 0.75rem;
}

/* The sentence that says what a ± hangs on, and — where one is closed — why.
   A greyed button without a reason is the thing this line exists against. */
.bubble-note {
  margin: 3px 0 8px;
  font-size: 0.7rem;
  line-height: 1.35;
  color: var(--muted);
}

.bubble-note:last-child {
  margin-bottom: 0;
}

.plan-ranktotal {
  margin: 0; /* a bare <p> carries a ~1em UA margin the measuring rind never
                sees (getBoundingClientRect excludes margin) — #63 found this
                via the fullscreen state, which removes the opaque controls
                bar that had been painting over the resulting overflow */
  font-size: 0.8rem;
  color: var(--muted);
}

.plan-rest {
  margin: 0;
  padding: 4px 0 8px;
  font-size: 0.8rem;
  font-weight: 600;
}

.plan-controls {
  flex: 0 0 auto;
  position: relative; /* its shadow lies over the grid's edge, not under it */
  z-index: 1;
  padding: 6px 8px;
  background: var(--paper);
  box-shadow: var(--panel-shadow);
}

/*
 * The hot controls themselves (#63): pulled out of `plan.php` into
 * `controls-hot.php`, which was then shared by the fixed rail under `Plan` and
 * `Details`'s Grundform body. Since #64 filled that page from
 * `controls-sheet.php`, this grid is the rail's alone — `.plan-controls` (the
 * rail's own section) supplies the border and background above. The sheet's
 * controls are `.sheet-control` below and do not use this grid.
 */
.controls-hot {
  --rail-cells: repeat(auto-fit, minmax(164px, 1fr));
  display: grid;
  /* One row where the width allows it, two where it doesn't — the same rule
     that keeps the tile grid from needing an `if` on the fold, applied to
     the one other place this ticket's own fixed strip is taller than the
     footer nav the spec's flat-stage math assumed (that nav is later
     tickets' work; these are four real controls). */
  /* 164: title above, then counter (38 + 44 + 38) and the way back (16 +
     28) side by side — at 150 the way back fell under the counter (#142).
     The rail's field is 44, not the sheet's 54, so two cells stand side by
     side down to a 360 stage (2 × 164 + 16 + 2 × 8); below it they stack
     (round 142·145, K2). */
  grid-template-columns: var(--rail-cells);
  /* 10 between the rows: a counter's field reaches 5 past its drawing, so a
     row below must not start inside that (#142). */
  gap: 10px 16px;
}

/*
 * The curve on the rail (#143, decision 6): the sheet's chips over the
 * rail's full width, under a head of title and state word. The chips are
 * 34 high and 44 to the finger (#142), so the row is as high as a counter's:
 * title line, then 34. Under a 348 stage seven chips at 44 no longer fit
 * (7 × 44 + 6 × 4 = 332 for the rail's content) and they break 4 + 3, as on
 * `Details` (round 142·145, K2: "+44 Höhe" at 320). The rail is the query
 * container for that, named, so the sheet's own query (`Details`' column)
 * does not answer for it.
 */
.plan-controls {
  container: rail / inline-size;
}

.hot-curve {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.controls-hot .curve-steps {
  grid-template-columns: repeat(7, minmax(0, 1fr));
}

@container rail (max-width: 331px) {
  .controls-hot .curve-steps {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/*
 * The rail's two numbers are the sheet's row (`control_row()`, #113), so
 * their form is `.sheet-control-head` below; what is the rail's alone is the
 * cell they sit in. A grid cell is narrower than the sheet's column, and
 * where title and counter do not fit side by side the counter goes under the
 * title as a whole, rather than the title breaking — `cap N` stays on the
 * title line (K4 of run 11). The rail's height is counted by the fold
 * arithmetic, so the row keeps the strip's small type.
 */
.controls-hot .sheet-control-head {
  flex-wrap: wrap;
  gap: 2px 6px;
  min-height: 0;
  font-size: 0.75rem;
}

.controls-hot .sheet-control-label {
  font-size: 0.75rem;
}

/* 44 wide on the rail, where the sheet's field is 4.2em (54 here): three
   digits fit, and two counters stand side by side down to 360 (round
   142·145, K2). Only the field narrows — the ± keep their 44 hit areas. */
.controls-hot .counter-value {
  width: 44px;
  padding: 1px 2px;
  font-size: var(--field-size);
}

/*
 * The Details sheet (#64): the Set block and the seventeen controls.
 *
 * Two levels, and the indent is the whole form of the nesting — no
 * disclosure, no frame, no rule (#15, CONTEXT.md `TournamentType`). `.set-sub`
 * therefore carries `padding-left` and nothing else; a `border-left` here
 * would be a line that separates no data.
 */
.set-block {
  margin: 12px 0 14px;
}

.set-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap; /* the row may wrap, the chip group may not — a lone third
                      type on its own line makes the level look broken */
  gap: 14px 8px; /* a wrapped line keeps its hit areas off the one above */
  min-height: 28px;
}

/* Beside the buttons, never above them: two lines per level measured 148px,
   40% of the height in phone landscape (#41). */
.set-label {
  font: 700 11px/1 var(--font-head);
  letter-spacing: var(--head-ls);
  text-transform: var(--head-tr);
  color: var(--muted);
  min-width: 40px;
}

.set-chips {
  display: flex;
  gap: 6px;
  flex-wrap: nowrap;
}

.set-chip {
  padding: 5px 9px;
  min-height: 30px;
  border: none;
  border-radius: var(--r-pill);
  background: var(--paper);
  box-shadow: var(--btn-shadow);
  color: var(--ink);
  font: inherit;
  font-size: 0.8rem;
  cursor: pointer;
}

.set-chip[aria-pressed='true'] {
  background: var(--accent);
  color: var(--on-accent);
  box-shadow: var(--glow);
}

.info {
  width: 22px;
  height: 22px;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: none;
  color: var(--muted);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}

/*
 * The ⓘ's sentence is **the** bubble, floating at its ⓘ (#154; prototype
 * `.pop`, z 38, `max-width: 292px`) — out of the flow, so opening it moves
 * nothing under it. `fixed`, like the drop question, because `Details`
 * scrolls itself; `placeInfo()` writes its `left`/`top` into the visible
 * viewport. The width is set rather than shrunk to fit: a fixed box shrinks
 * to the room right of where it last stood, and the placing would measure
 * the width of the previous place.
 */
.set-info {
  position: fixed;
  z-index: 38;
  width: min(292px, calc(100vw - 16px));
  max-width: 292px;
  padding: 9px 10px;
  border-radius: var(--r-tile);
  background: var(--paper);
  box-shadow: var(--tile-shadow), var(--panel-shadow);
  font-size: 0.78rem;
}

.set-info .bubble-close {
  float: right;
  margin: -6px -6px 0 6px;
}

.set-info p {
  margin: 0 0 6px;
}

.set-info p:last-child {
  margin-bottom: 0;
}

.set-info-muted {
  color: var(--muted);
}

.set-info-foot {
  color: var(--ink);
}

.set-sub {
  /* The indent, and nothing else — it subordinates, it does not separate. */
  padding-left: 18px;
  margin-top: 14px; /* 30-high chips, 7 of hit area each way (#142) */
}

.sheet-group {
  margin-top: 26px;
}

.sheet-group-head {
  /* The line over each group title (#15, #142; prototype `.grouphead`): it
     separates data from data, so it is one of the two places a line is
     allowed. With it no single section needs a box to set itself apart. */
  margin: 0 0 2px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
  font: 700 11px/1.3 var(--font-head);
  letter-spacing: var(--head-ls);
  text-transform: var(--head-tr);
  color: var(--muted);
}

.sheet-subhead {
  margin: 0 0 2px;
  font: 700 0.8rem/1.3 var(--font-head);
}

/* The explanation text stands only here, never at the rail under the plan
   (#64's last criterion). */
.sheet-desc {
  margin: 0 0 8px;
  font-size: 0.75rem;
  line-height: 1.35;
  color: var(--muted);
}

.sheet-foot {
  margin: 4px 0 0;
  font-size: 0.7rem;
  color: var(--muted);
}

.sheet-control {
  margin: 0 0 14px;
}

.sheet-control-head {
  display: flex;
  align-items: center;
  /* Where the title line and the counter with its way back do not fit side
     by side — `Details` at its first width 286, since the counter is drawn
     for the finger (#142) — the counter goes under the title as a whole,
     right-aligned, rather than the title line breaking: `cap N` stays on it. */
  flex-wrap: wrap;
  gap: 4px 8px;
  min-height: 26px;
}

/* Title, state word and note are one line and never break: `cap N` belongs
   on the title line of `Served ranks` (#104, #113). */
.sheet-control-title {
  display: inline-flex;
  flex: 0 1 auto;
  min-width: 0;
  align-items: baseline;
  gap: 6px;
  white-space: nowrap;
}

/* …except a label too long for its column, which wraps inside itself rather
   than pushing the counter off the edge. `Served ranks` is short, so its
   line — title, word, `cap N` — stays one. */
.sheet-control-title .sheet-control-label {
  white-space: normal;
}

.sheet-control-note {
  font-size: 0.7rem;
  color: var(--muted);
}

/* The number stands in the counter and nowhere else — twice the same number
   reads as two values (#64). The title therefore carries no figure. */
.sheet-control-label {
  font-size: 0.82rem;
  font-weight: 600;
}

.sheet-control-unit {
  font-weight: 400;
  font-size: 0.7rem;
  color: var(--muted);
  white-space: nowrap;
}

/* The counter and its way back are one group: where the head wraps, they
   go under the title together, never the way back alone (#142). */
.sheet-control-act {
  flex: none;
  display: flex;
  align-items: center;
  gap: 6px; /* with the way back's 10: 16 between `+` and it */
  margin-left: auto;
}

/* `flex: none`: a long title ("Tournament Packs per Promo-Envelope") may
   wrap, the counter may not shrink — squeezed, the field went to zero width
   and the number vanished (measured at 393 px, #113). */
.counter {
  flex: none;
  margin-left: auto;
  display: flex;
  align-items: stretch;
  height: 34px;
  border-radius: var(--r-ctl);
  box-shadow: var(--btn-shadow);
  background: var(--paper);
}

.counter button {
  position: relative;
  width: 38px;
  padding: 0;
  border: none;
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}

/* A ± that is out of reach says so, rather than moving nothing. Only a wall
   closes one — the core's own cut (`boundsFor()` in controls.mjs, #113) — or
   a minimum; an open number's plus never closes. */
.counter button:disabled {
  opacity: 0.3;
  cursor: default;
}

.counter button:first-child {
  border-radius: var(--r-ctl) 0 0 var(--r-ctl);
}

.counter button:last-child {
  border-radius: 0 var(--r-ctl) var(--r-ctl) 0;
}

/* The number is a field one types into (#113). Wide enough for four digits —
   1152 boosters is a real pool — and framed by the ± on either side, so it
   reads as the counter's middle and not as a form field beside it. The width
   is the 4.2em it had at its old 0.85rem type, held in rem: the field type
   (#146) makes the digits larger, not the field, so no row of the sheet
   wraps differently. */
.counter-value {
  width: 3.57rem;
  min-width: 0;
  /* Drawn 34 high, hit 44: the transparent border is part of the field, the
     background is not, and the negative margin gives the layout its 34 back
     (#142). An input carries no `::after`, so it cannot use `.hit`'s way. */
  margin: -5px 0;
  padding: 0 2px;
  border: none;
  border-block: 5px solid transparent;
  position: relative; /* the border reaches over the line below; it must win */
  z-index: 1;
  background: var(--accent-soft);
  background-clip: padding-box;
  color: var(--ink);
  text-align: center;
  font: inherit;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  font-size: var(--field-size);
}

/*
 * In the tile's bubble the number is a `<span>`, not a field (#154): an
 * `<input>` centres its text by itself, a flex child sets it at the top of
 * its 34 px box. Centred here on both axes, in the content box the field rule
 * leaves it.
 */
.bubble .counter-value {
  display: grid;
  place-items: center;
}

.counter-value:focus {
  outline: none;
  background-color: var(--bg);
}

/* Typed and not yet committed: the plan has not moved, and the field says so
   — dashed and pale, until Enter or leaving the field makes it count (#113). */
.counter.is-draft {
  outline: 1px dashed var(--accent);
  outline-offset: 1px;
}

.counter.is-draft .counter-value {
  font-style: italic;
  color: var(--accent);
}

.step-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px 4px; /* rows 34 + 10: each step's hit area is 44 high (#142) */
  margin: 6px 0 2px;
}

.step-chip,
.curve-step {
  min-height: 34px;
  padding: 4px 2px;
  border: none;
  border-radius: var(--r-pill);
  background: var(--paper);
  box-shadow: var(--btn-shadow);
  color: var(--ink);
  font: inherit;
  font-size: 0.65rem;
  cursor: pointer;
}

.step-chip[aria-pressed='true'],
.curve-step[aria-pressed='true'] {
  background: var(--accent);
  color: var(--on-accent);
  box-shadow: var(--glow);
}

/* A DistributionCurve step is a slope, so it is shown as one (ADR 0001). */
.curve-steps {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 10px 4px;
}

/* Seven steps at 44 need 7 × 44 + 6 × 4 = 332 px; the `Details` column
   has 286 at its first width, so there they go into two rows (#142). */
@container (max-width: 347px) {
  .curve-steps {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.curve-step {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 1px;
  height: 34px;
  padding: 6px 3px;
}

.curve-step i {
  display: block;
  width: 3px;
  background: currentColor;
}

/*
 * The app's one checkbox, `Handout` (#170): its own box and check from the
 * tokens, not the system's blue one. Off is the form of a button (paper,
 * the button's shadow — `--line` is no edge); on is the pressed form of the
 * chips (accent, glow), with Lucide `check` in on-accent.
 *
 * The native input stays the control: clipped out of sight, but focusable
 * and in the accessibility tree — never `display: none` or
 * `visibility: hidden`, or the label's tap, Space and `:focus-visible` break.
 * Since the box is CSS alone, the first paint already shows it, before
 * Alpine binds `:checked` (#158, 10b: no flash of the system look).
 *
 * The box stands on the first line of the label, also where the label wraps:
 * its margins pull it to one line's height, centred on it (`1lh`, the
 * label's own line), and the label's padding centres that line in the 44 px
 * hit height when it is one line only.
 */
.sheet-check {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  min-height: var(--hit);
  padding-block: calc((var(--hit) - 1lh) / 2);
  font-size: 0.78rem;
}

.sheet-check input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  appearance: none;
}

.sheet-box {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  margin-block: calc((1lh - 20px) / 2);
  border-radius: var(--r-ctl);
  background: var(--paper);
  box-shadow: var(--btn-shadow);
}

.sheet-box .icon {
  width: 14px;
  height: 14px;
  visibility: hidden;
}

.sheet-check input:checked + .sheet-box {
  background: var(--accent);
  color: var(--on-accent);
  box-shadow: var(--glow);
}

.sheet-check input:checked + .sheet-box .icon {
  visibility: visible;
}

/* The app's only gold focus ring, and deliberately so (#170, decision K2):
   the real input is clipped out of sight, so the browser's ring would be
   drawn on a 1 px square no one sees — the box has to carry it. Everywhere
   else the browser's own `:focus-visible` ring stays (#158, AC 10b). */
.sheet-check input:focus-visible + .sheet-box {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/*
 * #63's Grundform of `Prepare` — `.prepare-list`, `.prepare-item`,
 * `.prepare-label`, `.prepare-value` — stood here and is gone with the markup
 * it dressed: #65 replaces the three plain pool numbers with the three items
 * and their written-out derivations, whose rules are appended at the end of
 * this file. Left standing, four rules nothing selects.
 */

/*
 * The foot (#63): three page entries, icon over word, never icon alone.
 * Height is content-driven, not set — `.foot-icon`/`.foot-label` fix their
 * own line-height so the rendered height (56px, the spec's Master figure) is
 * a property of the content, not a number pasted onto the container: an
 * entry's 7px padding + 26px icon field (20 + 2 × 3) + 4px gap + 12px label
 * line + 7px padding = 56; `.foot` itself has no border and no vertical
 * padding of its own but the bottom inset. Measured in headless Chrome at
 * 393×852 on 2026-10-08: 56 without an inset, 56 + 34 = 90 with a bottom
 * inset of 34 (#158, K4) (`node --test` cannot see this — #61's Testing
 * Decisions name rendered pixel measures as the shell's explicitly unproven
 * half).
 * Hidden outright in fullscreen (`x-show="!fullscreen"` in foot.php), at
 * every width — #63 AC 5.
 */
.foot {
  flex: 0 0 auto;
  position: relative;
  z-index: 2;
  display: flex;
  /* Its paper runs to the screen's edge; the entries keep off it (#158).
     Upright the paper also runs under the home indicator, a strip of the
     bottom inset below the entries (K4); the turned foot sets it back. */
  padding-left: var(--inset-left);
  padding-right: var(--inset-right);
  padding-bottom: var(--inset-bottom);
  background: var(--paper);
  box-shadow: var(--panel-shadow);
}

.foot-item {
  position: relative; /* paints over the marking laid under it (#158) */
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 7px 0;
  border: none;
  background: none;
  color: var(--muted);
  font: 700 10px/12px var(--font-head);
  letter-spacing: var(--head-ls);
  text-transform: var(--head-tr);
  cursor: pointer;
}

.foot-icon {
  /* 20 + 2 × 3 field = 26; with the 4 gap, the 12 label line and 7 + 7
     padding the foot is its 56 (`FOOT_HEIGHT`), out of its content. */
  display: block;
  box-sizing: content-box;
  width: 20px;
  height: 20px;
  padding: 3px 16px;
  border-radius: var(--r-ctl);
}

.foot-label {
  display: block;
  font-size: 10px;
  line-height: 12px;
}

/*
 * The active entry reads by colour **and** a soft field, never by a stroke
 * (#63 AC 3, #15; prototype `.navbtn[aria-current]`): colour alone was too
 * quiet on 56 px, and a line under it would be a line that separates no data.
 * A field is depth, not a stroke.
 *
 * Since #158 (F4 a) the field is the **whole entry**, not the icon's: one
 * element under the entries (`.foot-mark`), placed and sized by
 * `placeMark()` in `foot.mjs` from the active entry's measured box. On a page
 * switch it slides there — sideways upright, up or down at the turned strip —
 * in the time a page takes to fade. Nothing else moves it with a transition.
 */
.foot-active {
  color: var(--accent);
}

.foot-mark {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--mark-width, 0px);
  height: var(--mark-height, 0px);
  transform: translate(var(--mark-x, 0px), var(--mark-y, 0px));
  opacity: var(--mark-shown, 0);
  border-radius: var(--r-ctl);
  background: var(--accent-soft);
  pointer-events: none;
}

.foot[data-mark-slide] .foot-mark {
  transition: transform 150ms ease, width 150ms ease, height 150ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .foot[data-mark-slide] .foot-mark {
    transition: none;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   The WinnerRaffle (#69) — the grip on the legend and the fixed bar
   ══════════════════════════════════════════════════════════════════════════ */

/*
 * The bar hangs on the fold's two insets and on the strip under the columns
 * (#71), the same three sizes the NoticeStack reads: directly over the foot
 * while `Plan` is a page, inside the `Plan` column from two columns on, and
 * over the whole bottom edge in fullscreen, where the insets and the strip
 * are zero — one rule, every result, no `if` on the surface.
 */
/*
 * The grip on the legend's `winner` entry. It reads as a pill, because it is
 * the one thing in that row that can be pressed — and it is never disabled,
 * however little there is to trigger.
 */
.legend-handle {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 8px 2px 6px;
  border: none;
  border-radius: var(--r-pill);
  background: var(--accent-soft);
  box-shadow: var(--btn-shadow);
  color: var(--ink);
  font: inherit;
  font-size: 0.75rem;
  line-height: 1.4;
  cursor: pointer;
}

.legend-handle[aria-expanded='true'] {
  background: var(--accent);
  color: var(--on-accent);
  box-shadow: var(--glow);
}

.legend-dice {
  font-size: 13px;
}

/* The counter of still-open winner packs. It is absent at zero rather than
   showing a 0: the grip stays reachable, but it stops advertising a throw. */
.legend-open {
  min-width: 15px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--gold);
  color: var(--on-gold);
  font-weight: 700;
  text-align: center;
}

/*
 * A gap all round through which the background shows, and the bubble's own
 * shadow set: edge to edge on `--paper` next to another raised surface, two
 * raised surfaces without a seam read as one broken one. No border instead —
 * #15 decided "no borders, depth from shadow", and a line here would be the
 * first that separates no data.
 */
.raffle-bar {
  position: absolute;
  left: calc(var(--plan-left, 0px) + var(--inset-left) + 8px);
  right: calc(var(--plan-right, 0px) + var(--inset-right) + 8px);
  bottom: calc(var(--strip-bottom, 56px) + 8px);
  z-index: 40;
  padding: 10px 12px 12px;
  border-radius: var(--r-tile);
  background: var(--paper);
  box-shadow: var(--tile-shadow), var(--panel-shadow);
}

.raffle-top {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-bottom: 16px; /* the trigger's and the steps' hit areas meet here (#142) */
}

.raffle-trigger {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 34px;
  padding: 0 14px;
  border: none;
  border-radius: var(--r-ctl);
  background: var(--accent);
  box-shadow: var(--btn-shadow);
  color: var(--on-accent);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

/* The locked trigger — the only thing in this step that is ever locked. */
.raffle-trigger:disabled {
  opacity: 0.4;
  cursor: default;
}

.raffle-stand {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  font-size: 0.75rem;
  line-height: 1.3;
  color: var(--muted);
}

/* The one thing the RafflePot ever shows: that it is empty. It is the reason
   beside a locked button, not a count. */
.raffle-empty {
  display: block;
  color: var(--warn);
}

.raffle-close {
  flex: 0 0 auto;
  display: inline-grid;
  place-items: center;
  min-width: 28px;
  min-height: 28px;
  margin-left: auto;
  padding: 0;
  border: none;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}

.raffle-ranges {
  display: flex;
  flex-direction: column;
  gap: 16px; /* rows 28 + 16: each step's hit area is 44 high (#142) */
}

.raffle-range-row {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 3px;
}

.raffle-range {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1px;
  min-height: 28px;
  padding: 4px 5px;
  border: none;
  border-radius: var(--r-pill);
  background: var(--paper);
  box-shadow: var(--btn-shadow);
  color: var(--ink);
  font: inherit;
  font-size: 0.8rem;
  cursor: pointer;
}

.raffle-range-wide {
  grid-column: 1 / span 2;
  font-size: 0.7rem;
  letter-spacing: 0.04em;
}

.raffle-range[aria-pressed='true'] {
  background: var(--accent);
  color: var(--on-accent);
  box-shadow: var(--glow);
}

.raffle-arrow {
  font-size: 0.6rem;
  opacity: 0.5;
}

.raffle-range[aria-pressed='true'] .raffle-arrow {
  opacity: 0.85;
}

/* The announcement: a fixed, loud place, and no ✕ on it. */
.raffle-hit {
  margin: 9px 0 0;
  font-size: 0.75rem;
}

.raffle-hit-said {
  color: var(--muted);
}

.raffle-takeback-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin: 9px 0 4px;
  font-size: 0.75rem;
}

.raffle-takeback-head > span:first-child {
  color: var(--muted);
}

/* The arrows of the sideways row (#129, run 15, K5). They sit in the head
   line and are no taller than it, so they add no height to the bar; they say
   the row goes on and move it by one pill — no page count. */
.raffle-scroller {
  display: inline-flex;
  align-items: baseline;
  gap: 2px;
  white-space: nowrap;
}

.raffle-scroller button {
  display: inline-grid;
  place-items: center;
  padding: 0 6px;
  border: none;
  background: none;
  color: var(--ink);
  font: inherit;
  line-height: inherit;
  cursor: pointer;
}

.raffle-scroller button:disabled {
  opacity: 0.3;
  cursor: default;
}

.raffle-takeback-list {
  display: flex;
  flex-wrap: wrap;
  gap: 16px 4px; /* rows 28 + 16 (#142) */
}

/* Flat and cramped, the list is one row that scrolls sideways and never
   wraps, so the bar does not grow with it (#129, run 15, K5). Dragged on a
   phone; no scrollbar of its own, which would add height on a desktop with
   classic scrollbars — the arrows above stand in for it. */
.raffle-takeback-row {
  flex-wrap: nowrap;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  /* The scroller would clip its pills' hit areas (`.hit`) above and below;
     the padding takes them in, the negative margin keeps the bar's height. */
  padding-block: 8px;
  margin-block: -8px;
}

.raffle-takeback-row::-webkit-scrollbar {
  display: none;
}

.raffle-takeback-row > .raffle-chip {
  flex: 0 0 auto;
}

.raffle-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: 28px;
  padding: 3px 8px;
  border: none;
  border-radius: var(--r-pill);
  background: var(--paper);
  box-shadow: var(--btn-shadow);
  color: var(--ink);
  font: inherit;
  font-size: 0.7rem;
  cursor: pointer;
}

/*
 * The hit, and it is **fleeting**. The pulse runs for one animation and is
 * taken away again at `animationend` (`measure.mjs`), so nothing on the screen
 * and nothing in the component remembers afterwards which rank was drawn and
 * which was set by hand — that provenance is what the model deliberately does
 * not keep (CONTEXT.md, `WinnerRaffle`).
 *
 * The values are the app's: `scale(1.14)` at 35 %, a 5 px ring, 900 ms, once.
 * A **deliberate departure from the prototype**'s hitpulse (proto:741-747:
 * `scale(1.11)` at 28 %, `.85s`, three times), decided as K1 —
 * https://github.com/ditshej/tcg-prizing/issues/168#issuecomment-6068726592 —
 * with the one condition that the pulse is never cut. It reaches 9.48 px past
 * the tile, more than the grid's padding can hold (≤ 8), so it does not run on
 * the tile in the scroller but on a copy of it in `.tile-pulse`, outside the
 * scroller's clip (`measure.mjs`, `startPulse()`; #168). `PULSE_SCALE`,
 * `PULSE_RING_MAX` and `PULSE_MS` in `geometry.mjs` repeat these numbers.
 */
@keyframes tile-hit {
  0% {
    transform: none;
    box-shadow: var(--tile-shadow), 0 0 0 0 var(--accent);
  }
  35% {
    transform: scale(1.14);
    box-shadow: var(--tile-shadow), 0 0 0 5px var(--accent);
  }
  100% {
    transform: none;
    box-shadow: var(--tile-shadow), 0 0 0 11px transparent;
  }
}

.tile-hit {
  animation: tile-hit 900ms ease-out;
  z-index: 5;
}

/* The layer the pulse runs in (#168, K1): on the tile's box, moved there every
   frame by `measure.mjs`, in the top layer as a manual popover — so the UA's
   popover box (centred, bordered, padded, painted) is undone here — or
   `position: fixed` where there is no popover. Never a target: taps go
   through to the tile beneath, which is only transparent meanwhile. */
.tile-pulse {
  position: fixed;
  inset: auto;
  z-index: 1000;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  overflow: visible;
  pointer-events: none;
}

.tile-pulse > .tile {
  width: 100%;
  height: 100%;
}

.tile.tile-pulsing {
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  .tile-hit {
    animation-duration: 1ms;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   `pinned` against `auto`, and the three reaches of the way back (#67)
   ══════════════════════════════════════════════════════════════════════════ */

/* The marking is a **word**, not a colour: `pinned` and `auto` stand on screen
   as the glossary writes them (CONTEXT.md, `Pinned`), and colour alone would
   not survive either mode. The prototype's violet (`--pin`) carries it, and
   the bold label beside it is its second channel (`.ctl.pinned .lbl`). */
.pin-state {
  font-size: 0.6rem;
  letter-spacing: 0.04em;
  text-transform: lowercase;
  padding: 1px 5px;
  border-radius: var(--r-pill);
  white-space: nowrap;
}

.pin-state.is-pinned {
  color: var(--pin);
  background: var(--pin-soft);
  font-weight: 700;
}

.pin-state.is-auto {
  color: var(--muted);
}

.sheet-control.is-pinned .sheet-control-label,
.sheet-control-head.is-pinned .sheet-control-label {
  font-weight: 700;
}

/* The counter pushes itself right (`.counter { margin-left: auto }`); a
   control without one — Curve, Handout — needs the same shove, or its way
   back sits against the title. */
.pin-spacer {
  margin-left: auto;
}

/* The first reach: one control back on the chosen type, and it asks nothing.
   Dimmed rather than hidden while the control is `auto` — a button that
   appears on the first drag moves the counter beside it — and live rather
   than `disabled`, which would read as broken where it is merely idle. */
.pin-reset {
  display: inline-grid;
  place-items: center;
  /* 28 wide and 10 further from the counter: its hit area (8 each side) and
     the `+`'s (6 outward) fit into the 16–18 between them, and at the
     column's edge it ends on the edge (#142). */
  min-width: 28px;
  min-height: 28px;
  margin-left: 10px;
  border: none;
  background: none;
  padding: 0;
  border-radius: var(--r-ctl);
  color: var(--pin);
  font: inherit;
  font-size: 0.95rem;
  line-height: 1;
  cursor: pointer;
}

.pin-reset.is-idle {
  opacity: 0.28;
}

/*
 * The question. Same form as the tile's bubble and a higher layer than the
 * messages: since the third reach sits in a button *inside* the NoticeStack,
 * a bubble on the stack's own layer had its lower half covered by the message
 * that opened it (prototype, `.pop`: "z 38, nicht mehr 30"). It is placed
 * against the **viewport** (`position: fixed`), because its anchor stands in a
 * column that scrolls itself — an absolutely placed bubble would scroll away
 * from the button it hangs on. That does not make it an overlay: no backdrop,
 * no modality, and it covers nothing but the strip it needs.
 */
.drop-bubble {
  position: fixed;
  z-index: 40;
  min-width: 210px;
  max-width: 292px;
  padding: 9px 10px;
  border-radius: var(--r-tile);
  background: var(--paper);
  box-shadow: var(--tile-shadow), var(--panel-shadow);
}

.drop-head {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  margin-bottom: 6px;
  font-size: 0.78rem;
}

/* Which controls fall and where each one goes — a title and a value per line.
   It wraps rather than scrolls: a list that is cut off is a list that
   under-reports what it destroys. The value keeps its own column so the four
   or five lines read as a table and a long title wraps inside its cell. */
.drop-items {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  column-gap: 5px;
  row-gap: 2px;
  margin: 0 0 6px;
  padding: 0;
  list-style: none;
  font-size: 0.72rem;
  line-height: 1.35;
}

.drop-items li {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
}

.drop-item-label {
  color: var(--pin);
}

.drop-item-arrow {
  color: var(--muted);
}

.drop-item-to {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.drop-note {
  margin: 0 0 9px;
  font-size: 0.7rem;
  line-height: 1.35;
  color: var(--muted);
}

/* Declining has a named place beside confirming — that is the whole reason
   this is a bubble and not a button that turns into a question (#33). */
.drop-ask {
  display: flex;
  gap: 6px;
}

.drop-go,
.drop-no {
  flex: 1;
  min-height: 34px;
  padding: 5px 11px;
  border: none;
  border-radius: var(--r-pill);
  box-shadow: var(--btn-shadow);
  font: inherit;
  font-size: 0.72rem;
  cursor: pointer;
}

.drop-go {
  /* The destructive one, named first and still not the calm one (prototype
     `.popask .go`). */
  background: var(--warn);
  color: var(--warn-soft);
  font-weight: 700;
}

.drop-no {
  background: var(--paper);
  color: var(--ink);
}

/* On the rail the word is the same word, in the compact form the strip is:
   the rail's height is counted by the fold arithmetic (#71), and `cap N`
   beside `RankPoolDepth` wrapped to a second line once a padded badge stood
   in front of it (measured at the picture, 2026-09-30). The state is not
   dropped there — it belongs to the control, not to the page — only its
   box. */
.controls-hot .pin-state {
  padding: 0 2px;
  background: none;
  font-size: 0.58rem;
}

/*
 * ── `Prepare` and its written-out derivations (#65) ────────────────────────
 *
 * The form is the prototype's `prepContent()` (`.prepitem` / `.steps` /
 * `.offerline` in `prototypes/cockpit.prototype.html`, branch
 * `prototype/rank-distribution`): a big headline number with its unit, the
 * raw piece count pushed to the far end, and under it the step sequence as
 * hairline-separated rows whose totals line up on the right.
 *
 * Three things the rows do on purpose:
 *
 * - **The totals are tabular.** A derivation is read down the right edge, so
 *   the figures have to sit under one another — `tabular-nums` and
 *   `margin-left: auto`, never a fixed column width, which would break the
 *   moment a four-digit booster count turns up.
 * - **The separator is between rows and never above the first.** A line here
 *   separates data from data, which is the one place `CONTEXT.md` allows one
 *   (`NoticeStack`); a line under the headline would separate the number from
 *   its own derivation.
 * - **`keep` is the accent, not a warning colour.** The reservation split and
 *   a yield set by hand are facts about where pieces come from, not problems
 *   — the three notice colours belong to the `NoticeStack` and stay there.
 *
 * No scroll fade band, and that is a decision: `Prepare` does not scroll at
 * usual height (#61, 566 px of content against 542 for Release), and a band
 * belongs to the surface that really scrolls.
 */
.prep-items {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 12px;
}

.prep-item {
  padding: 10px 12px;
  border-radius: var(--r-tile);
  background: var(--paper);
  box-shadow: var(--panel-shadow);
}

.prep-bar {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.prep-count {
  font-size: 1.6rem;
  line-height: 1;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.prep-unit {
  font-style: normal;
  font-weight: 600;
  font-size: 0.82rem;
}

.prep-aside {
  margin-left: auto;
  font-size: 0.72rem;
  color: var(--muted);
  white-space: nowrap;
}

.prep-steps {
  margin-top: 8px;
  font-size: 0.75rem;
}

.prep-step {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 3px 0;
  border-top: 1px solid var(--line); /* separates data from data */
}

.prep-step:first-child {
  border-top: 0;
}

.prep-sub {
  color: var(--muted);
}

.prep-value {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.prep-muted {
  color: var(--muted);
}

.prep-keep {
  color: var(--accent);
  font-weight: 600;
}

.prep-keep .prep-sub {
  color: inherit;
}

.prep-note {
  margin: 10px 2px 0;
  font-size: 0.7rem;
  color: var(--muted);
}

/* ══════════════════════════════════════════════════════════════════════════
   The SetupLink on screen (#72)
   ══════════════════════════════════════════════════════════════════════════ */

/* No clipboard: the address in a preselected field, in the Share bubble. */
.link-field {
  display: flex;
  width: 100%;
  gap: 6px;
  align-items: center;
}

.link-field-input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: var(--hit);
  padding: 4px 6px;
  border: none;
  border-radius: var(--r-ctl);
  background: var(--bg);
  box-shadow: var(--btn-shadow);
  color: var(--ink);
  font: var(--field-size) var(--font-mono);
}

.link-field-close {
  display: inline-grid;
  place-items: center;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 1rem;
  cursor: pointer;
}

/* The one overlay. The top layer puts it over everything; the backdrop says
   that the app behind it is not reachable while it stands. */
.link-report {
  max-width: min(420px, calc(100vw - 32px));
  padding: 16px 18px 14px;
  border: none;
  border-radius: var(--r-tile);
  background: var(--paper);
  color: var(--ink);
  box-shadow: var(--panel-shadow);
}

.link-report::backdrop {
  background: var(--scrim);
}

.link-report-title {
  margin: 0 0 8px;
  font-size: 1rem;
}

.link-report-lines {
  margin: 0 0 10px;
  padding-left: 18px;
  font-size: 0.85rem;
  line-height: 1.4;
}

.link-wire {
  font-family: var(--font-mono);
  font-size: 0.92em;
}

.link-report-bookmark {
  margin: 0 0 12px;
  font-size: 0.85rem;
  font-weight: 600;
}

.link-report-exit {
  display: block;
  margin-left: auto;
  min-height: 34px;
  padding: 5px 14px;
  border: none;
  border-radius: var(--r-pill);
  background: var(--accent);
  box-shadow: var(--btn-shadow);
  color: var(--on-accent);
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
}

/*
 * The NoticeStack (#68). A layer over the whole app that covers and pushes
 * nothing: both halves are `position: fixed`, so a standing notice takes no
 * height from the tile grid — it lies over it (#61: measured, a notice in the
 * flow left 11 of 114px of grid, on the maintainer's device none).
 *
 * The open notices stand on the foot, the plan's notice at the top of the
 * stack and the input's at the bottom, beside the controls (#31). The chips
 * have their own anchor one row above the foot: the corner is the lowest free
 * one on the right, and a pill in its own band covers nothing, so it goes to
 * the edge (CONTEXT.md, `NoticeStack`). In fullscreen there is no foot, and
 * the same rule puts both on the bottom edge.
 *
 * The open surface hangs on the fold's two insets, `--plan-left` and
 * `--plan-right` (#71): from two columns on it ends before the controls
 * column and leaves their pin markings free. On one page the insets are 0 and
 * the open notice covers the controls, `pinned` and reset included, on
 * purpose — the lighter loss, since the ways out are clickable in the notice
 * itself (the prototype's comment over `.stack > *`; maintainer decision on
 * #68, run 12, K3 `prototyp-gilt`). It stands on the strip under the columns
 * (`--strip-bottom`: the 56px foot, the 48px strip, or nothing on the flat
 * stage and in fullscreen), and the chips sit where `--chip-right` and
 * `--chip-bottom` put them — all of it out of `foldProperties()`. While the
 * raffle bar is open, the stack rises above it by `--raffle-lift` (measured
 * by the rind), and so do the chips wherever they share its corner
 * (`--chip-raffle`): a ConflictNotice under the bar is not present (ADR 0002;
 * the prototype's `--raffleh`, Runde 16; found at the acceptance by image, #73).
 *
 * Told apart by colour and ways out only, never by form or by how they start
 * (#61). The three tones are the prototype's: warn red, the reserve violet,
 * and the bronze accent of what was just switched — not the pin violet, which
 * sits next to the Offer's and cannot be told apart from it.
 */
/* A chip row standing lifts the open stack above it — where the chips are not
   in a band of their own. */
.notice-layer-chips {
  --notice-chiprow: var(--chip-lift, 42px);
}

.notice-stack {
  position: fixed;
  left: calc(var(--deck-margin, 0px) + var(--plan-left, 0px) + var(--inset-left));
  right: calc(var(--deck-margin, 0px) + var(--plan-right, 0px) + var(--inset-right));
  bottom: calc(var(--strip-bottom, 56px) + var(--notice-chiprow, 0px) + var(--raffle-lift, 0px));
  z-index: 36;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 0 12px 8px;
  max-height: calc(100dvh - var(--strip-bottom, 56px) - var(--notice-chiprow, 0px) - var(--raffle-lift, 0px) - var(--inset-top));
  overflow-y: auto;
  pointer-events: none;
}

.notice {
  pointer-events: auto;
  border-radius: var(--r-ctl);
  padding: 8px 10px;
  font-size: 0.8rem;
  box-shadow: var(--panel-shadow);
}

.notice-conflict {
  background: var(--warn-soft);
  color: var(--warn);
}

.notice-offer {
  background: var(--reserve-soft);
  color: var(--reserve-ink);
}

/* The notice of a switched-off handout is the CarryOverNotice's form (#103, E4). */
.notice-carryOver,
.notice-handoutOff {
  background: var(--accent-soft);
  color: var(--ink);
}

.notice-head {
  display: flex;
  align-items: flex-start;
  gap: 6px;
}

.notice-text {
  flex: 1 1 auto;
}

.notice-line {
  margin: 0;
}

.notice-line + .notice-line {
  margin-top: 2px;
  opacity: 0.8;
}

.notice-lead {
  font-weight: 700;
}

.notice-fold,
.notice-close {
  display: inline-grid;
  place-items: center;
  min-width: 28px;
  min-height: 28px;
  margin: -6px -4px -6px 0; /* drawn 28 for the finger, laid out as before */
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 16px;
  padding: 0;
  cursor: pointer;
  opacity: 0.8;
}

/* Fold and dismiss side by side: 16 between them, where their hit areas
   (8 each) meet (#142). */
.notice-close {
  margin-left: 10px;
}

.notice-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 4px; /* rows 34 + 10: 44 a hit area (#142) */
  margin-top: 6px;
}

.notice-action {
  min-height: 34px;
  font: inherit;
  font-size: 0.75rem;
  border: none;
  background: var(--paper);
  box-shadow: var(--btn-shadow);
  color: inherit;
  border-radius: var(--r-pill);
  padding: 3px 9px;
  cursor: pointer;
}

.notice-chips {
  position: fixed;
  right: calc(var(--deck-margin, 0px) + var(--chip-right, 0px) + var(--inset-right));
  /* Above an open raffle bar wherever the chips share its corner (#73). The
     5 px padding above and below takes in the chips' hit areas, which the
     row's own scroller would clip (#142); the bottom gives them back. */
  bottom: calc(var(--chip-bottom, 64px) - 5px + var(--raffle-lift, 0px) * var(--chip-raffle, 1));
  z-index: 37;
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  max-width: calc(100% - var(--chip-right, 0px));
  overflow-x: auto;
  padding: 5px 12px;
  pointer-events: none;
}

/*
 * The chip carries its colour as a ring, not as a fill, on the raised paper
 * of both modes — on the dark paper a soft tint was not to be seen
 * (prototype, `.chip`). Word and number, never a glyph alone; the ⚠ stands
 * beside the ConflictNotice's word and is the corner's one sign.
 */
.notice-chip {
  pointer-events: auto;
  position: relative;
  height: 34px;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 13px;
  border: 0;
  border-radius: var(--r-pill);
  background: var(--paper);
  font: 600 0.78rem/1 var(--font);
  white-space: nowrap;
  cursor: pointer;
  box-shadow: inset 0 0 0 2px currentColor, var(--chip-shadow);
}

.notice-chip-conflict {
  color: var(--warn);
}

.notice-chip-offer {
  color: var(--reserve);
}

.notice-chip-carryOver,
.notice-chip-handoutOff {
  color: var(--accent);
}

.notice-glyph {
  display: inline-grid;
  font-size: 15px;
}

/* ══════════════════════════════════════════════════════════════════════════
   The fold (#71) — the foot in its three forms, the rail, the flat stage
   ══════════════════════════════════════════════════════════════════════════ */

/*
 * The scroll fade band (#71 AC 10): laid by the rind (`attachFades()`) on the
 * bottom edge of the surface that really scrolls, in that surface's own
 * colour. Under the NoticeStack (36), the bubble (38) and the raffle bar (40).
 */
.fade {
  position: absolute;
  z-index: 28;
  height: 60px;
  pointer-events: none;
}

/*
 * From two columns on the foot is a flat strip, and it is **the place where
 * the page division does not hold**: on the left the switcher, on the right
 * the notice chips, the middle empty. It is 48px — icon beside word — and it
 * stands at every width from the breakpoint on, at three columns too, with
 * its left place empty: a corner that is there or not depending on the width
 * is no corner (#61, "The foot").
 */
.app[data-columns='2'] .foot,
.app[data-columns='3'] .foot {
  /* The strip's 48 above the bottom inset it continues into (#158, K4). */
  height: calc(48px + var(--inset-bottom));
  align-items: center;
  padding: 0 calc(8px + var(--inset-right)) var(--inset-bottom) calc(8px + var(--inset-left));
}

.app[data-columns='2'] .foot-item,
.app[data-columns='3'] .foot-item {
  flex: 0 0 auto;
  flex-direction: row;
  gap: 7px;
  min-height: var(--hit);
  padding: 0 12px;
}

.app[data-columns='2'] .foot-label,
.app[data-columns='3'] .foot-label {
  line-height: 20px;
}

/*
 * The flat stage: below the first height nothing opens side by side. The foot
 * turns to the right edge — the same organism as the strip, only upright — and
 * **takes room rather than lying on top**, so no page reads its right column
 * behind the navigation. Its three entries spread over the whole height.
 */
.app[data-flat] .foot {
  /* The strip's 48 plus the right inset it continues into (#158). No bottom
     inset: turned, the foot runs to the bottom edge like the pages (K4). */
  flex: 0 0 calc(var(--strip-width) + var(--inset-right));
  padding: var(--inset-top) var(--inset-right) 0 0;
  flex-direction: column;
  justify-content: space-evenly;
}

.app[data-flat] .foot-item {
  flex: 0 0 auto;
  gap: 4px;
  padding: 6px 0;
}

.app[data-flat] .foot-icon {
  width: 18px;
  height: 18px;
  padding: 4px 10px;
}

.app[data-flat] .foot-label {
  font-size: 8px;
  line-height: 10px;
  letter-spacing: 0.04em;
}

/*
 * On the flat stage the four hot controls stand upright beside the `Plan` —
 * no new content and no second arrangement: the width goes inward instead of
 * opening a second page (prototype, `hotCol`). `fold()` only puts them there
 * where both first widths fit beside the strip — from 722 (#71, K-B10c);
 * between 436 and 721 they stand on `Details` only.
 */
.app[data-rail='column'] .page-plan {
  flex-direction: row;
}

/* Basis 0 and no minimum: the tile grid's column count is measured off this
   box, and a box that sized itself off the grid would never shrink again. */
.app[data-rail='column'] .plan-stage {
  flex: 1 1 0;
  min-width: 0;
}

.app[data-rail='column'] .plan-controls {
  flex: 0 0 var(--col-details);
  overflow-y: auto;
}

.app[data-rail='column'] .controls-hot {
  grid-template-columns: 1fr;
}

/*
 * …and `Details` and `Prepare` turn two-column inside, with **row flow**,
 * never column flow: every group heading spans both columns and the group
 * stays together under it (#71 AC 7). Column flow would tear a group at the
 * column break and put its second half beside its own heading.
 */
.app[data-flat] .sheet-block,
.app[data-flat] .sheet-group,
.app[data-flat] .prep-items {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: 20px;
  align-items: start;
}

.app[data-flat] .sheet-group > .sheet-group-head,
.app[data-flat] .sheet-group > .sheet-desc {
  grid-column: 1 / -1;
}

.app[data-flat] .page-prepare > .prep-note {
  max-width: none;
}

/*
 * The cramped stage (#73, run 14, K4 `seite-scrollt`): under 436 wide and under
 * the one-column threshold no fold holds two rows in view. Nothing lies on top
 * of anything there — the `Plan` keeps its floor (two tile rows), the rail
 * stands under it, and the `Plan` page scrolls between head and foot. The
 * sizes come from `foldProperties()`.
 */
.app[data-cramped] .page-plan {
  overflow-y: auto;
}

.app[data-cramped] .plan-stage,
.app[data-cramped] .plan-controls {
  flex: 0 0 auto;
}

/* No diagram here (#157, F2 a): the floor shares 173 px with the tiles, and a
   third of that is under the diagram's 60. Hidden by the stylesheet, not left
   to the rind, because the cramped stage is as tall as its content — a drawn
   diagram would feed its own height back into the leftover it is cut from. */
.app[data-cramped] .plan-diagram {
  display: none;
}

.app[data-cramped] .plan-grid-wrap {
  flex: 0 0 var(--two-rows);
}

/*
 * The rail probe (`measureRail()` in `measure.mjs`, #73 K2): a copy of the
 * rail laid out invisibly in its bar form at the stage width, measured and
 * removed in the same task. Whatever form the real rail has now — a column on
 * the flat stage, or hidden — the probe is the bar.
 */
.app .plan-controls.rail-probe {
  position: absolute;
  top: 0;
  left: 0;
  display: block;
  visibility: hidden;
  pointer-events: none;
  overflow: visible;
}

/* The bar's own cells, by the same property, so the probe cannot go on
   measuring a cell the bar no longer has: until #143 it said 176 here while
   the bar had 164 since K2 (round 142·145), and at 360–392 the probe stacked
   the counters the bar stood side by side. */
.app .rail-probe .controls-hot {
  grid-template-columns: var(--rail-cells);
}

/*
 * The about block (#159, form from review 2, point 3, F1 = b): the page's
 * footer, at the very bottom of `Details` after the last group. A line
 * `1px var(--line)` stands before it and no head under that line — the block
 * is the end of the page, not one more group. The line is allowed here
 * because it separates data, the controls, from the colophon (#15 allows a
 * line only where it separates data; review 2, point 3a). The space above it
 * is a group's (26).
 *
 * Both lines centred, small, `--muted`: the Discord line has no precedence in
 * colour or size, only its place on top puts it first. The links wear the
 * line's colour, no underline at rest, and stand out by weight alone; the
 * browser's `:focus-visible` ring stays.
 *
 * Each of the two lines is 44 high and centres its text: the links take
 * their 44 from the hit rule's `::after` (in the `:where` lists above), and
 * with the lines 44 apart no link's area reaches over the other line's link.
 * In the by-line the two links stand three words apart, so neither area
 * lies over the other's word (#142).
 */
.about {
  margin-top: 26px;
  border-top: 1px solid var(--line);
  font-size: 0.7rem;
  color: var(--muted);
  text-align: center;
}

.about p {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  column-gap: 0.3em;
  min-height: var(--hit);
  margin: 0;
}

.about a {
  color: inherit;
  font-weight: 700;
  text-decoration: none;
}

.about-bar {
  margin-inline: 4px;
}
