/* BonBonBot Console: THE TOOLS SHELF.

   New on the console-refresh branch, and deliberately empty: the shelf page
   is being designed rather than restyled, so there is nothing here to carry
   over from console.css.

   WHAT BELONGS HERE: the shelf page itself and anything only it draws. What
   does NOT is the rail's shelf rows, which are navigation and live in
   console-rail.css under "The shelf tools' lockups", and the lockup component
   those rows share with a page headline, which lives there too. A tool's own
   screen keeps its own sheet (console-sugarfix.css, console-momentum.css,
   console-grapevine.css, console-snackbar.css); this is the shelf they are
   reached from, not the tools.

   Every class here is prefixed `cb-shelf-`, for the reason console-snackbar.css
   gives for its own prefix: a sheet whose classes are all its own adjusts
   nothing above it, so its position in the cascade stops being something
   anybody has to reason about.

   Loaded after console-popup.css and before the feature sheets. */

/* ═══════════════════════════════════════════════════════════════════════════
   console-tools.css — THE SHELF GATE (Marquee)

   The page that stands in front of ONE shelf tool. Every class is prefixed
   cb-shelf-, so this sheet adjusts nothing above it.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ═══ THE ORCHESTRATION ════════════════════════════════════════════════════
   ── WHY THIS SURFACE HAS ITS OWN BEAT AND DOES NOT USE --cbc-step ─────────
   --cbc-step is capped at --cb-motion-base, and console-theme.css gives the
   reason: past a quarter second the eye stops reading a LIST as sequence and
   starts reading it as slow. That cap is exactly right for twenty table rows
   and exactly wrong here. This is not a list, it is one composed page with
   about a dozen parts, and the whole point is that they arrive in an order you
   can follow. Capped at 250ms the last eight would land together.

   So: a longer step and a much higher ceiling, both still derived from the
   brand's motion tokens rather than typed. Nothing else in the console gets
   this, and nothing else should.

     beat      60ms apart, ceiling 900ms   the page's own ladder
     arrive    500ms                       a part settling into place
     reveal    700ms                       a headline line clearing its mask

   Everything rides --cbc-spring, which is fast out and slow to settle. The
   whole entrance is over in about 1.3 seconds, which is how long it takes to
   read the headline, which is the rule. */
/* ── DECLARED ON EVERY ELEMENT, NEVER ONCE ON THE PARENT ───────────────────
   A var() inside a custom property resolves WHERE THE PROPERTY IS DECLARED.
   Declared once on .cb-shelf-gate, var(--i) resolved against the gate, where
   --i is unset, so --cb-shelf-beat computed to 0ms and every descendant
   inherited that already-computed zero. The whole staggered entrance ran as
   one simultaneous fade, and it looked deliberate: nothing threw, nothing
   logged, and the only way to see it was to read animation-delay off the
   elements, which came back 0s on all twelve.

   console-theme.css hit this first and wrote it down at --cbc-step: "a copy at
   :root would resolve --i against the root and hand every element a zero." It
   declares its steps on `*, *::before, *::after` for exactly this reason. This
   does the same, scoped to this surface rather than globally, because this
   sheet owns one page and has no business declaring anything on `*`. */
.cb-shelf-gate,
.cb-shelf-gate *,
.cb-shelf-gate *::before {
  --cb-shelf-beat: min(
    calc(var(--i, 0) * var(--cb-motion-fast) / 2.5),
    calc(var(--cb-motion-slow) * 2.25)
  );
}

.cb-shelf-gate {
  --cb-shelf-arrive: calc(var(--cb-motion-slow) * 1.25);
  --cb-shelf-reveal: calc(var(--cb-motion-slow) * 1.75);
  /* ── THE HAZE IS IN `em`, AND THAT IS THE ANSWER RATHER THAN A DODGE ──────
     The headline resolves out of a blur, which is a length, and the token set
     has no blur scale. Borrowing --cb-space-md would pass every guard while
     being a SPACING token doing optics' work, which is precisely the fault
     rule 13 names. A raw pixel is refused by check:tokens, correctly.

     `em` is right on its own merits, not as a way round the guard: this blur
     is a property of the TYPE it softens, and the headline is a clamp between
     30 and 72 pixels. At a fixed radius the haze is a whisper on a laptop and
     a fog on a monitor. At 0.12em it is the same fraction of a letter at every
     size, which is what "out of focus" means. rem and em are deliberately
     outside check:tokens' scope; check-tokens.ts carries the reasoning. */
  --cb-shelf-haze: 0.12em;

  /* Its own container, so the headline sizes off the COLUMN it lives in
     rather than off the window. 7.5cqi puts a twenty-two character line just
     inside the measure at every width the console has, which is what makes the
     authored line breaks hold instead of rewrapping. */
  container-type: inline-size;
  container-name: gate;

  /* THE LOCKUP'S SIZE LIVES HERE, with the rest of what this surface decides.
     It was a second `.cb-shelf-gate` block further down, which is one class
     defined twice unconditionally: harmless today and exactly the shape of the
     `.cb-shelf-head` collision that cost this page its stacked headline. */
  /* SIZED OFF THE HEADLINE, so it takes a TYPE token and not a spacing one: a
     cap height is type, and tool-marks.ts's whole argument is that a header's
     mark is a question about the headline it stands in for. */
  --cb-shelf-cap: var(--cb-type-4xl-size);

  /* A MEASURE, because this is a poster and not a table. `.cb-main` caps every
     screen at --cbc-measure, which is right for a wide data table and wrong
     here: at that width the checklist column stretched past a thousand pixels
     and left the code field alone at the far edge of the screen, with the eye
     having to travel the whole way between the two halves of one decision. */
  max-inline-size: var(--cb-width-2xl);

  display: grid; gap: var(--cb-space-5xl);
  padding-block: var(--cb-space-9xl) var(--cb-space-8xl);
}

/* Every part that simply settles into place. Up and in, on the spring, one
   beat after the part before it. */
.cb-shelf-settle {
  animation: cb-shelf-settle var(--cb-shelf-arrive) var(--cbc-spring) var(--cb-shelf-beat) backwards;
}
@keyframes cb-shelf-settle {
  from { opacity: 0; transform: translate3d(0, var(--cb-space-2xl), 0); }
}

/* ═══ THE LOCKUP ═══════════════════════════════════════════════════════════
   The mark itself is drawn by console-rail.css, which owns .cb-lockup and the
   shelf's artwork. What this sheet decides is how big it is HERE.
   Sized off the HEADLINE rather than off the rail's fit, which is the
   distinction tool-marks.ts draws: a rail row holds a mark beside five others,
   so its size is a question about its neighbours; a header holds one mark and
   no neighbours, so its size is a question about the type it stands over.

   It arrives first and it arrives differently: a small scale up rather than a
   travel, because a logo settling into focus is the one thing on the page that
   should feel like it was always there. */
.cb-shelf-crest {
  animation: cb-shelf-crest var(--cb-shelf-reveal) var(--cbc-spring) backwards;
}
@keyframes cb-shelf-crest {
  from { opacity: 0; transform: scale(0.94); }
}

/* ═══ THE LOCKUP ═════════════════════════════════════════════════════════════
   IT IS THE CONSOLE'S OWN COMPONENT AND ITS ONLY LEVER IS WIDTH. tool-marks.ts
   returns `.cb-lockup`: a typed fallback holding the box open and two absolutely
   positioned images at `inline-size: 100%`. So a height cannot be set on it, and
   whatever width it has, the drawing fills.

   WHAT WENT WRONG WITHOUT THIS RULE, because it is worth not repeating. Left to
   itself the lockup takes console-rail.css's default of `--cbc-mark-span`, which
   is `58%`. In a rail row that is a sensible share of a row. Dropped into a page
   column it is 58% of the COLUMN: on a wide monitor Sugarfix came out about nine
   hundred pixels across and a hundred and forty tall, and sat on top of the
   headline. A percentage is only ever a share of whatever it lands in, and this
   landed somewhere its author never had to think about.

   console-header.css solves the same problem for `.cb-h1-mark` and cannot solve
   it for this: that selector is the page header's, and this gate heads itself.

   ── WIDTH PER TOOL, WHICH IS WHAT OPTICAL PARITY COSTS ────────────────────
   One width across five drawings makes one of them wrong, because an artboard
   is not an optical size: Sugarfix is 6.59:1 and Grapevine is 4.23:1 with a
   grape cluster over the word. Each ratio below is that drawing's width for the
   cap this gate asks for, measured off the artwork the way tool-marks.ts
   measures its lift: the letter body between the 12th and 88th percentile of
   ink down the artboard, then a correction by eye, because no measurement can
   know that an all-caps wordmark reads larger than a mixed-case one at the same
   letter height and that a script reads smaller. Sitesaver is the reference. */
.cb-shelf-crest .cb-lockup {
  inline-size: calc(var(--cb-shelf-cap) * var(--cb-shelf-wide, 3));
}
.cb-shelf-crest[data-tool="sitesaver"] { --cb-shelf-wide: 3.35; }
.cb-shelf-crest[data-tool="sugarfix"] { --cb-shelf-wide: 3.03; }
.cb-shelf-crest[data-tool="grapevine"] { --cb-shelf-wide: 2.96; }
.cb-shelf-crest[data-tool="snackbar"] { --cb-shelf-wide: 2.95; }
.cb-shelf-crest[data-tool="bonbonniere"] { --cb-shelf-wide: 3.18; }
/* The fallback is a real headline here, as it is in a page header, rather than
   the rail's small-caps wordmark: this stands where a headline stands. It also
   holds the box open, so it is what stops a mark overlapping the line below. */
.cb-shelf-crest .cb-lockup-word {
  font-size: var(--cb-type-2xl-size);
  line-height: var(--cb-type-2xl-line-height);
  font-weight: var(--cb-font-weight-semibold);
  text-transform: none;
  letter-spacing: normal;
}

/* ═══ THE HEADLINE ═════════════════════════════════════════════════════════
   ── AUTHORED IN LINES, REVEALED PER LINE ───────────────────────────────────
   Each line is its own window and the words inside it are what travel through
   it. One element cannot clip its own text and move it at the same time, which
   is the note console-header.css already carries for the console's own page
   headings; this is the same mechanism at poster size.

   The padding is not spacing, it is descender headroom: at this size the tail
   of a "y" sits below the line box and an overflow:hidden that does not allow
   for it shaves the bottom off "already". The negative margin hands the space
   straight back, so the headline occupies exactly what it would have.

   THE HAZE IS WHAT MAKES IT READ AS A REVEAL RATHER THAN A SLIDE. Clipping
   alone is a hard edge wiping upward, which is right for a word being written
   and too emphatic for a sentence arriving. Resolving out of a blur over the
   same curve is what turns the wipe into a settle. It is three elements for
   700ms and then it is over, which is a different thing from the persistent
   backdrop-filter console-theme.css measured and turned off. */
.cb-shelf-headline {
  margin: 0;
  /* ── SET TO THE COLUMN, NOT TO A TYPE STEP ────────────────────────────────
     It was clamped at --cb-type-hero-size, and hero is 72px: the largest step
     the token set has, and a step chosen for a heading rather than for a
     poster. At that ceiling the longest slogan stopped about two thirds of the
     way across and the line under it stopped sooner, so the composition read as
     indented rather than as set.

     8.3cqi puts it at 85px against the wide measure and 47 against the narrow,
     and the ceiling is a multiple of hero rather than a new number so it is
     still the type scale deciding.

     IT IS NOT SIZED TO FILL THE COLUMN, and that is the decision rather than an
     oversight. Sized to fill, the longest slogan wants about 113px, and at that
     size the headline is the loudest thing in the console by some distance on a
     screen whose job is to ask for six digits. It was tried at 113 and is 85 on
     purpose. What that costs is measured: the five reach 62 to 74 percent of
     the column, so the type stops short of the rule under it. Anybody moving
     this number should move it knowing the trade rather than discovering it.

     MEASURED WITH A RANGE OVER THE TEXT NODE, not off the span. The lines are
     display:block, so their own rects are the container width and report a
     confident 100% at any font size at all; the first pass at this number was
     taken that way and was meaningless. Letterform widths belong to the
     typeface, and arithmetic is not to be trusted about them. */
  font-size: clamp(
    var(--cb-type-2xl-size),
    8.3cqi,
    calc(var(--cb-type-hero-size) * 1.2)
  );
  line-height: 1.04;
  letter-spacing: var(--cb-type-3xl-letter-spacing);
  font-weight: var(--cb-font-weight-bold);
}
.cb-shelf-line {
  display: block; overflow: hidden;
  padding-block-end: 0.14em; margin-block-end: -0.14em;
}
.cb-shelf-line > span {
  display: block;
  animation: cb-shelf-clear var(--cb-shelf-reveal) var(--cbc-spring) var(--cb-shelf-beat) backwards;
}
@keyframes cb-shelf-clear {
  from { opacity: 0; transform: translate3d(0, 108%, 0); filter: blur(var(--cb-shelf-haze)); }
}

/* ── THE DESCRIPTOR IS A TITLE OVER THE LIST, NOT A LABEL UNDER THE LOGO ───
   It sat between the lockup and the headline, where it was a third small thing
   before the one big thing and it delayed the only line on the page anybody
   reads first. Over the checklist it has a job: `does` is the plain sentence
   that says what the tool IS, and the four lines under it are what that means
   in practice, so it heads them. The lockup now runs straight into the
   headline, which is the order a poster has always used.

   Ink-muted rather than ink-faint, because it is a heading here and was a
   label there. The console's own section headings are small uppercase
   (.cb-nav-group, .cb-report-head), so this borrows a treatment the screen
   already has rather than inventing a second one. */
.cb-shelf-title {
  margin: 0 0 var(--cb-space-2xl);
  font-size: var(--cb-type-xs-size); font-weight: var(--cb-font-weight-semibold);
  text-transform: uppercase; letter-spacing: var(--cb-tracking-xxs);
  color: var(--cbc-ink-muted);
}
/* NO MEASURE ON IT. A reading measure is right for a paragraph and wrong for
   the one line under a poster headline: --cb-width-lg cut it at 640 and left it
   ending well short of the rule below, which reads as an accident rather than
   as a decision. These are one short sentence each, so what this actually does
   is stop the line breaking early rather than let it run long. */
.cb-shelf-lede {
  margin: 0;
  font-size: var(--cb-type-md-size); line-height: var(--cb-type-md-line-height);
  color: var(--cbc-ink-muted);
}

/* ═══ THE RULE ═════════════════════════════════════════════════════════════
   It DRAWS, from the start edge, and it is the hinge of the whole entrance:
   everything above it is the pitch and everything below it is the work, so the
   line arriving is what separates the two. On the flat ease rather than the
   spring, because a line being drawn is a quantity rather than an object
   arriving, which is the same reasoning --cbc-plot carries for a chart. */
.cb-shelf-rule {
  block-size: 1px; background: var(--cbc-line);
  transform-origin: left;
  animation: cb-shelf-draw calc(var(--cb-motion-slow) * 1.5) var(--cb-motion-ease) var(--cb-shelf-beat) backwards;
}
@keyframes cb-shelf-draw { from { transform: scaleX(0); } }

/* ═══ THE FOOT: what it does on the left, what it wants on the right ═══════ */
/* WHAT IT DOES ON THE LEFT, WHAT IT WANTS ON THE RIGHT.

   The ask column takes --cb-width-xs rather than --cb-width-sm, and the
   hundred pixels that buys goes to the sentences. Six slots are perfectly
   legible at the narrower measure; a four-item feature list at 384px wraps
   every line, which turns a scannable column into a paragraph. */
.cb-shelf-foot {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, var(--cb-width-xs));
  gap: var(--cb-space-8xl);
  align-items: start;
}
/* ═══ THE REEL ═════════════════════════════════════════════════════════════
   Ten lines, four on screen. There are more true things to say about each of
   these tools than a column has room for, and a list of ten is a list nobody
   finishes.

   ── IT CRAWLS, AND IT IS THE ONE LINEAR THING IN THIS SHEET ───────────────
   A stepped version was built and compared, and this is the one that was
   chosen: the drift is calm enough to read past and never asks for attention.
   Linear is correct here and nowhere else. Rule 14's "never linear" is about
   things that ARRIVE, and a thing that arrives should decelerate into its
   resting place; this never arrives and never rests, and any easing on a
   seamless loop is a visible stutter once per cycle at the seam.

   ── FIXED ROWS ARE WHAT MAKE IT DETERMINISTIC ─────────────────────────────
   Every row is exactly --cb-shelf-row, so four rows is a height rather than a
   measurement, the step is one row rather than a query, and a line that ran
   long could never quietly make it five. The copy is written to fit (data).

   ── THE EDGES DISSOLVE, THEY DO NOT CLIP ──────────────────────────────────
   A mask rather than a border, so a line entering and leaving fades instead of
   being cut in half. About half a row at each end, which leaves the middle two
   entirely clean. */
.cb-shelf-list {
  --cb-shelf-row: var(--cb-space-6xl);
  display: block; margin: 0; padding: 0; list-style: none;
}
.cb-shelf-reel {
  block-size: calc(var(--cb-shelf-row) * 4);
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom, transparent, var(--cb-color-black) 14%, var(--cb-color-black) 86%, transparent);
  mask-image: linear-gradient(to bottom, transparent, var(--cb-color-black) 14%, var(--cb-color-black) 86%, transparent);
}
/* ── IT WAITS FOR THE ENTRANCE, THEN NEVER STOPS ──────────────────────────
   Started on render it would be crawling while the lines were still arriving,
   which is two motions arguing over the same pixels. The delay is the LAST
   SEAT's beat plus the time that seat takes to land, so the reel begins at the
   exact moment the page has finished assembling itself. The track carries
   --i:11, the same seat the last arriving element has, so this reads out of the
   ladder rather than out of a number somebody totalled by hand.

   LINEAR, AND IT IS THE ONE LINEAR THING IN THIS SHEET. Rule 14's "never
   linear" is about arrival: a thing coming to rest should decelerate. This does
   not arrive and never rests, and any easing on a seamless loop is a visible
   stutter once per cycle at the seam. */
/* ── AND IT IS UNCONDITIONAL, WHICH IT WAS NOT ────────────────────────────
   This rule read `[data-reel="glide"] .cb-shelf-track`, and NOTHING IN THE
   CONSOLE EVER WRITES THAT ATTRIBUTE. It was a switch on the mockup's own
   harness, where a toggle let the stepped and continuous versions be compared,
   and it came across with the stylesheet while the toggle did not. The result
   is the failure this whole sheet keeps meeting: a selector that matches
   nothing costs nothing, says nothing, and looks exactly like a rule that
   works. The reel simply stood still.

   A shipped stylesheet has no harness in it. There is one behaviour, so there
   is no attribute to ask about. */
.cb-shelf-track {
  animation: cb-shelf-roll calc(var(--cb-motion-creep) * 2) linear infinite;
  animation-delay: calc(var(--cb-shelf-beat) + var(--cb-shelf-arrive));
}
/* THE LOOP TRAVELS THE REAL ROWS, NOT HALF THE TRACK. -50% would be half of
   fourteen rows, and the track is ten real ones plus a four row echo: the crawl
   would have landed seven rows down and jumped. The distance is the row height
   times how many features there actually are, which the markup declares, so a
   tool with nine or eleven works without anybody editing this. */
@keyframes cb-shelf-roll {
  to { transform: translate3d(0, calc(var(--cb-shelf-row) * var(--cb-shelf-n, 10) * -1), 0); }
}
/* A LIST THAT MOVES WHILE SOMEBODY IS READING IT IS HOSTILE. Approaching it
   stops it, and so does tabbing into it, which is the same courtesy by
   keyboard. */
.cb-shelf-reel:hover .cb-shelf-track,
.cb-shelf-reel:focus-within .cb-shelf-track { animation-play-state: paused; }
.cb-shelf-list li {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--cb-space-lg);
  align-items: center;
  block-size: var(--cb-shelf-row);
  font-size: var(--cb-type-sm-size); line-height: var(--cb-type-sm-line-height);
  color: var(--cbc-ink-muted);
}
/* The tick is a mark, so it is held to 3:1 and takes the accent; the sentence
   beside it is text at 4.5:1 and takes the ink. */
.cb-shelf-tick {
  display: grid; place-items: center;
  inline-size: var(--cb-space-2xl); block-size: var(--cb-space-2xl);
  border-radius: var(--cb-radius-full);
  background: var(--cbc-accent-quiet); color: var(--cbc-accent-text);
}
.cb-shelf-tick svg { inline-size: var(--cb-space-lg); block-size: var(--cb-space-lg); }
.cb-shelf-ask { display: grid; gap: var(--cb-space-xl); }
.cb-shelf-detail {
  margin: 0;
  font-size: var(--cb-type-xs-size); line-height: var(--cb-type-xs-line-height);
  color: var(--cbc-ink-faint);
}

/* ═══ THE CODE FIELD ═══════════════════════════════════════════════════════
   Six rules and ONE input. mfa/digits.ts holds the argument and the build
   reuses that control rather than this drawing of it: six separate boxes means
   six inputs and a pile of code moving focus between them, which breaks paste,
   breaks the operating system's own one-time-code offer, breaks a screen reader
   and breaks backspace. Drawn as rules rather than boxes because six filled
   boxes would be the heaviest thing on a page whose subject is a headline. */
.cb-shelf-code { position: relative; display: grid; inline-size: 100%; }
.cb-shelf-slots { display: grid; grid-template-columns: repeat(6, 1fr); gap: var(--cb-space-lg); }
.cb-shelf-slot {
  display: grid; place-items: center;
  block-size: var(--cb-space-7xl);
  /* MEASURED, NOT PICKED. This underline is the whole affordance: it is what
     says six digits go here, so WCAG holds it to 3:1 as a graphical object
     rather than treating it as a hairline between two surfaces. On the page
     ground --cbc-line is 1.57 in dark and 1.29 in light, and --cbc-line-strong
     only reaches 1.62 in light: both are tuned to divide two panels, not to be
     seen against the page. --cbc-ink-faint is the lowest step in the set that
     clears the bar on the WORST of the two grounds (4.56 light, 5.94 dark).

     The console's own code slots (console-gate.css) clear the same bar a
     different way, with a --cbc-sunken FILL, so the box is a shape rather than
     a line. That is the right answer for six boxes and the wrong one here: a
     filled box is the heaviest thing on a page whose subject is a headline. */
  border-block-end: 2px solid var(--cbc-ink-faint);
  font-size: var(--cb-type-2xl-size); font-weight: var(--cb-font-weight-medium);
  font-variant-numeric: tabular-nums;
  transition: border-color var(--cb-motion-fast) var(--cbc-spring);
}
.cb-shelf-slot[data-filled] { border-block-end-color: var(--cbc-ink); }
/* A digit LANDS. One read off a slip of paper arrives with no feedback at all
   in a plain box; here it drops into its own slot and the slot answers, with
   nothing queued behind it, so six typed quickly is six separate answers. */
.cb-shelf-slot[data-filled] > span { animation: cb-shelf-land var(--cb-motion-base) var(--cbc-spring); }
@keyframes cb-shelf-land { from { transform: scale(0.4) translateY(var(--cb-space-md)); opacity: 0; } }
/* WHICH SLOT IS NEXT is the focus indicator, and it is the right one: it moves
   as the code is typed, where a static ring round all six is on the whole time
   and says nothing. Drawn rather than left to :focus-within, because what has
   to change is WHICH slot is lit and CSS cannot count characters. */
.cb-shelf-code[data-at="0"] .cb-shelf-slot:nth-child(1),
.cb-shelf-code[data-at="1"] .cb-shelf-slot:nth-child(2),
.cb-shelf-code[data-at="2"] .cb-shelf-slot:nth-child(3),
.cb-shelf-code[data-at="3"] .cb-shelf-slot:nth-child(4),
.cb-shelf-code[data-at="4"] .cb-shelf-slot:nth-child(5),
.cb-shelf-code[data-at="5"] .cb-shelf-slot:nth-child(6) { border-block-end-color: var(--cbc-accent); }
.cb-shelf-entry {
  position: absolute; inset: 0; inline-size: 100%; block-size: 100%;
  border: 0; padding: 0; background: transparent;
  color: transparent; caret-color: transparent; font: inherit; outline: none;
}
/* A REFUSAL SHAKES AND EMPTIES, over about the width of one slot: enough to
   read as no, short enough that nobody watching their own typing loses their
   place. Never colour alone, because colour alone is not a label. */
[data-state="no"] .cb-shelf-slots { animation: cb-shelf-no var(--cb-motion-base) var(--cb-motion-ease); }
@keyframes cb-shelf-no {
  25% { transform: translate3d(calc(var(--cb-space-md) * -1), 0, 0); }
  50% { transform: translate3d(var(--cb-space-md), 0, 0); }
  75% { transform: translate3d(calc(var(--cb-space-xs) * -1), 0, 0); }
}
[data-state="won"] .cb-shelf-slot { border-block-end-color: var(--cbc-accent); }

.cb-shelf-answer {
  min-block-size: var(--cb-type-sm-line-height); margin: 0;
  font-size: var(--cb-type-sm-size); line-height: var(--cb-type-sm-line-height);
  color: var(--cbc-ink-faint);
}
[data-state="no"] .cb-shelf-answer { color: var(--cbc-poor-text); }
[data-state="won"] .cb-shelf-answer { color: var(--cbc-good-text); }
[data-state="checking"] .cb-shelf-answer { animation: cb-shelf-think var(--cb-motion-work-loop-calm) var(--cb-motion-ease) infinite; }
@keyframes cb-shelf-think { 50% { opacity: 0.45; } }

/* TURNED OFF UNTIL THERE ARE SIX DIGITS, never removed and never the disabled
   attribute (rule 15): a disabled control stops receiving pointer events, so it
   cannot carry the tooltip that explains why it is off. aria-disabled plus a
   data-tip, exactly as dom.ts's enable() does it, and the handler checks the
   same attribute, which is what pressable() is. */
.cb-shelf-go {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--cb-space-md);
  justify-self: start;
  padding: var(--cb-space-lg) var(--cb-space-3xl);
  border: 0; border-radius: var(--cb-radius-full);
  background: var(--cbc-accent); color: var(--cbc-on-accent);
  font: inherit; font-size: var(--cb-type-sm-size); font-weight: var(--cb-font-weight-semibold);
  cursor: pointer;
  transition: background var(--cb-motion-fast) var(--cbc-spring),
              opacity var(--cb-motion-fast) var(--cbc-spring);
}
.cb-shelf-go:hover { background: var(--cbc-accent-hover); }
.cb-shelf-go[aria-disabled="true"] { opacity: 0.45; cursor: default; }
.cb-shelf-go[aria-disabled="true"]:hover { background: var(--cbc-accent); }
.cb-shelf-go svg { inline-size: var(--cb-space-xl); block-size: var(--cb-space-xl); }
/* The arrow answers the pointer and moves nothing: it travels inside its own
   box, so no layout shifts under the hand approaching it. */
.cb-shelf-go svg { transition: transform var(--cb-motion-fast) var(--cbc-spring); }
.cb-shelf-go:not([aria-disabled="true"]):hover svg { transform: translateX(var(--cb-space-xs)); }

.cb-stage :is(a, button, input):focus-visible {
  outline: 2px solid var(--cbc-focus); outline-offset: 2px; border-radius: var(--cb-radius-md);
}

/* WHAT THE GATE LEAVES BEHIND: the tool's own screen, in the same host. The
   gate is a VIEW and not a layer, so there is nothing left over to dismiss. */
.cb-shelf-through {
  display: grid; justify-items: center; gap: var(--cb-space-2xl);
  padding: var(--cb-space-9xl) var(--cb-space-4xl); text-align: center;
}
.cb-shelf-through p { margin: 0; max-inline-size: var(--cb-width-sm); color: var(--cbc-ink-muted); }

/* ═══ NARROW ═══════════════════════════════════════════════════════════════
   A media query rather than a container one: the console's own breakpoints are
   media queries (console.css narrows the rail at the same width), and a second
   mechanism for the same question would be two answers to keep in step. The
   literal mirrors --cb-width-2xl, which CSS cannot read inside a query prelude:
   that is the exemption check:tokens carries for exactly this. */
@media (max-width: 1024px) { /* --cb-width-2xl */
  .cb-shelf-gate { padding-block: var(--cb-space-6xl) var(--cb-space-7xl); gap: var(--cb-space-4xl); --cb-shelf-cap: var(--cb-type-2xl-size); }
  /* The two columns become one and the CODE GOES FIRST: at this width the
     feature list would push the thing somebody came to use below the fold. */
  .cb-shelf-foot { grid-template-columns: minmax(0, 1fr); gap: var(--cb-space-5xl); }
  .cb-shelf-ask { order: -1; }
  .cb-shelf-slots { gap: var(--cb-space-md); }
  .cb-shelf-slot { block-size: var(--cb-space-6xl); font-size: var(--cb-type-xl-size); }
  /* The column is full width when the foot stacks, so a line has room and the
     rows can come in a step. Still four at a time. */
  .cb-shelf-list { --cb-shelf-row: var(--cb-space-5xl); }
}

/* ═══ REDUCED MOTION ═══════════════════════════════════════════════════════
   Everything that arrives is simply there, and the haze goes with it. What
   stays is the hover and the digit landing in its slot: a colour answering a
   pointer is not motion, and a code typed into a box that never answers is
   worse than the movement ever was. */
/* ═══ REDUCED MOTION ═══════════════════════════════════════════════════════
   THE REEL DOES NOT JUST STOP, IT OPENS. Freezing it would leave four of ten
   lines on screen and six unreachable, which is a motion preference costing
   somebody the content. So the mask, the fixed height and the clipping all come
   off and the full ten stand as an ordinary list. Motion off means show
   everything, not show less. */
@media (prefers-reduced-motion: reduce) {
  .cb-shelf-reel {
    block-size: auto; overflow: visible;
    -webkit-mask-image: none; mask-image: none;
  }
  .cb-shelf-track { animation: none; transform: none; }
  /* MORE SPECIFIC THAN THE ROW RULE, WHICH IS WHY THIS IS NOT `.cb-shelf-echo`.
     `.cb-shelf-list li` sets `display: grid` at (0,1,1) and a bare class is
     (0,1,0), so the plain selector lost and the opened-out reel showed fourteen
     lines: all ten, then the first four again. Under the one setting whose
     whole point is that nothing is hidden from you, it was quietly repeating
     itself. Found by counting the rows, not by reading the rule. */
  .cb-shelf-list li.cb-shelf-echo { display: none; }
  .cb-shelf-settle, .cb-shelf-crest, .cb-shelf-rule { animation: none; }
  .cb-shelf-line > span { animation: none; }
  .cb-shelf-slot[data-filled] > span { animation: none; }
  [data-state="no"] .cb-shelf-slots { animation: none; }
  [data-state="checking"] .cb-shelf-answer { animation: none; }
  .cb-shelf-go:not([aria-disabled="true"]):hover svg { transform: none; }
}
