/* ==========================================================================
   DINKOMART — SIGNATURE
   Business-specific components. Loads after refine.css, last of all.

   ---------------------------------------------------------------------------
   WHY THESE AND NOT A COMPONENT LIBRARY

   The brief was "more components, and they should make sense, not generic".
   A generic component library is exactly the thing that made the site read as
   templated in the first place: another card, another accordion, another
   tab strip. Nothing in a tab strip says *Mandya*.

   Everything here exists because THIS business has a fact that needs a shape:

     .cutoff    the order book closes at 9 PM. Whether you have made today's
                cut is the single most decision-relevant fact on the site, and
                it was previously a line of text in a ticker. It is now a live
                clock.
     .chain     produce is held at 4 degrees from field crate to van. That is
                the claim the whole business rests on, so it gets drawn as the
                five stages it actually passes through.
     .pincheck  crates are routed by pin code, and we deliver to two cities.
                So the question "do you reach me?" gets an answer, honestly —
                it validates the format and then points at a person, because
                the site genuinely does not hold a coverage list.
     .quotebar  machinery has no online price because freight, installation
                and the district subsidy change it. That deserves better than
                a greyed-out Add button.
     .sk        a grid that renders from JS pops in. Skeletons hold the space.

   None of these invent data. The cut-off comes from the 9 PM the business
   already runs on, the threshold from DINKOMART.config, the chain stages from
   the About page's own description. Per the project's standing rule there are
   still no ratings, no review counts and no performance metrics anywhere.

   RULES OBSERVED
   * Semantic tokens only — never --bone-*, --loam-*, --forest-*, which do not
     flip with the theme.
   * Nothing is hidden waiting for JS. Every component below is authored with
     its content visible and is only ENHANCED by script; if the script never
     runs, the markup still reads.
   * Loads last, so it wins on source order rather than on !important.
   ========================================================================== */

/* ==========================================================================
   1. THE CUT-OFF CLOCK

   "Order before 9 PM and it is cut the next morning." Everything about how
   this business works follows from that sentence, so it gets a live readout
   rather than a line in a marquee.

   The dial is a conic-gradient ring showing how much of the ordering day is
   left. No SVG, no canvas, one element.
   ========================================================================== */
.cutoff {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  padding: var(--s-4) var(--s-5);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  background: var(--surface);
  box-shadow: var(--shadow-xs);
}

.cutoff-dial {
  position: relative;
  flex: none;
  width: 46px; height: 46px;
  border-radius: 50%;
  /* --cut-pct is written by signature.js, 0 to 100 */
  background: conic-gradient(var(--brand) calc(var(--cut-pct, 0) * 1%), var(--surface-sunk) 0);
  display: grid;
  place-items: center;
  transition: background var(--dur-4) var(--ease-out);
}
.cutoff-dial::after {
  content: '';
  position: absolute;
  inset: 5px;
  border-radius: 50%;
  background: var(--surface);
}
.cutoff-dial .ico {
  position: relative;
  z-index: 1;
  width: 19px; height: 19px;
  color: var(--brand-ink);
}

.cutoff-txt { min-width: 0; }
.cutoff-txt b {
  display: block;
  font-family: var(--font-display);
  font-size: var(--t-lg);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
  color: var(--fg);
}
.cutoff-txt span {
  display: block;
  margin-top: 2px;
  font-size: var(--t-sm);
  color: var(--fg-subtle);
  line-height: 1.4;
}

/* past nine: the book is shut, and it says so rather than counting down to
   nothing */
.cutoff.is-closed .cutoff-dial { background: var(--surface-sunk); }
.cutoff.is-closed .cutoff-dial .ico { color: var(--accent-ink); }
.cutoff.is-closed .cutoff-txt b { color: var(--accent-ink); }

/* under an hour: worth hurrying */
.cutoff.is-soon .cutoff-dial { background: conic-gradient(var(--accent) calc(var(--cut-pct, 0) * 1%), var(--surface-sunk) 0); }
.cutoff.is-soon .cutoff-dial .ico { color: var(--accent-ink); }

.cutoff-slim {
  padding: var(--s-3) var(--s-4);
  gap: var(--s-3);
}
.cutoff-slim .cutoff-dial { width: 34px; height: 34px; }
.cutoff-slim .cutoff-dial::after { inset: 4px; }
.cutoff-slim .cutoff-dial .ico { width: 15px; height: 15px; }
.cutoff-slim .cutoff-txt b { font-size: var(--t-md); }
.cutoff-slim .cutoff-txt span { font-size: var(--t-xs); }

/* ==========================================================================
   2. THE COLD CHAIN TRACE

   Five stages, one line, and the cold step marked. Drawn with a flex row and
   a single ::before rule rather than SVG, so it reflows instead of needing
   re-measurement — the parent build's flow diagram needed a ResizeObserver
   and JS geometry for the same job.
   ========================================================================== */
.chain {
  position: relative;
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--s-2);
  padding: var(--s-5) 0 var(--s-2);
}
/* the rule the stages sit on */
.chain::before {
  content: '';
  position: absolute;
  left: 9%; right: 9%;
  top: calc(var(--s-5) + 15px);
  height: 2px;
  background: var(--border);
  z-index: 0;
}
/* the traced portion, drawn on reveal */
.chain::after {
  content: '';
  position: absolute;
  left: 9%; right: 9%;
  top: calc(var(--s-5) + 15px);
  height: 2px;
  background: linear-gradient(90deg, var(--brand), var(--chill) 62%, var(--brand));
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform var(--dur-4) var(--ease-out);
  z-index: 1;
}
.chain.is-in::after { transform: scaleX(1); }

.chain-stage {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
  text-align: center;
}
.chain-dot {
  width: 32px; height: 32px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 2px solid var(--border);
  background: var(--surface);
  color: var(--fg-subtle);
  transition: border-color var(--dur-2) var(--ease-out),
              color var(--dur-2) var(--ease-out),
              transform var(--dur-2) var(--ease-spring);
}
.chain-dot .ico { width: 15px; height: 15px; }
.chain-stage b {
  font-family: var(--font-mono);
  font-size: var(--t-2xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-muted);
  line-height: 1.25;
}
.chain-stage span {
  font-size: var(--t-2xs);
  color: var(--fg-faint);
  line-height: 1.3;
}
.chain.is-in .chain-dot { border-color: var(--brand); color: var(--brand-ink); }
/* the four-degree step is the one that matters, so it gets the cold colour */
.chain.is-in .chain-stage.is-cold .chain-dot {
  border-color: var(--chill);
  color: var(--chill);
  background: var(--chill-soft);
  transform: scale(1.12);
}
.chain-stage.is-cold b { color: var(--chill); }

@media (max-width: 620px) {
  .chain { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 2px; }
  .chain-stage span { display: none; }
  .chain-stage b { font-size: var(--t-2xs); letter-spacing: 0.04em; }
  .chain-dot { width: 27px; height: 27px; }
  .chain::before, .chain::after { top: calc(var(--s-5) + 12px); }
}

/* on a dark band */
.on-band .chain-dot { background: var(--band-bg-2); border-color: var(--band-border); color: var(--band-fg-muted); }
.on-band .chain-stage b { color: var(--band-fg-muted); }
.on-band .chain-stage span { color: var(--band-fg-faint); }
.on-band .chain::before { background: var(--band-border); }

/* ==========================================================================
   3. THE PIN CODE CHECK

   Honest by construction. It checks the FORMAT with the same rule the real
   checkout uses, then tells the visitor what we actually know — that we pack
   in Mandya and deliver to Bengaluru and Mysuru — and offers a person for
   anything beyond that. It must never claim to have looked a pin code up in
   a coverage table, because there is no coverage table.
   ========================================================================== */
.pincheck {
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  background: var(--surface);
  padding: var(--s-4) var(--s-5);
}
.pincheck-head {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  font-family: var(--font-mono);
  font-size: var(--t-2xs);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin-bottom: var(--s-3);
}
.pincheck-head .ico { width: 15px; height: 15px; color: var(--brand); }

.pincheck-row { display: flex; flex-wrap: wrap; gap: var(--s-2); }

/* `width: 100%` IS LOAD-BEARING, NOT DECORATION.

   A bare <input> has an intrinsic width of about twenty characters — 240px
   in this mono face — and that preferred width feeds into the intrinsic
   sizing of every ancestor, even with `flex: 1` and `min-width: 0` set.
   On the product page that pushed .pdp-info to 380px inside a 354px column
   and gave the whole page 8px of horizontal scroll on a 390px phone.

   Measured: shrinking this input to 40px took scrollWidth from 398 to 390.
   Giving it a percentage width makes its preferred size come from the
   container instead of from the character count. */
.pincheck-row input {
  flex: 1 1 140px;
  width: 100%;
  min-width: 0;
  min-height: 44px;
  padding: 0 var(--s-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-ctl);
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-mono);
  font-size: var(--t-base);
  letter-spacing: 0.08em;
  transition: border-color var(--dur-1) var(--ease-out), box-shadow var(--dur-1) var(--ease-out);
}
.pincheck-row input:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-soft);
}
.pincheck-row input::placeholder { color: var(--fg-faint); letter-spacing: normal; }

.pincheck-out {
  margin: var(--s-3) 0 0;
  font-size: var(--t-sm);
  line-height: 1.5;
  color: var(--fg-muted);
}
.pincheck-out:empty { display: none; }
.pincheck-out.is-bad { color: var(--danger); }
.pincheck-out.is-good { color: var(--fg-muted); }
.pincheck-out b { color: var(--fg); }

/* ==========================================================================
   4. THE QUOTE BAR — machinery's answer to "Add to cart"

   A machine has no online price because freight, installation and the
   district subsidy all move it. That is a reason, not an omission, so it is
   stated where the buy button would be instead of leaving a dead control.
   ========================================================================== */
.quotebar {
  display: grid;
  gap: var(--s-4);
  padding: var(--s-5);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--r-card);
  background: var(--accent-soft);
}
.quotebar-why {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}
.quotebar-why span {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  padding: 5px 10px;
  border-radius: var(--r-pill);
  background: var(--surface);
  border: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: var(--t-2xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.quotebar-why .ico { width: 13px; height: 13px; color: var(--accent-ink); }
.quotebar p { font-size: var(--t-sm); color: var(--fg-muted); line-height: 1.55; margin: 0; }

/* ==========================================================================
   5. (WAS: A STICKY BUY BAR — REMOVED)

   A sticky add-to-cart bar was added here before checking, and the build
   already ships one: `.buy-bar` is in product.php, styled in pages.css and
   filled by catalog.js. The result was two sticky bars stacked on top of
   each other on every phone.

   Left as a note rather than deleted silently, because "does this already
   exist?" is the question that would have prevented it.
   ========================================================================== */

/* ==========================================================================
   6. SKELETONS

   A grid that renders from JS pops in and shifts the page. These hold the
   space at the right size until the real cards land.
   ========================================================================== */
.sk {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-card);
  background: var(--surface-sunk);
}
.sk::after {
  content: '';
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg,
    transparent,
    color-mix(in srgb, var(--fg) 6%, transparent),
    transparent);
  animation: sk-sweep 1.4s ease-in-out infinite;
}
@keyframes sk-sweep { to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { .sk::after { animation: none; } }

.sk-card { aspect-ratio: 3 / 4; border: 1px solid var(--border); }

/* ==========================================================================
   7. CHECKOUT PROGRESS

   Three states, and it is honest about the third: the order is confirmed on
   WhatsApp, not by a payment gateway that does not exist.
   ========================================================================== */
.pflow {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-4) 0;
  overflow-x: auto;
  scrollbar-width: none;
}
.pflow::-webkit-scrollbar { display: none; }
.pflow-step {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--t-2xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-faint);
  white-space: nowrap;
}
.pflow-step i {
  display: grid;
  place-items: center;
  width: 24px; height: 24px;
  border-radius: 50%;
  border: 1.5px solid var(--border-strong);
  font-style: normal;
  font-size: 11px;
  color: var(--fg-faint);
  background: var(--surface);
  transition: background-color var(--dur-2) var(--ease-out),
              border-color var(--dur-2) var(--ease-out),
              color var(--dur-2) var(--ease-out);
}
.pflow-step.is-done i { background: var(--brand); border-color: var(--brand); color: var(--brand-fg); }
.pflow-step.is-here i { border-color: var(--brand); color: var(--brand-ink); box-shadow: 0 0 0 3px var(--brand-soft); }
.pflow-step.is-done, .pflow-step.is-here { color: var(--fg); }
.pflow-sep { flex: 1 1 14px; min-width: 14px; height: 1px; background: var(--border-strong); }

/* ==========================================================================
   8. FACT STRIP — a row of small, checkable facts.

   Used where a page makes a promise: the product page, the cart, the farm
   box. Every entry is something the business can be held to.
   ========================================================================== */
.facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
  gap: var(--s-3);
}
.fact-chip {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--border);
  border-radius: var(--r-ctl);
  background: var(--surface);
  transition: border-color var(--dur-1) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.fact-chip:hover { border-color: var(--border-strong); transform: translateY(-1px); }
.fact-chip .ico { flex: none; width: 17px; height: 17px; margin-top: 1px; color: var(--brand); }
.fact-chip b { display: block; font-size: var(--t-sm); line-height: 1.25; }
.fact-chip span { display: block; margin-top: 2px; font-size: var(--t-xs); color: var(--fg-subtle); line-height: 1.4; }
.fact-chip.is-cold .ico { color: var(--chill); }
.fact-chip.is-warm .ico { color: var(--accent); }

/* ==========================================================================
   9. SECTION INTRO — a small reusable lockup for the thinner pages.

   shop, product, wishlist and contact each carried one section and stopped.
   This gives them a way to open a second one without inventing a new layout
   each time.
   ========================================================================== */
.mini-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  padding-bottom: var(--s-3);
  margin-bottom: var(--s-5);
  border-bottom: 1px solid var(--border);
}
.mini-head h2 {
  font-size: var(--t-xl);
  letter-spacing: -0.02em;
  margin: 0;
}
.mini-head p {
  margin: var(--s-2) 0 0;
  font-size: var(--t-sm);
  color: var(--fg-subtle);
  max-width: 56ch;
}

/* ==========================================================================
   10. THE COUNT-UP

   Numbers that arrive rather than appear. Applied to the figures that are
   already on the page — nothing new is claimed.
   ========================================================================== */
[data-tally] { font-variant-numeric: tabular-nums; }

/* ==========================================================================
   10b. THE INVISIBLE BUTTONS IN THE HERO

   The hero carousel stacks three panels and fades between them with opacity
   alone. `opacity: 0` hides a thing from the eye and from nothing else: the
   two inactive panels kept `visibility: visible`, so their four links stayed
   in the tab order and in hit testing.

   Measured: focusing the first link inside an inactive panel succeeded, and
   it was a "Browse machinery" button nobody could see. A keyboard visitor
   tabbing through the hero passed through four invisible controls.

   visibility:hidden removes an element from the tab order AND from hit
   testing while still allowing the opacity transition, as long as the
   visibility change is delayed until the fade has finished. On the way in it
   must switch immediately, hence the zero delay on .is-on.

   SAFE WITHOUT JS: the first panel carries `is-on` in the authored markup,
   so with scripting off exactly one panel is visible — which is the correct
   fallback, not a blank hero.
   ========================================================================== */
.hero-panel {
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--dur-3) var(--ease-out),
              transform var(--dur-3) var(--ease-out),
              visibility 0s linear var(--dur-3);
}
.hero-panel.is-on {
  visibility: visible;
  pointer-events: auto;
  transition-delay: 0s;
}
@media (prefers-reduced-motion: reduce) {
  .hero-panel { transition: none; }
}

/* ==========================================================================
   10d. THE OFF-CANVAS PANELS WERE WIDENING THE DOCUMENT

   Every page carries drawers parked off-canvas to the right — the menu, the
   cart, the assistant. They are `position: fixed` and translated out of
   view, and a fixed element is not clipped by `overflow` on an ancestor, so
   they kept extending the document's scrollable width.

   Measured on a 390px viewport: `scrollWidth` 400, ten pixels of horizontal
   scroll, with the nav drawer sitting at 398..796. Present in the live build
   too. It is easy to miss because no VISIBLE element is out of place — the
   audit found the overflow and could not name an offender, which is the
   signature of this particular bug.

   `overflow-x: clip` on the root is the fix, and specifically `clip` rather
   than `hidden`: `hidden` would make the root a scroll container and break
   every `position: sticky` on the site — the header, the filter rail, the
   cart summary. `clip` clips without creating one.

   Guarded, because Safari only took `clip` in 16. Older engines keep the ten
   pixels rather than losing their sticky headers.

   BOTH rules below are required, and the second is the subtle one.

   The build sets `body { overflow-x: hidden }`. While `html` is `visible`
   that value is PROPAGATED to the viewport and body itself computes to
   visible. The moment `html` stops being visible the propagation stops, body
   keeps its own hidden, that forces its overflow-y to `auto`, and **body
   becomes the scrolling box**. Every `position: sticky` on the site then
   sticks to body's scrollport instead of the viewport.

   Measured: with only the html rule, the sticky header scrolled away —
   its top went to -563px where the original holds it at 0. Releasing body's
   overflow-x puts the scrolling back on the viewport and the header sticks
   again.
   ========================================================================== */
@supports (overflow-x: clip) {
  html { overflow-x: clip; }
  /* must accompany the rule above — see the note */
  body { overflow-x: visible; }
}

/* ==========================================================================
   10c. THE ASSISTANT'S TEXTAREA WAS WIDENING THE PAGE

   Exactly the bug the pin-code input had, in the inherited build: a bare
   <textarea> carries an intrinsic width from its default `cols`, and that
   preferred width feeds the intrinsic sizing of every ancestor even though
   `.rm-input` already sets `flex: 1` and `min-width: 0`.

   Measured on a 390px viewport: the textarea reported 316px and the document
   scrolled to 400px — ten pixels of horizontal scroll on every page carrying
   the assistant. Present in the live build too, and easy to miss because the
   panel it lives in is closed.

   A percentage width makes its preferred size come from the container
   instead of from a character count.
   ========================================================================== */
.rm-input {
  width: 100%;
  flex: 1 1 auto;
  min-width: 0;
}
.rm-panel, .rm-form { max-width: 100%; min-width: 0; }

/* ==========================================================================
   11. THE CRATE SPEC PLATE

   WHAT WAS THERE, AND WHY IT DID NOT WORK.

   Four numbers in a white box with three vertical rules through it. Every
   fact on it was true, and none of it was doing any work. "4 °C" and "Any
   week" sat at identical weight with nothing to say which one mattered, and
   the whole strip read as a SPECIFICATION TABLE on a page whose entire
   argument is that the crate is deliberately not specified in advance. The
   page says "not a fixed list" in the lede and then prints a fixed list of
   four numbers directly underneath it.

   Each cell now demonstrates its number instead of printing it:

     RANGE   five solid ticks and nine hollow ones — which is the actual
             promise. Five every week whatever happens; up to fourteen when
             the plots allow. A bare "5–14" hides that asymmetry.
     TEMP    a marker that travels down a real 0–30 scale from ambient to
             four. The cold chain is the claim the business rests on, so it
             gets the one animation on the plate that means something.
     CLOSE   live. It reads the same `cutoffState()` the order clock does, so
             the two can never disagree, and the bar depletes with the
             ordering day.
     SKIP    a week of seven dots with one struck out.

   IT IS A PLATE, NOT FOUR CARDS. The crate carries a printed weight slip —
   "weighed at the collection centre, printed on the label", further down this
   same page — and this is that slip. One surface, hairline gutters, mono
   keys. Four floating cards would have been the same generic answer in a
   different arrangement.

   The gutters are `gap: 1px` over a `--hairline` ground rather than borders
   on the cells. That reflows correctly at every breakpoint on its own; a
   divider drawn per cell needs a different first-in-row rule at each column
   count and gets one of them wrong.

   NOTHING HERE IS HIDDEN WAITING FOR JS. Every value and every note is in
   the markup at full opacity. The decorative bars start short and grow —
   short, not absent — so a dead script leaves a quieter plate, not an empty
   one. The reveal comes from `data-rise`, which motion.js already marks
   `.is-in` and already force-marks on a watchdog.
   ========================================================================== */
.spec-plate {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1px;
  margin: 0;
  padding: 0;
  list-style: none;
  background: var(--hairline);
  border: 1px solid var(--hairline);
  border-radius: var(--r-panel);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}
@media (min-width: 560px)  { .spec-plate { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .spec-plate { grid-template-columns: repeat(4, 1fr); } }

/* SCOPED TO THE PLATE, and it has to be. `.spec` is not a free name — the
   product page has carried `<table class="spec">` since components.css:502,
   and this file loads after it, so a bare `.spec` here won a table it was
   never written for. It put `display: grid` on a `<table>`, which blockified
   the `<tbody>` and left the rows rebuilding themselves through anonymous
   table fallback: measured, the label column came out 39.3% against the 44%
   its own rule asks for, and 72px of the table's width belonged to no column
   at all. It also handed every product page a `backdrop-filter` compositor
   layer, against decision 14, and a hover wash on a reference table.

   All four plate cells are `<li>` children of `<ul class="spec-plate">`, so
   scoping costs nothing here and releases the table. The compound selectors
   further down (`.spec:hover .spec-ticks`, `.spec.is-closed .spec-v`) were
   never part of the collision — each needs a Farm-Box-only class or child to
   match — but the two bare ones did all the damage on their own. */
.spec-plate .spec {
  position: relative;
  display: grid;
  align-content: start;
  gap: var(--s-3);
  padding: var(--s-6) var(--s-5);
  /* `.fb-hero` centres its column, and the plate sits inside it. A spec label
     reads left — centred keys over centred figures is the marketing stat row
     this component exists to stop being. */
  text-align: left;
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--glass-blur));
  backdrop-filter: blur(var(--glass-blur));
  transition: background var(--dur-2) var(--ease-out);
}
.spec-plate .spec:hover { background: var(--glass-2); }

.spec-k {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .2em;
  text-transform: uppercase;
  /* `--fg-faint` is documented as 4.7:1 ON BONE, and these keys are not on
     bone — they are on `--glass`, which composites darker over the page
     field. Measured 3.78:1 there. `--fg-muted` is the step that clears it. */
  color: var(--fg-muted);
  line-height: 1;
}

.spec-v {
  display: flex;
  align-items: baseline;
  gap: 1px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--t-3xl);
  line-height: 1;
  letter-spacing: var(--tr-tight);
  color: var(--fg);
  font-variant-numeric: tabular-nums;
}
.spec-v small {
  font-size: var(--t-md);
  font-weight: 600;
  letter-spacing: var(--tr-flat);
  color: var(--fg-muted);
}
/* A word is not a number and must not be set like one — "Any week" at the
   figure size overflows a quarter-width cell on every screen under 1200px. */
.spec-v.is-word { font-size: var(--t-2xl); }

.spec-note {
  margin: 0;
  font-size: var(--t-xs);
  line-height: 1.5;
  color: var(--fg-muted);
  text-wrap: pretty;
}

/* ---------------------------------------------------------- viz: the range */
.spec-ticks {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 24px;
  margin-top: 2px;
}
.spec-ticks i {
  --h: .5;
  flex: 1 1 0;
  height: 100%;
  border-radius: 2px;
  /* NOT `--brand-soft`. That token is a background TINT and so it inverts
     with the theme — #DDEEE3 in light, #103524 in dark — which is right for a
     panel and wrong for a mark: on the dark cell the nine hopeful ticks
     disappeared almost entirely. A translucent `--brand` flips the right way,
     because `--brand` is itself dark green in light and bright green in dark,
     so the mark stays visible against whichever ground it lands on. */
  background: color-mix(in srgb, var(--brand) 32%, transparent);
  transform: scaleY(.1);
  transform-origin: 50% 100%;
  transition: transform var(--dur-3) var(--ease-spring);
}
/* the five that are promised, not the nine that are hoped for */
.spec-ticks i.is-core { --h: 1; background: var(--brand); }
.is-in .spec-ticks i { transform: scaleY(var(--h)); }

.spec-ticks i:nth-child(1)  { transition-delay: 0ms }
.spec-ticks i:nth-child(2)  { transition-delay: 34ms }
.spec-ticks i:nth-child(3)  { transition-delay: 68ms }
.spec-ticks i:nth-child(4)  { transition-delay: 102ms }
.spec-ticks i:nth-child(5)  { transition-delay: 136ms }
.spec-ticks i:nth-child(6)  { transition-delay: 170ms }
.spec-ticks i:nth-child(7)  { transition-delay: 204ms }
.spec-ticks i:nth-child(8)  { transition-delay: 238ms }
.spec-ticks i:nth-child(9)  { transition-delay: 272ms }
.spec-ticks i:nth-child(10) { transition-delay: 306ms }
.spec-ticks i:nth-child(11) { transition-delay: 340ms }
.spec-ticks i:nth-child(12) { transition-delay: 374ms }
.spec-ticks i:nth-child(13) { transition-delay: 408ms }
.spec-ticks i:nth-child(14) { transition-delay: 442ms }

/* hovering the cell tops the hopeful nine up, which is the point being made */
.spec:hover .spec-ticks i { --h: .78; }
.spec:hover .spec-ticks i.is-core { --h: 1; }

/* ---------------------------------------------------- viz: the temperature */
.spec-therm {
  position: relative;
  display: block;
  height: 24px;
  margin-top: 2px;
}
.spec-therm::before {
  content: '';
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: 4px;
  margin-top: -2px;
  border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--chill) 0%, var(--border) 52%, var(--accent) 100%);
  opacity: .38;
}
.spec-merc {
  position: absolute;
  top: 50%;
  left: 93.3%;                    /* 28 °C — a crate in the sun at the gate */
  width: 14px;
  height: 14px;
  margin: -7px 0 0 -7px;
  border-radius: 50%;
  background: var(--surface);
  border: 3px solid var(--chill);
  box-shadow: var(--shadow-sm);
  transition: left 1200ms var(--ease-spring);
}
.is-in .spec-merc { left: 13.3%; }   /* 4 °C, and it stays there */

.spec-scale {
  display: flex;
  justify-content: space-between;
  font-family: var(--font-mono);
  font-size: var(--t-2xs);
  letter-spacing: .12em;
  color: var(--fg-muted);      /* same ground, same reason as `.spec-k` */
  margin-top: -4px;
}

/* --------------------------------------------------------- viz: the close */
.spec-bar {
  position: relative;
  display: block;
  height: 6px;
  margin-top: 9px;
  border-radius: var(--r-pill);
  background: var(--surface-sunk);
  overflow: hidden;
}
.spec-bar::after {
  content: '';
  position: absolute;
  top: 0; bottom: 0; left: 0;
  width: calc(var(--cut-pct, 100) * 1%);
  border-radius: inherit;
  background: linear-gradient(90deg, var(--brand), var(--brand-ink));
  transition: width var(--dur-3) var(--ease-out);
}
/* the last hour is the one worth noticing, so it changes colour rather than
   just getting shorter */
.spec.is-soon .spec-bar::after {
  background: linear-gradient(90deg, var(--accent), var(--accent-hover));
}
.spec.is-closed .spec-bar::after { width: 0; }
.spec.is-closed .spec-v { color: var(--fg-faint); }

/* ---------------------------------------------------------- viz: the week */
.spec-week {
  display: flex;
  align-items: center;
  gap: 6px;
  height: 24px;
  margin-top: 2px;
}
.spec-week i {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--brand) 38%, transparent);  /* see the tick note */
  border: 1.5px solid transparent;
  transform: scale(.4);
  opacity: .35;
  transition: transform var(--dur-2) var(--ease-spring),
              opacity var(--dur-2) var(--ease-out),
              border-color var(--dur-2) var(--ease-out);
}
.is-in .spec-week i { transform: scale(1); opacity: 1; }
.spec-week i.is-skip { background: transparent; border-color: var(--fg-faint); }
.is-in .spec-week i.is-skip { transform: scale(.84); }
/* lifting the skipped day out on hover is the whole idea, drawn */
.spec:hover .spec-week i.is-skip {
  transform: scale(.84) translateY(-4px);
  border-color: var(--accent);
}

.spec-week i:nth-child(1) { transition-delay: 0ms }
.spec-week i:nth-child(2) { transition-delay: 45ms }
.spec-week i:nth-child(3) { transition-delay: 90ms }
.spec-week i:nth-child(4) { transition-delay: 135ms }
.spec-week i:nth-child(5) { transition-delay: 180ms }
.spec-week i:nth-child(6) { transition-delay: 225ms }
.spec-week i:nth-child(7) { transition-delay: 270ms }

/* The `--dur-*` tokens already collapse to 1ms under reduced motion, but the
   mercury's 1200ms is hand-written because no token is that long — so the
   whole component is stated once here rather than relying on that. */
@media (prefers-reduced-motion: reduce) {
  .spec-ticks i,
  .spec-merc,
  .spec-week i,
  .spec-bar::after { transition: none !important; }
  .spec:hover .spec-week i.is-skip { transform: scale(.84); }
}

/* ==========================================================================
   12. THE YEAR BAND

   The season table was six equal boxes with `is-now` HARD-CODED onto the
   Jul/Aug cell, and the words "· now" typed into its label. It was correct on
   the day it was written and silently wrong from September — a page whose
   whole subject is what is good *right now* telling you, in November, that it
   is August. That is worse than showing nothing, because it is confident.

   So the window is computed from the date. And since it is being computed
   anyway it may as well be shown properly: a twelve-month rail above the
   grid, the current two-month window lit, the current month solid, and a
   marker at today's real position in the year. That is a calendar. Six boxes
   in a row was a table of six boxes.

   The rail is decoration in the strict sense — every month it labels is also
   named in the cell underneath it — so it is `aria-hidden` and the cells go
   on carrying the meaning. The pip on the live cell is what makes "now" read
   as now rather than as a highlight somebody left on by accident.
   ========================================================================== */
.year { display: grid; gap: var(--s-5); }

.year-rail {
  position: relative;
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 2px;
  height: 26px;
  margin-bottom: var(--s-1);
}
.year-seg {
  display: grid;
  place-items: center;
  border-radius: 3px;
  background: var(--surface-sunk);
  font-family: var(--font-mono);
  font-size: var(--t-2xs);
  letter-spacing: .08em;
  text-transform: uppercase;
  /* on `--surface-sunk`, which is a step below bone — `--fg-faint` measured
     4.1:1 here, same trap as `.spec-k` above */
  color: var(--fg-muted);
  transition: background var(--dur-2) var(--ease-out),
              color var(--dur-2) var(--ease-out);
}
.year-seg.is-window { background: var(--brand-soft); color: var(--brand-ink); }
.year-seg.is-now    { background: var(--brand); color: var(--brand-fg); font-weight: 700; }

/* TWELVE MONTHS AT 0.08em OF TRACKING DO NOT FIT A 320px SCREEN, AND THE RAIL
   TOOK THE SEASON GRID WITH IT.

   `repeat(12, 1fr)` is `minmax(auto, 1fr)`, so no track may go below its own
   min-content: "Jan" in mono at this tracking is 22.45px, and 12 of those plus
   eleven 2px gaps is 291.4 against a 284px column. `.year` is a grid with one
   `auto` track, so that 291.4 became the TRACK width — which is why `.season`
   underneath, which fits comfortably, was dragged 7px past the page gutter
   too. One item's minimum sizing a shared track is the part worth remembering.

   The tracking is what gives. It is worth 2.6px a segment and 31px across the
   rail, four times the shortfall, and it costs nothing a reader can name — the
   months stay three letters and nothing is clipped. Below 328px only; above
   that the rail already fits. */
@media (max-width: 340px) {
  .year-seg { letter-spacing: 0; }
}

/* Today, to the day, in the one colour this site reserves for money and
   deadlines. It is the only thing on the rail that is not a month. */
.year-now {
  position: absolute;
  top: -5px;
  bottom: -5px;
  left: calc(var(--year-pct, 50) * 1%);
  width: 2px;
  margin-left: -1px;
  border-radius: 2px;
  background: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
  transition: left var(--dur-3) var(--ease-out);
}
.year-now::after {
  content: '';
  position: absolute;
  top: -4px;
  left: 50%;
  width: 6px;
  height: 6px;
  margin-left: -3px;
  border-radius: 50%;
  background: var(--accent);
}

.season-cell { position: relative; transition: background var(--dur-2) var(--ease-out); }
.season-cell:hover { background: var(--surface-2); }
.season-cell.is-now:hover { background: var(--brand-soft); }

.season-cell.is-now .season-m { display: flex; align-items: center; gap: 6px; }
.season-cell.is-now .season-m::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--brand);
  animation: seasonPip 2.6s var(--ease-out) infinite;
}
@keyframes seasonPip {
  0%   { box-shadow: 0 0 0 0   color-mix(in srgb, var(--brand) 45%, transparent); }
  70%  { box-shadow: 0 0 0 7px color-mix(in srgb, var(--brand)  0%, transparent); }
  100% { box-shadow: 0 0 0 0   color-mix(in srgb, var(--brand)  0%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .season-cell.is-now .season-m::before { animation: none; }
  .year-now, .year-seg { transition: none; }
}

/* ==========================================================================
   13. THE CRATE MANIFEST

   WHAT WAS THERE. Ten items in `repeat(auto-fill, minmax(…136px, 1fr))` over a
   `--border` ground showing through a 1px gap. Ten items in three columns is
   three rows of three and one left over, so the bottom right of the crate was
   a BLANK TWO-CELL HOLE filled with border green. It is the first thing the
   eye lands on, and it made the component look broken rather than sparse.

   A grid whose cell count is a product decision will hole itself the moment
   the decision changes. So this is not a grid any more: groups of ruled rows,
   which end wherever they end. A group with five items now leaves whitespace
   instead of a coloured rectangle.

   WHAT THE GROUPING IS FOR. It is not decoration — it is the one honest
   summary available. The quantities are mixed units (bunches, pieces, grams,
   kilos), so a proportional weight bar would have to invent numbers, and this
   project does not do that. Kind is derivable from the items themselves and
   answers the real question: two leaves, six vegetables, two fruit. That is
   the shape of the crate.

   EVERY ITEM IS A LINK, BECAUSE THE SECTION ALREADY PROMISES ONE. The heading
   sits next to "Buy any of it loose" and then gave you ten pieces of dead
   text. Nine of the ten resolve to real catalogue ids.

   THE TENTH DOES NOT. Amaranth is in the crate and is not in the catalogue,
   so it is marked "box only" rather than linked to a 404 or quietly dropped.
   The promise on the button was always slightly wider than the truth; this is
   the cheapest place to say so.

   The top edge was 8px of repeating `--accent` at .6 — packing tape, on a
   panel with nothing else amber in it, reading as a stray. A packing slip is
   torn from a pad, so it is a perforation now: smaller, in `--border`, and
   quiet enough to be an edge rather than an ornament.
   ========================================================================== */
.crate::before {
  height: 6px;
  background: repeating-linear-gradient(90deg,
    var(--border) 0 7px, transparent 7px 14px);
  opacity: 1;
}

.manifest { display: grid; gap: var(--s-5); }
.mf-group { display: grid; gap: var(--s-1); }

.mf-head {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  font-family: var(--font-mono);
  font-size: var(--t-2xs);
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
/* the rule runs out to the edge, so the label reads as a heading on a form
   rather than as a floating word */
.mf-head::after {
  content: '';
  flex: 1 1 auto;
  height: 1px;
  background: var(--hairline);
}
.mf-count { color: var(--fg); font-weight: 700; font-variant-numeric: tabular-nums; }

.mf-items { display: grid; grid-template-columns: 1fr; }
@media (min-width: 520px) {
  .mf-items { grid-template-columns: repeat(2, 1fr); column-gap: var(--s-6); }
}

.mf-item {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-2);
  margin-inline: calc(var(--s-2) * -1);
  border-bottom: 1px solid var(--hairline);
  color: inherit;
  text-decoration: none;
  transition: background var(--dur-2) var(--ease-out);
}

.mf-glyph {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--brand) 12%, transparent);
  color: var(--brand-ink);
  transition: background var(--dur-2) var(--ease-out),
              color var(--dur-2) var(--ease-out),
              transform var(--dur-2) var(--ease-spring);
}
.mf-glyph .ico { width: 15px; height: 15px; }

.mf-name {
  display: block;
  font-family: var(--font-display);
  font-size: var(--t-sm);
  font-weight: 700;
  letter-spacing: var(--tr-snug);
  line-height: 1.2;
}
.mf-qty {
  display: block;
  margin-top: 2px;
  font-family: var(--font-mono);
  font-size: var(--t-2xs);
  letter-spacing: .05em;
  color: var(--fg-muted);
}

/* Resting at .38 rather than 0. A control that only exists on hover does not
   exist on a phone, and this is the affordance saying the row is a link. */
.mf-go {
  width: 15px;
  height: 15px;
  color: var(--fg-muted);
  opacity: .38;
  transform: translateX(-3px);
  transition: opacity var(--dur-2) var(--ease-out),
              transform var(--dur-2) var(--ease-spring),
              color var(--dur-2) var(--ease-out);
}

a.mf-item:hover { background: var(--rhythm); }
a.mf-item:hover .mf-glyph { background: var(--brand); color: var(--brand-fg); transform: scale(1.07); }
a.mf-item:hover .mf-go { opacity: 1; transform: translateX(0); color: var(--brand-ink); }
a.mf-item:focus-visible { background: var(--rhythm); }

/* the one item in the crate that is not in the catalogue */
.mf-tag {
  font-family: var(--font-mono);
  font-size: var(--t-2xs);
  letter-spacing: .1em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--fg-muted);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  padding: 3px 8px;
}
.mf-item.is-off .mf-glyph { background: var(--surface-sunk); color: var(--fg-muted); }

@media (pointer: coarse) {
  .mf-item { padding-block: var(--s-4); }   /* 44px row, not 38 */
  .mf-go { opacity: .6; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .mf-glyph, .mf-go { transition: none !important; }
  a.mf-item:hover .mf-glyph { transform: none; }
}

/* ==========================================================================
   14. THE SPILL — what is actually in Monday's crate

   The hero made the page's whole claim — "a crate of whatever is actually
   good this week" — and then showed you nothing at all. Eyebrow, headline,
   lede, two buttons, and a great deal of near-white. Every other hero on this
   site has an object in it; this one had a paragraph about an object.

   The strongest thing available is not a stock photograph of a crate. It is
   THIS WEEK'S ACTUAL PRODUCE, which the catalogue already has real
   photography for, and which the page lists in full two sections down. The
   headline says "whatever is actually good this week" and the evidence for
   that claim now sits directly underneath it instead of below the fold.

   Six of the ten items, not all ten. Brinjal and green chilli have no
   catalogue photograph, and the row stops being a glance and starts being a
   grid past six. The manifest in section 02 remains the complete list — this
   is the trailer, not the inventory.

   The tiles sit on a hairline rail with alternating 10px offsets, so the row
   reads as produce laid in something rather than as a toolbar. The rail is
   dropped below 720px, where the row wraps and a line through the middle of
   two stacked rows would be nonsense.
   ========================================================================== */
.spill {
  display: grid;
  justify-items: center;
  gap: var(--s-4);
  margin-top: var(--s-8);
}

.spill-k {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--t-2xs);
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--fg-muted);
}

.spill-row {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--s-3) var(--s-5);
  margin: 0;
  padding: 0 0 12px;
  list-style: none;
}
@media (min-width: 720px) {
  .spill-row::before {
    content: '';
    position: absolute;
    left: 4%;
    right: 4%;
    top: 60px;
    height: 1px;
    background: var(--hairline);
    z-index: 0;
  }
}

.spill-item { position: relative; z-index: 1; }
@media (min-width: 720px) {
  .spill-item:nth-child(even) { transform: translateY(10px); }
}

.spill-link {
  display: grid;
  justify-items: center;
  gap: var(--s-2);
  text-decoration: none;
  color: inherit;
}

.spill-shot {
  display: block;
  width: 84px;
  height: 84px;
  border-radius: 20px;
  overflow: hidden;
  background: var(--surface-2);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-sm);
  transition: transform var(--dur-2) var(--ease-spring),
              box-shadow var(--dur-2) var(--ease-out),
              border-color var(--dur-2) var(--ease-out);
}
.spill-shot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur-3) var(--ease-out);
}

.spill-name {
  font-family: var(--font-mono);
  font-size: var(--t-2xs);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--fg-muted);
  transition: color var(--dur-2) var(--ease-out);
}

.spill-link:hover .spill-shot,
.spill-link:focus-visible .spill-shot {
  transform: translateY(-5px);
  box-shadow: var(--shadow-md);
  border-color: var(--brand);
}
.spill-link:hover .spill-shot img { transform: scale(1.08); }
.spill-link:hover .spill-name,
.spill-link:focus-visible .spill-name { color: var(--brand-ink); }

@media (max-width: 519px) {
  .spill-shot { width: 68px; height: 68px; border-radius: 16px; }
  .spill-row { gap: var(--s-3) var(--s-4); }
}
@media (prefers-reduced-motion: reduce) {
  .spill-shot, .spill-shot img { transition: none !important; }
  .spill-link:hover .spill-shot { transform: none; }
  .spill-link:hover .spill-shot img { transform: none; }
}

/* ==========================================================================
   15. THE COLD CHAIN TIMELINE, AND THE PLAN CARDS

   ---- 15a. the timeline spine ------------------------------------------

   The spine was `linear-gradient(90deg, brand, chill 52%, accent)` at .32
   opacity. Two things wrong with that, and the second is the real one.

   It was too faint to read at .32. And — the part that matters — it was a
   SMOOTH BLEND with its cold point at an arbitrary 52%, while the actual
   steps put the cold at 50% and the door at 83.3%. So the one piece of
   colour on the page that could have carried the argument was carrying a
   decoration that merely resembled it. The section is titled "cut to your
   order" and its whole claim is eighteen hours at four degrees; the line
   should go cold exactly where the crate does.

   Hard stops now, on the sixths the grid actually uses: warm through steps
   1-3, chill from step 4 (down to four degrees) through step 5 (split by pin
   code), amber at step 6 (at your door). A reader can now point at where the
   cold starts, which is the entire point of drawing it.

   And a bracket over the cold stretch, labelled, because a colour that means
   something specific should say what it means rather than hoping the reader
   infers it from a legend that does not exist. It is `aria-hidden` — the two
   steps under it already say "down to four degrees" and "held there from
   here on" in words.

   ---- 15b. the plan badges ---------------------------------------------

   `.badge` is `display: inline-flex`, which is why nobody noticed: inside
   `.plan`, which is `display: grid`, a grid item stretches to its column
   unless told otherwise. So "PLACEHOLDER PRICE" rendered as a FULL-WIDTH
   GREY BAR across the bottom of two cards and "MOST ORDERED" as a cream one
   — the ugliest detail on the page, and invisible in the CSS because the rule
   that produced it says `inline-flex`.

   `justify-self: start` is the whole fix for that. "Most ordered" also moves
   to the top with `order: -1`, because it labels the entire card rather than
   the button above it, and the top is where you look first.

   The size chip was a 46px CIRCLE containing "9-10" at 13px over "ITEMS" at
   7.5px. Nothing legible fits in a circle at that size — 7.5px is below any
   floor worth defending — so it is a rounded rectangle with room in it.
   ========================================================================== */

/* --- 15a ---------------------------------------------------------------- */
@media (min-width: 1000px) {
  .week::before {
    opacity: .6;
    background: linear-gradient(90deg,
      var(--band-accent)  0%,     var(--band-accent) 16.66%,
      var(--brand)       16.66%,  var(--brand)       50%,
      var(--band-chill)  50%,     var(--band-chill)  83.33%,
      var(--band-accent) 83.33%,  var(--band-accent) 100%);
  }
}
.week::before {
  opacity: .6;
  background: linear-gradient(180deg,
    var(--band-accent)  0%,     var(--band-accent) 16.66%,
    var(--brand)       16.66%,  var(--brand)       50%,
    var(--band-chill)  50%,     var(--band-chill)  83.33%,
    var(--band-accent) 83.33%,  var(--band-accent) 100%);
}

/* the cold steps are the emphasised ones, so they are filled rather than
   outlined — the eye should find them before it reads a word */
.week-step[data-cold]::before {
  border-color: var(--band-chill);
  background: var(--band-chill);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--band-chill) 16%, transparent);
}
.week-step[data-warm]::before {
  border-color: var(--band-accent);
  background: var(--band-accent);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--band-accent) 16%, transparent);
}
.week-step::before {
  transition: transform var(--dur-2) var(--ease-spring),
              box-shadow var(--dur-2) var(--ease-out);
}
.week-step:hover::before { transform: scale(1.25); }

/* The bracket. Absolutely positioned, so it takes no grid cell — `.week` is
   already `position: relative`. Desktop only: below 1000px the spine is
   vertical and a horizontal bracket over it would be nonsense. */
.week-cold { display: none; }
@media (min-width: 1000px) {
  .week-cold {
    display: block;
    position: absolute;
    left: 50%;
    width: 33.33%;
    top: -30px;
    pointer-events: none;
  }
  .week-cold i {
    display: block;
    text-align: center;
    font-family: var(--font-mono);
    font-style: normal;
    font-size: var(--t-2xs);
    letter-spacing: .16em;
    text-transform: uppercase;
    color: var(--band-chill);
  }
  .week-cold::after {
    content: '';
    display: block;
    margin-top: 6px;
    height: 7px;
    border: 1px solid var(--band-chill);
    border-bottom: 0;
    border-radius: 5px 5px 0 0;
    opacity: .45;
  }
}

/* --- 15b ---------------------------------------------------------------- */
.plan .badge { justify-self: start; }
.plan .badge-accent {
  order: -1;                       /* it labels the card, not the button */
  justify-self: start;
  font-weight: 600;
  color: var(--accent-ink);
  border-color: color-mix(in srgb, var(--accent) 35%, transparent);
}

.plan-size {
  min-width: 0;
  width: auto;
  height: auto;
  border-radius: 12px;
  padding: 7px 11px;
  font-size: 15px;
  letter-spacing: -.01em;
  transition: background var(--dur-2) var(--ease-out),
              color var(--dur-2) var(--ease-out);
}
.plan-size small {
  font-size: var(--t-2xs);                  /* was 7.5px, which is not a size */
  letter-spacing: .14em;
  opacity: .75;
  margin-top: 3px;
}
.plan:hover .plan-size { background: var(--brand); color: var(--brand-fg); }

/* the price is the thing being compared, so it gets the weight and the
   figures line up column to column */
.plan-price { font-size: var(--t-3xl); line-height: 1; }
.plan-price small { margin-left: 4px; }

@media (prefers-reduced-motion: reduce) {
  .week-step::before, .plan-size { transition: none !important; }
  .week-step:hover::before { transform: none; }
}

/* ==========================================================================
   16. THE ONBOARDING SEQUENCE — farmers.php

   THE FIRST PASS AT THIS WAS COSMETIC AND THE COMPLAINT WAS CORRECT.

   I added a connector between the numerals and markers to the lists, and left
   the layout alone. The layout was the problem. `.seq-row` was a three column
   grid — `116px | 1fr | 300px` — with a short paragraph in column two and a
   tall checklist CARD in column three. A grid row is as tall as its tallest
   cell, so every row was sized by the card, and the paragraph column simply
   stopped, leaving ABOUT 150 PIXELS OF DEAD WHITE under four of the five
   steps. Each step ran most of a screen. The connector I drew ran straight
   down the middle of that void and made it more obvious, not less.

   The checklist is not a sidebar. It is four short phrases — "land record or
   lease", "Aadhaar or ID proof" — and putting four short phrases in a 300px
   column stacked vertically is what created a 240px card next to a 120px
   paragraph. They are CHIPS now, sitting under the paragraph in the same
   column, wrapping on one or two lines.

   The third column is gone. Nothing sets the row height except the content,
   and the dead space cannot come back because there is no longer a tall
   element to come back from.

   MEASURED, BECAUSE THE OBVIOUS ASSUMPTION IS WRONG. Removing the void does
   not make the section shorter: the chips move below the paragraph, so the
   space they occupy is space the row now genuinely uses. Dropped under the
   paragraphs before: 114 / 87 / 71 / 87 / 73 px of nothing. A first version
   of this ran the label on its own line and came out 13% TALLER overall,
   which is a bad trade for the same content. The label sits inline with the
   chips instead, which buys back a line per step and lands the section within
   a few pixels of where it started — with the void gone and the paragraphs
   running full width rather than squeezed into a 1fr between two fixed
   columns.

   Chips also read better than a stacked list here: "bring these four things"
   is a set, not an order, and a horizontal set is scannable in one pass.
   ========================================================================== */
@media (min-width: 880px) {
  /* two columns, not three. See the note above. */
  .seq-row { grid-template-columns: 116px minmax(0, 1fr); gap: var(--s-7); }
  .seq-aside { grid-column: 2; }
}

.seq { gap: var(--s-6); }
.seq-row { padding-bottom: var(--s-6); }

@media (min-width: 880px) {
  .seq-row { position: relative; }
  .seq-row::before {
    content: '';
    position: absolute;
    left: 21px;                 /* the centre of a two-digit numeral */
    top: 46px;
    bottom: calc(var(--s-6) * -1 + var(--s-2));
    width: 2px;
    border-radius: 2px;
    background: linear-gradient(180deg,
      color-mix(in srgb, var(--accent) 55%, transparent),
      color-mix(in srgb, var(--accent)  0%, transparent));
  }
  .seq-row:last-child::before { display: none; }
}

.seq-n {
  display: block;
  transition: transform var(--dur-2) var(--ease-spring),
              color var(--dur-2) var(--ease-out);
}
.seq-row:hover .seq-n { color: var(--accent); transform: translateY(-2px); }

.seq-body p { max-width: 68ch; }

/* --- the checklist, as chips ------------------------------------------- */
.seq-aside {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--s-3) var(--s-5);
  margin-top: var(--s-4);
  padding: 0;
  border: 0;                     /* was a dashed card */
  background: none;
  box-shadow: none;
  font-family: inherit;
}
/* inline, not stacked: a label on its own line costs a line per step, and
   five of those put the section 13% taller than the layout it replaced */
.seq-aside b {
  flex: none;
  margin-bottom: 0;
  padding-top: 9px;              /* optical: align to the chip text, not the pill */
  font-family: var(--font-mono);
  font-size: var(--t-2xs);
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.seq-aside ul {
  flex: 1 1 320px;
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-3);
}
.seq-aside li {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  padding: 8px 14px 8px 12px;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--glass-blur));
  backdrop-filter: blur(var(--glass-blur));
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  line-height: 1.3;
  color: var(--fg-muted);
  transition: border-color var(--dur-2) var(--ease-out),
              color var(--dur-2) var(--ease-out),
              transform var(--dur-2) var(--ease-spring);
}
.seq-aside li::before {
  content: '';
  flex: none;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 70%, transparent);
  transition: background var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-spring);
}
.seq-aside li:hover {
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  color: var(--fg);
  transform: translateY(-2px);
}
.seq-aside li:hover::before { background: var(--accent); transform: scale(1.3); }

@media (prefers-reduced-motion: reduce) {
  .seq-n, .seq-aside li, .seq-aside li::before { transition: none !important; }
  .seq-row:hover .seq-n, .seq-aside li:hover { transform: none; }
  .seq-aside li:hover::before { transform: none; }
}

/* ==========================================================================
   17. THE SPLIT, AND THE GROUP CARD — farmers.php

   ---- 17a. the logo was being cropped by its own corners ---------------

   `.lineage img` is 62x62 with `object-fit: cover` and `border-radius`. The
   source is 1080x1085 — square — so `cover` was not the cropper and the
   obvious suspect is innocent. THE ROUNDED CORNERS WERE. The Dinko Agritech
   artwork has its wordmark baked in along the bottom edge, so the two bottom
   radii ate the D and the H: the tile read "INKO AGRITEC".

   A logo is not a photograph and must never be cropped to fit a box — the one
   thing it cannot survive is losing letters. `contain` with padding, and a
   bigger tile, because a wordmark at 62px is not legible even uncropped.

   This is the mirror of the note in CLAUDE.md about `dinkomart.png`: that one
   is 2.15:1 art on a square canvas and NEEDS `cover` on a wide box. Same
   family of bug, opposite direction. The question is always what the artwork
   is, not what the box is.

   Fixed for `about.php` and `index.php` at the same time — they carry the
   same card and the same crop.

   ---- 17b. the split was one box with a hairline in it ------------------

   The section is called "The split" and its whole argument is that there are
   two sides. It was drawn as a SINGLE rounded rectangle with a 1px gutter
   down the middle and both halves painted the identical `rgba(255,255,255,
   .04)`. One box with a line in it is not a split; it is a table. The only
   thing distinguishing the two sides was the tint of a 15px icon.

   Two cards now, with real space between them, each carrying its own colour
   as a top rule and a wash: sprout for what stays with the grower, amber for
   what the business takes on. Those are the two colours the whole site uses
   for exactly this — produce out, machinery back — so the split is drawn in
   the same language the rest of the build argues in.

   `align-items: start`, so each card is the height of its own contents. The
   grower's column is four short lines and ours is four long ones; stretched
   to match, the left card carried about eighty pixels of trailing dead space
   for no reason. Letting them differ also happens to say the true thing: the
   grower's side is the shorter list.
   ========================================================================== */

/* --- 17a --------------------------------------------------------------- */
.lineage img {
  width: 78px;
  height: 78px;
  padding: 9px;                /* the wordmark needs air inside the radius */
  object-fit: contain;         /* was cover — see the note above */
}

/* --- 17b --------------------------------------------------------------- */
.duty {
  gap: var(--s-5);
  background: none;
  border: 0;
  border-radius: 0;
  overflow: visible;
  align-items: start;
}
.on-band .duty { background: none; border: 0; }

.duty-col {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--band-border);
  border-radius: var(--r-panel);
  transition: transform var(--dur-2) var(--ease-out),
              border-color var(--dur-2) var(--ease-out);
}
/* the side's colour, stated once at the top of its own card */
.duty-col::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
}
.on-band .duty-col.is-yours {
  background: color-mix(in srgb, var(--band-sprout) 7%, transparent);
}
.on-band .duty-col.is-ours {
  background: color-mix(in srgb, var(--band-accent) 7%, transparent);
}
.duty-col.is-yours::before { background: var(--band-sprout); }
.duty-col.is-ours::before  { background: var(--band-accent); }

.duty-col:hover { transform: translateY(-3px); }
.duty-col.is-yours:hover { border-color: color-mix(in srgb, var(--band-sprout) 55%, transparent); }
.duty-col.is-ours:hover  { border-color: color-mix(in srgb, var(--band-accent) 55%, transparent); }

/* the head rule picks up the side's colour too, instead of being a grey line
   that could belong to either column */
.duty-col.is-yours .duty-head { border-bottom-color: color-mix(in srgb, var(--band-sprout) 30%, transparent); }
.duty-col.is-ours  .duty-head { border-bottom-color: color-mix(in srgb, var(--band-accent) 30%, transparent); }

@media (prefers-reduced-motion: reduce) {
  .duty-col { transition: none !important; }
  .duty-col:hover { transform: none; }
}

/* ==========================================================================
   18. THE TEN-STAGE TIMELINE — about.php

   ---- 18a. rules that ran past their own content ------------------------

   `.tl-row` is `210px | minmax(0, 1fr)` inside a full `.wrap`, and the prose
   inside it is capped at a readable measure — 622px, which is correct and is
   not the bug. The bug is that the ROW was 1014px at this viewport and wider
   on a large monitor, so every `border-top` ran 142px (and on a 1600px screen
   nearer 400px) past the last word underneath it. Seven rules, each ending in
   nothing. That is what makes a section read as broken rather than airy: not
   the empty space, but a line drawn across it.

   Capping the timeline at the width of its own content is the whole fix. The
   measure stays where typography wants it and the rules now stop where the
   text stops.

   ---- 18b. a spine that did not match its own stages --------------------

   `linear-gradient(180deg, brand, chill 46%, accent 78%, iron)` at .3. The
   stages are tinted `default default default chill chill default gold`, so
   the gradient agreed with the content at no point on its length, and `iron`
   appeared in the spine while no stage is tinted iron at all.

   The farm-box spine took hard stops on sixths because its steps are equal
   grid columns. These rows are prose and every one is a different height, so
   percentages cannot line up with them at any value. Each row draws its OWN
   segment instead, in its own tint, exactly as tall as it is. It cannot drift
   from the content because it is made of the content.

   Same reasoning as the connector on farmers.php: per row, not per group.
   ========================================================================== */

/* --- 18a --------------------------------------------------------------- */
.timeline {
  /* 210px key + the --s-8 gutter + the prose measure. Anything wider draws
     rule across dead space. */
  max-width: 900px;
}

/* --- 18b --------------------------------------------------------------- */
@media (min-width: 800px) {
  /* the single gradient spine is replaced by one segment per row */
  .timeline::before { display: none; }

  .tl-row::after {
    content: '';
    position: absolute;
    left: 194px;
    top: 0;
    bottom: 0;
    width: 2px;
    border-radius: 2px;
    background: var(--brand);
    opacity: .34;
  }
  .tl-row[data-tint='chill']::after { background: var(--chill); }
  .tl-row[data-tint='gold']::after  { background: var(--accent); }
  .tl-row[data-tint='iron']::after  { background: var(--iron); }
  /* the last stage is where the loop turns, so its segment fades out rather
     than stopping dead — there is no stage eleven to carry on to */
  .tl-row:last-child::after {
    background: linear-gradient(180deg, var(--accent), transparent);
    opacity: .5;
  }

  /* a tinted stage is a stage worth finding, so it is filled rather than
     outlined — the same rule the farm-box timeline follows */
  .tl-row::before {
    transition: transform var(--dur-2) var(--ease-spring),
                box-shadow var(--dur-2) var(--ease-out);
  }
  .tl-row[data-tint]::before { background: currentColor; }
  .tl-row[data-tint='chill']::before {
    background: var(--chill);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--chill) 15%, transparent);
  }
  .tl-row[data-tint='gold']::before {
    background: var(--accent);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 15%, transparent);
  }
  .tl-row:hover::before { transform: scale(1.3); }
}

.tl-row { transition: background var(--dur-2) var(--ease-out); }
.tl-row:hover { background: var(--rhythm); }
.tl-row h2 { transition: color var(--dur-2) var(--ease-out); }
.tl-row:hover h2 { color: var(--brand-ink); }
.tl-row[data-tint='chill']:hover h2 { color: var(--chill); }

@media (prefers-reduced-motion: reduce) {
  .tl-row, .tl-row::before, .tl-row h2 { transition: none !important; }
  .tl-row:hover::before { transform: none; }
}

/* ==========================================================================
   19. THE DOCUMENT WALL — about.php

   The section is titled "The documents that exist" and its lede promises
   "only things that are on paper". Four cards, and the footer line of each is
   the payload — the actual document. It was the smallest, faintest thing on
   the card, and all four were styled identically.

   WHICH MATTERS, BECAUSE TWO OF THE FOUR ARE NOT DOCUMENTS THAT EXIST.
   "Certificate of incorporation" is on file. "Reference to be published" is
   explicitly not — the card's own prose says the number is withheld until it
   has been checked against the letter. "Per district, at enquiry" is raised
   when someone asks; nobody is holding it. On a wall whose entire argument is
   that these are real, rendering a thing that exists and a thing that does
   not in the identical grey mono is the one mistake the section cannot
   afford.

   Three states, each with its own mark:
     filed    a document icon, in the ink used for real references
     pending  a clock, muted, and the tear-line stays dashed
     request  an envelope — it comes to you, it is not held here

   The dashed-rule-inks-solid hover is kept for `filed` and deliberately NOT
   given to `pending`. That gesture means "the document exists"; running it
   under a reference that has not been published yet would be the animation
   telling a lie the copy is careful not to.
   ========================================================================== */
.cred-doc {
  display: flex;
  align-items: center;
  gap: var(--s-2);
}
.cred-doc .ico {
  width: 13px;
  height: 13px;
  flex: none;
  transition: transform var(--dur-2) var(--ease-spring);
}
.cred:hover .cred-doc .ico { transform: translateY(-1px) scale(1.08); }

.cred-doc[data-doc='pending'],
.cred-doc[data-doc='request'] { color: var(--fg-muted); }

/* the ink-in gesture means "this exists". A pending reference does not get
   it — see the note above. */
.cred-doc[data-doc='pending']::before { display: none; }
.cred-doc[data-doc='request']::before { background: var(--fg-faint); }

@media (prefers-reduced-motion: reduce) {
  .cred-doc .ico { transition: none !important; }
  .cred:hover .cred-doc .ico { transform: none; }
}

/* ==========================================================================
   20. THE TOPIC CHOOSER, AND THE ORPHANED PIN CHECK — contact.php

   ---- 20a. six routes, shown ------------------------------------------

   See the note in signature.js section 9 for why this exists. In short: the
   section lede promises the form "asks what it is about first", the form
   asked it fourth in a native `<select>`, and a page whose whole subject is
   routing should not hide its six routes behind a dropdown.

   `.is-enhanced` is set by script only after the chips are in the DOM, so
   the select is never hidden on the strength of a promise — if that line is
   never reached, the form is a plain complete form. That is the difference
   between progressive enhancement and the rule this project keeps breaking.

   The select is stood down with the build's own `.visually-hidden` (applied
   from script, see signature.js) rather than `display: none`, because the
   validator in app.js focuses the offending control on a failed submit and
   focus does nothing on a display:none element. Script also redirects that
   focus onto the selected chip, but the control stays focusable either way.

   ---- 20b. a 520px box in a 1400px wrap --------------------------------

   The pin check sat at `max-width: 520px` at the bottom of a full-width
   section with eight hundred pixels of nothing beside it. It is the same
   mistake as `.lineage` before it was given `fit-content`, and the same
   mistake as the timeline rules on about.php: a component narrower than the
   space it is asked to hold reads as an orphan, not as breathing room.

   It becomes a full-width bar instead — label left, field right — which is
   the shape a single-question form actually wants, and it now reads as the
   footer of the section it belongs to rather than something left behind.
   ========================================================================== */

/* --- 20a --------------------------------------------------------------- */
/* THE STRAY TICK.

   `.field.is-success::after` in atelier.css draws a validation tick at
   `top: 50%; right: 14px` - correct for a single-line input, where the middle
   of the right edge is inside the box being validated. This field is a
   label plus a two-row grid of tiles, so its centre-right fell in the corner
   of whichever tile happened to sit at the end of row one, anchored to
   nothing and unmoved by which tile was actually chosen. A tick floating over
   an unselected option is worse than no tick at all.

   The affordance belongs on the chosen tile, so that is where it goes. */
[data-topic-field].is-enhanced.field.is-success::after { display: none; }
/* TWO COLUMNS WAS A COUNT, NOT A FIT, AND ON A 320px SCREEN IT DID NOT FIT.

   `repeat(2, minmax(0, 1fr))` gave each tile 136px at 320. A tile is padding
   32 + icon 34 + gap 12 + text, and the text cannot shrink past its longest
   word: measured, the six tiles need 129, 132, 147, 148, 150 and 158px of
   min-content. So "My farm / registering a plot" and three of its neighbours
   ran their second line out to 147px inside a 136px tile — `.topic-opt` is
   `overflow: visible`, so it painted over its own rounded border rather than
   being cut.

   `minmax(0, …)` is what allowed it: it lets a track go below its content's
   minimum. The floor is now the widest tile's min-content, so the count falls
   to one on its own where two will not fit (below ~375px) instead of being
   promised and then broken. `min(…, 100%)` keeps it from overflowing a
   container narrower than one tile. */
.topic-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(160px, 100%), 1fr));
  gap: var(--s-3);
  margin-top: var(--s-2);
}
@media (min-width: 620px) { .topic-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.topic-opt {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-4);
  text-align: left;
  border: 1px solid var(--border);
  border-radius: var(--r-ctl);
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--glass-blur));
  backdrop-filter: blur(var(--glass-blur));
  color: var(--fg);
  cursor: pointer;
  transition: border-color var(--dur-2) var(--ease-out),
              background var(--dur-2) var(--ease-out),
              transform var(--dur-2) var(--ease-spring),
              box-shadow var(--dur-2) var(--ease-out);
}
.topic-ico {
  display: grid;
  place-items: center;
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--brand) 12%, transparent);
  color: var(--brand-ink);
  transition: background var(--dur-2) var(--ease-out),
              color var(--dur-2) var(--ease-out),
              transform var(--dur-2) var(--ease-spring);
}
.topic-ico .ico { width: 17px; height: 17px; }

.topic-txt { display: grid; gap: 1px; min-width: 0; }
.topic-txt b {
  font-family: var(--font-display);
  font-size: var(--t-sm);
  font-weight: 700;
  letter-spacing: var(--tr-snug);
  line-height: 1.2;
}
.topic-txt small {
  font-family: var(--font-mono);
  font-size: var(--t-2xs);
  letter-spacing: .04em;
  color: var(--fg-muted);
  line-height: 1.3;
}

.topic-opt:hover {
  border-color: color-mix(in srgb, var(--brand) 45%, transparent);
  transform: translateY(-2px);
  box-shadow: var(--shadow-sm);
}
.topic-opt:hover .topic-ico { transform: scale(1.06); }

/* chosen: the tile fills, so the answer is legible from across the form
   rather than only from inside it */
.topic-opt.is-on {
  border-color: var(--brand);
  background: color-mix(in srgb, var(--brand) 10%, transparent);
  box-shadow: 0 0 0 1px var(--brand);
}
.topic-opt.is-on .topic-ico { background: var(--brand); color: var(--brand-fg); }
.topic-opt.is-on .topic-txt small { color: var(--brand-ink); }

/* the mark, on the tile it belongs to. Drawn from two borders rather than a
   glyph so it inherits the brand ink and needs no icon sprite. */
.topic-opt { position: relative; }
.topic-opt.is-on::after {
  content: '';
  position: absolute;
  top: 12px;
  right: 12px;
  width: 11px;
  height: 6px;
  border-left: 2px solid var(--brand);
  border-bottom: 2px solid var(--brand);
  transform: rotate(-45deg);
  animation: topicTick 260ms var(--ease-out) backwards;
}
@keyframes topicTick {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}
@media (prefers-reduced-motion: reduce) {
  .topic-opt.is-on::after { animation: none; }
}

/* --- 20b --------------------------------------------------------------- */
.pincheck {
  max-width: none;              /* overrides the inline 520px */
  display: grid;
  gap: var(--s-3) var(--s-6);
  align-items: center;
}
@media (min-width: 760px) {
  .pincheck { grid-template-columns: minmax(0, 1fr) auto; }
  .pincheck-head { margin: 0; }
  .pincheck-out { grid-column: 1 / -1; }
}

@media (prefers-reduced-motion: reduce) {
  .topic-opt, .topic-ico { transition: none !important; }
  .topic-opt:hover { transform: none; }
  .topic-opt:hover .topic-ico { transform: none; }
}

/* ==========================================================================
   21. THE LINE, AS A LINE — about.php

   See the note in signature.js section 10 for why. In short: the section is a
   diagram written out, it ran about 1500px as seven stacked paragraphs, and
   its own last stage says "we draw the line as a circle rather than an arrow"
   underneath a column that is unmistakably an arrow.

   EACH NODE PAINTS THE SEGMENT TO ITS LEFT, in its own tint. That is what
   makes the rail carry the argument rather than decorate it: the line goes
   cold arriving at stage 04, which is the stage where the produce goes to
   four degrees, and turns amber arriving at the return. The first node has no
   segment, which is how you can tell it is the first.

   `is-done` dims the nodes behind you. A stepper that looks identical either
   side of the current position is a row of buttons, not a line you are part
   way along.

   The stacked list is left entirely alone below 720px. A seven-node rail on a
   phone is either unreadably small or a horizontal scroll nobody asked for,
   and the original layout was never the problem at that width.
   ========================================================================== */
/* Below 720px there is no rail, so the panels are simply the stacked list
   again — `.is-off` does nothing here on purpose. An earlier version used the
   `hidden` attribute and `display: none` on `.lineup`, which left the whole
   section BLANK on a phone. */
.lineup { display: contents; }
.lineup-rail, .lineup-nav { display: none; }
.lineup-stage { display: contents; }

@media (min-width: 720px) {
  .timeline.is-stepper { max-width: none; }
  /* the plain list and the per-row spine are the no-JS layout; once the rail
     exists they would be a second, contradictory drawing of the same thing */
  .timeline.is-stepper > .tl-row,
  .timeline.is-stepper::before { display: none; }
  .lineup { display: grid; gap: var(--s-7); }
  .lineup-stage { display: block; }   /* the base rule is `contents`, for phones */
  /* Only where there is a rail to switch them — and at a specificity that
     clears `.timeline.is-stepper .lineup-panel` further down, which sets
     `display: grid` and otherwise leaves all seven panels on screen. */
  .timeline.is-stepper .lineup-panel.is-off { display: none; }

  /* ------------------------------------------------------------- the rail */
  .lineup-rail {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
    align-items: start;
  }

  .lineup-node {
    position: relative;
    display: grid;
    justify-items: center;
    gap: var(--s-2);
    padding: var(--s-5) var(--s-2) var(--s-3);
    border: 0;
    border-radius: var(--r-ctl);
    background: none;
    cursor: pointer;
    color: var(--fg-muted);
    text-align: center;
    transition: color var(--dur-2) var(--ease-out),
                background var(--dur-2) var(--ease-out);
  }
  /* a hit area you can see. Without it the rail is a diagram, and nobody
     clicks a diagram. */
  .lineup-node:hover { background: var(--rhythm); }
  .lineup-node:focus-visible { background: var(--rhythm); }

  /* the segment reaching back to the previous node, in THIS node's tint */
  .lineup-node::before {
    content: '';
    position: absolute;
    top: calc(var(--s-5) + 6px);
    right: 50%;
    left: -50%;
    height: 2px;
    border-radius: 2px;
    background: var(--brand);
    opacity: .34;
  }
  .lineup-node:first-child::before { display: none; }
  .lineup-node[data-tint='chill']::before { background: var(--chill); }
  .lineup-node[data-tint='gold']::before  { background: var(--accent); }

  .lineup-dot {
    position: relative;
    z-index: 1;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--bg);
    border: 2px solid var(--brand);
    transition: transform var(--dur-2) var(--ease-spring),
                background var(--dur-2) var(--ease-out),
                box-shadow var(--dur-2) var(--ease-out);
  }
  .lineup-node[data-tint='chill'] .lineup-dot { border-color: var(--chill); }
  .lineup-node[data-tint='gold']  .lineup-dot { border-color: var(--accent); }

  .lineup-n {
    font-family: var(--font-mono);
    font-size: var(--t-xs);
    font-weight: 700;
    letter-spacing: .06em;
    color: var(--fg);
    font-variant-numeric: tabular-nums;
  }
  .lineup-lbl {
    font-family: var(--font-mono);
    font-size: var(--t-2xs);
    letter-spacing: .12em;
    text-transform: uppercase;
    line-height: 1.3;
    color: var(--fg-muted);
    transition: color var(--dur-2) var(--ease-out);
  }

  .lineup-node:hover .lineup-dot { transform: scale(1.2); }
  .lineup-node:hover .lineup-lbl { color: var(--fg); }

  /* where you are */
  .lineup-node.is-on .lineup-dot {
    background: var(--brand);
    box-shadow: 0 0 0 5px color-mix(in srgb, var(--brand) 15%, transparent);
    transform: scale(1.15);
  }
  .lineup-node.is-on[data-tint='chill'] .lineup-dot {
    background: var(--chill);
    box-shadow: 0 0 0 5px color-mix(in srgb, var(--chill) 15%, transparent);
  }
  .lineup-node.is-on[data-tint='gold'] .lineup-dot {
    background: var(--accent);
    box-shadow: 0 0 0 5px color-mix(in srgb, var(--accent) 15%, transparent);
  }
  .lineup-node.is-on .lineup-lbl { color: var(--fg); }

  /* where you have been */
  .lineup-node.is-done .lineup-dot { background: var(--brand); opacity: .55; }
  .lineup-node.is-done[data-tint='chill'] .lineup-dot { background: var(--chill); }
  .lineup-node.is-done .lineup-n,
  .lineup-node.is-done .lineup-lbl { opacity: .6; }

  /* ------------------------------------------------------------ the panel */
  /* The notch. It points from the live node into the panel, which is the
     one thing that makes "this dot is showing this text" visible rather than
     something the reader has to work out. `--lineup-x` is set by script. */
  .lineup-stage {
    position: relative;
  }
  .lineup-stage::before,
  .lineup-stage::after {
    content: '';
    position: absolute;
    top: -9px;
    left: var(--lineup-x, 50%);
    width: 18px;
    height: 9px;
    margin-left: -9px;
    clip-path: polygon(50% 0, 100% 100%, 0 100%);
    transition: left var(--dur-3) var(--ease-spring);
  }
  .lineup-stage::before { background: var(--border); }
  .lineup-stage::after  { top: -8px; background: var(--surface); }

  .lineup-stage {
    border: 1px solid var(--border);
    border-radius: var(--r-panel);
    background: var(--glass);
    -webkit-backdrop-filter: blur(var(--glass-blur));
    backdrop-filter: blur(var(--glass-blur));
    box-shadow: var(--shadow-sm);
  }
  .timeline.is-stepper .lineup-panel {
    display: grid;
    gap: var(--s-3);
    grid-template-columns: 210px minmax(0, 1fr);
    padding: var(--s-7);
    border: 0;
    align-items: baseline;
  }
  .timeline.is-stepper .lineup-panel::before,
  .timeline.is-stepper .lineup-panel::after { display: none; }
  .timeline.is-stepper .lineup-panel:hover { background: none; }
  .timeline.is-stepper .lineup-panel h2 { font-size: var(--t-xl); }
  .timeline.is-stepper .lineup-panel p { max-width: 62ch; }

  /* -------------------------------------------------------------- the nav
     Named prev/next and a count. A hover state cannot advertise anything to
     someone who has not hovered; these can be read from across the page, and
     they are also simply the fastest way through seven stages. */
  .lineup-nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-4);
    padding: var(--s-4) var(--s-7) var(--s-5);
    border-top: 1px solid var(--hairline);
  }
  .lineup-step {
    display: inline-flex;
    align-items: center;
    gap: var(--s-3);
    /* 44px, not the 39px this shipped at. These are the primary way through
       the section, so they get the target size rather than an exemption. */
    padding: 12px 16px;
    min-height: 44px;
    border: 1px solid var(--border);
    border-radius: var(--r-pill);
    background: var(--surface);
    color: var(--fg-muted);
    cursor: pointer;
    font-family: var(--font-mono);
    font-size: var(--t-xs);
    letter-spacing: .04em;
    text-transform: uppercase;
    transition: border-color var(--dur-2) var(--ease-out),
                color var(--dur-2) var(--ease-out),
                transform var(--dur-2) var(--ease-spring);
  }
  .lineup-step:hover { border-color: var(--brand); color: var(--brand-ink); }
  .lineup-step.is-prev:hover { transform: translateX(-2px); }
  .lineup-step.is-next:hover { transform: translateX(2px); }

  .lineup-arrow {
    width: 7px; height: 7px;
    border-top: 1.5px solid currentColor;
    border-right: 1.5px solid currentColor;
    flex: none;
  }
  .lineup-step.is-prev .lineup-arrow { transform: rotate(-135deg); }
  .lineup-step.is-next .lineup-arrow { transform: rotate(45deg); }

  .lineup-count {
    font-family: var(--font-mono);
    font-size: var(--t-xs);
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--fg-muted);
    font-variant-numeric: tabular-nums;
  }
  @media (max-width: 899px) {
    .lineup-step-txt { display: none; }   /* the arrows still read */
    .lineup-step { padding: 12px 14px; }
  }

  .lineup-panel.is-swap { animation: lineupIn var(--dur-3) var(--ease-out) both; }
  @keyframes lineupIn {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
  }
}

@media (prefers-reduced-motion: reduce) {
  .lineup-dot, .lineup-step, .lineup-stage::before, .lineup-stage::after { transition: none !important; }
  .lineup-node:hover .lineup-dot { transform: none; }
  .lineup-step:hover { transform: none; }
  .lineup-panel.is-swap { animation: none !important; }
}

/* ==========================================================================
   22. <code>, THE FACT CARDS, AND THE PIN BAR

   ---- 22a. Bootstrap was colouring the order number --------------------

   `<code>` was never styled by this build, so it fell through to
   `bootstrap.min.css`: `code { color: var(--bs-code-color) }`, which is
   `#d63384` — Bootstrap's pink — in Bootstrap's system mono stack. So the
   sample order number on a customer-facing page rendered in magenta, in an
   unvendored font, on a site that vendors three families precisely so it
   controls its own typography and whose palette is green and amber.

   Nobody wrote that colour. It arrived, which is the only reason it survived
   this long: there is no line to find in this project's own CSS.

   Checked, rather than assumed: `docs/user.php` also uses `<code>` but does
   NOT load this file — it has its own shell, and `guide.css:122` already
   styles `code` with `--font-mono` and a dark-theme variant. So the leak was
   contact.php alone, and the rule below is the storefront's missing half of
   what the docs shell had all along.

   ---- 22b. the fact cards ----------------------------------------------

   A bare 17px glyph floating beside two lines of text. Every other component
   built here seats its icon in a tile — the manifest, the topic chooser, the
   contact methods — and these were the odd ones out, which is most of why
   they read as unfinished next to the rest of the page. Same tile, same
   sizing, so the page has one idea about what an icon is.

   ---- 22c. the pin bar, and a fix that made things worse ---------------

   It shipped at `max-width: 520px` in a 1400px section: an orphan. I made it
   full width, which put the label at the far left and the field at the far
   right with NINE HUNDRED PIXELS OF NOTHING between them — the same bug,
   larger. A bar is only a bar if something occupies its middle.

   What belongs there is the answer the component already knows: the two
   cities we deliver to and the district we pack in, which is exactly what
   `initPincheck()` says in its own fallback. The bar now states what is
   known before you ask it anything, which is also the more useful order.
   ========================================================================== */

/* --- 22a --------------------------------------------------------------- */
code {
  font-family: var(--font-mono);
  font-size: .92em;
  letter-spacing: .02em;
  color: var(--fg);
  background: var(--surface-sunk);
  border: 1px solid var(--hairline);
  border-radius: 5px;
  padding: 2px 6px;
  white-space: nowrap;
}
.on-band code {
  color: var(--band-fg);
  background: rgba(255, 255, 255, .07);
  border-color: var(--band-border);
}

/* --- 22b --------------------------------------------------------------- */
.fact-chip {
  gap: var(--s-4);
  padding: var(--s-4) var(--s-5);
  transition: border-color var(--dur-2) var(--ease-out),
              transform var(--dur-2) var(--ease-spring),
              box-shadow var(--dur-2) var(--ease-out);
}
.fact-chip:hover { transform: translateY(-3px); box-shadow: var(--shadow-sm); }

/* the icon gets a seat, like every other icon on the page */
.fact-chip > .ico {
  box-sizing: content-box;
  width: 16px;
  height: 16px;
  margin-top: 0;
  padding: 8px;
  border-radius: 9px;
  background: color-mix(in srgb, var(--brand) 12%, transparent);
  transition: background var(--dur-2) var(--ease-out),
              color var(--dur-2) var(--ease-out),
              transform var(--dur-2) var(--ease-spring);
}
.fact-chip.is-cold > .ico { background: color-mix(in srgb, var(--chill) 14%, transparent); }
.fact-chip.is-warm > .ico { background: color-mix(in srgb, var(--accent) 14%, transparent); }

.fact-chip:hover > .ico { transform: scale(1.06); background: var(--brand); color: var(--brand-fg); }
.fact-chip.is-cold:hover > .ico { background: var(--chill); color: var(--band-fg); }
.fact-chip.is-warm:hover > .ico { background: var(--accent); color: var(--accent-fg); }

.fact-chip b { font-size: var(--t-md); letter-spacing: var(--tr-snug); }
.fact-chip b + span { margin-top: 4px; line-height: 1.5; color: var(--fg-muted); }

/* --- 22c --------------------------------------------------------------- */
.pincheck-lead { display: grid; gap: 4px; min-width: 0; }
.pincheck-sub {
  margin: 0;
  font-size: var(--t-xs);
  line-height: 1.5;
  color: var(--fg-muted);
  max-width: 54ch;
  text-wrap: pretty;
}
@media (min-width: 760px) {
  .pincheck { grid-template-columns: minmax(0, 1fr) auto; align-items: center; }
}

@media (prefers-reduced-motion: reduce) {
  .fact-chip, .fact-chip > .ico { transition: none !important; }
  .fact-chip:hover { transform: none; }
  .fact-chip:hover > .ico { transform: none; }
}

/* ==========================================================================
   23. THE SPECIFICATION TABLE — product.php

   Scoped `.pdp-block .spec` so it can never be confused with the plate above
   again. Three things were wrong with it and only one of them was styling.

   THE LABEL COLUMN WAS SIZED BY A GUESS. components.css asks for `width: 44%`,
   which on a 670px table reserves 295px for the word "KEEPS" and leaves the
   value column to wrap "Held at 4°C from the field crate to the van" onto two
   ragged lines. `width: 1%` + `nowrap` is the shrink-to-content idiom: the
   column ends up as wide as the longest label and not one pixel wider, so the
   values get every remaining pixel and mostly stop wrapping at all. It also
   self-adjusts per category — machinery labels are longer than produce ones —
   which a percentage cannot do.

   EVERYTHING WAS ONE COLOUR. Label and value were both mono, both 500, a step
   apart in ink. A spec sheet is scanned, not read, and scanning needs the
   value to win. The label drops to the same 11px/.2em treatment the plate
   keys use, and the value comes up to `--fg`.

   THE COLD CHAIN ROW IS TINTED, and it is the only one. Four degrees is the
   claim the whole business rests on, `--chill` is already the colour this site
   uses for it on the chain diagram and the plate, and a table that prints it
   in the same grey as "1 kg" is throwing away a distinction it has already
   made everywhere else. The class is put on by signature.js from the row's own
   label, so it cannot land on a row that is not about temperature.
   ========================================================================== */
.pdp-block .spec { font-size: var(--t-sm); }

.pdp-block .spec th {
  /* shrink-to-content: `width: 1%` on an auto-layout table resolves to
     min-content once `nowrap` stops the label breaking */
  width: 1%;
  white-space: nowrap;
  padding: var(--s-4) var(--s-6) var(--s-4) 0;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--fg-muted);
  vertical-align: baseline;
}

.pdp-block .spec td {
  padding: var(--s-4) 0;
  color: var(--fg);
  font-weight: 500;
  line-height: 1.55;
  text-wrap: pretty;
  font-variant-numeric: tabular-nums;
}

/* The one row that carries a temperature. */
.pdp-block .spec tr.is-chill td { color: var(--chill); }

@media (max-width: 560px) {
  /* Two columns of nowrap labels and long values do not both fit on a phone.
     Stack them — the label keeps its own line and the value gets full width,
     which is the same information in the order it is read. */
  .pdp-block .spec th,
  .pdp-block .spec td { display: block; width: auto; white-space: normal; }
  .pdp-block .spec th { padding: var(--s-4) 0 2px; border-bottom: 0; }
  .pdp-block .spec td { padding: 0 0 var(--s-4); }
}

/* ==========================================================================
   23b. "WHAT WE CAN HELP WITH", ONCE IT ROUTES

   `.is-routed` is set by signature.js only after the rows have actually been
   upgraded to buttons, so none of this can style a control that is not there.

   IT HAS TO LOOK CLICKABLE AT REST. This build has already learnt that once,
   on the Line's stepper: a row of dots on a line was a diagram, and nobody
   clicks a diagram. A hover state does not answer it, because hover is
   invisible to the person deciding whether to bother. So the affordance is
   in the resting state — a chevron in the margin, present on every row — and
   hover only confirms what the chevron already promised.

   The chevron is drawn from `content` on a pseudo-element rather than an icon
   because these rows already carry one icon each and a second SVG per row is
   a lot of markup to say "→". It is `aria-hidden` by construction: generated
   content is not in the accessibility tree.
   ========================================================================== */
.topic-list.is-routed li { padding: 0; }

.topic-list.is-routed .topic-link {
  /* a button, reset to the row it replaced */
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  width: 100%;
  margin: 0;
  padding: var(--s-3) var(--s-5) var(--s-3) 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--t-sm);
  line-height: 1.45;
  color: var(--fg-muted);
  text-align: left;
  cursor: pointer;
  position: relative;
  border-radius: var(--r-sm, 6px);
  transition: color var(--dur-1) var(--ease-out),
              background-color var(--dur-1) var(--ease-out);
}

/* the resting affordance */
.topic-list.is-routed .topic-link::after {
  content: '\203A';                    /* › */
  position: absolute;
  right: var(--s-3);
  top: 50%;
  transform: translateY(-50%);
  color: var(--fg-faint);
  font-size: 15px;
  line-height: 1;
  transition: transform var(--dur-1) var(--ease-out),
              color var(--dur-1) var(--ease-out);
}

.topic-list.is-routed .topic-link:hover,
.topic-list.is-routed .topic-link:focus-visible {
  color: var(--fg);
  background: var(--rhythm);
}
.topic-list.is-routed .topic-link:hover::after,
.topic-list.is-routed .topic-link:focus-visible::after {
  color: var(--brand);
  transform: translateY(-50%) translateX(3px);
}
@media (prefers-reduced-motion: reduce) {
  .topic-list.is-routed .topic-link::after { transition: none; }
  .topic-list.is-routed .topic-link:hover::after { transform: translateY(-50%); }
}

@media (pointer: coarse) {
  .topic-list.is-routed .topic-link { min-height: 44px; align-items: center; }
}

/* ==========================================================================
   23c. THE CONTACT PAGE, FINISHED

   The page has two peer sections and they were set two steps apart with
   nothing between: `.sec-head h2` measures 54px and `.mini-head h2` measured
   26px — less than half, for a section carrying four cards and a live pin
   check. The second section is genuinely secondary and should be smaller;
   it should not look like a caption.

   Everything below is craft on markup that already exists. The fact cards
   already carry `is-warm` and `is-cold`; they were just not being spent.
   ========================================================================== */

/* --- scale ---------------------------------------------------------------
   A middle step, so the page reads 54 -> 38 -> body rather than 54 -> 26. */
.mini-head h2 {
  font-size: clamp(1.6rem, 1.15rem + 1.9vw, 2.35rem);
  line-height: 1.12;
  letter-spacing: -.02em;
  text-wrap: balance;
}
.mini-head > div > p { max-width: 58ch; }

/* --- the four cards ------------------------------------------------------
   THE ORNAMENT IS THE THING YOU HAVE TO HAVE READY. Each card names one item
   to bring — an order number, a district, a pin code, a time — and each of
   those was set in running prose at body weight. The card is a checklist
   item; the item should be the first thing off it.

   The tone rule on the top edge spends `is-warm` and `is-cold`, which the
   markup already declares and nothing was drawing. Machinery is amber and
   delivery is chill everywhere else on this site, so the cards now say which
   half of the line they belong to in the language the rest of the build
   argues in. */
.fact-chip {
  position: relative;
  overflow: hidden;
  transition: transform var(--dur-2) var(--ease-out),
              border-color var(--dur-2) var(--ease-out),
              box-shadow var(--dur-2) var(--ease-out);
}
.fact-chip::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  background: var(--brand);
  transform: scaleX(1);
  transform-origin: left center;
}
.fact-chip.is-warm::before { background: var(--accent); }
.fact-chip.is-cold::before { background: var(--chill); }

.fact-chip:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-lg);
  border-color: transparent;
}

/* the item to bring, lifted out of the sentence around it */
.fact-chip b {
  font-size: var(--t-md);
  letter-spacing: -.01em;
}
.fact-chip code {
  font-family: var(--font-mono);
  font-size: .92em;
  padding: 1px 6px;
  border-radius: 5px;
  background: var(--rhythm);
  color: var(--fg);
  white-space: nowrap;
}

/* --- the pin bar ---------------------------------------------------------
   It is the one control in this section and it was the quietest thing in it:
   a wide sparse strip with a small field at the far end. The field is now
   sized like the answer it is asking for, and Check reads as the primary
   action of the row rather than a second thought beside it. */
.pincheck { transition: border-color var(--dur-2) var(--ease-out); }
.pincheck:focus-within { border-color: var(--brand); }

.pincheck-row input {
  min-height: 48px;
  font-size: var(--t-md);
}
.pincheck-row button { min-height: 48px; padding-inline: var(--s-6); }

/* --- the channels --------------------------------------------------------
   `.reach-item` already lifts and takes the brand on hover. What it never
   said, at rest, is that it goes anywhere — the same lesson the stepper cost
   this build: hover is invisible to the person deciding whether to bother. */
.reach-item { position: relative; padding-right: var(--s-8); }
.reach-item::after {
  content: '\203A';
  position: absolute;
  right: var(--s-5);
  top: 50%;
  transform: translateY(-50%);
  color: var(--fg-faint);
  font-size: 17px;
  line-height: 1;
  transition: transform var(--dur-2) var(--ease-out), color var(--dur-2) var(--ease-out);
}
.reach-item:hover::after { color: var(--brand); transform: translateY(-50%) translateX(3px); }

/* --- the form ------------------------------------------------------------
   One focus treatment across every field on the page, and a submit that
   acknowledges a press. */
.contact-grid .input:focus-visible,
.contact-grid textarea:focus-visible,
.contact-grid select:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}
.contact-grid button[type="submit"] {
  transition: transform 90ms var(--ease-out), box-shadow var(--dur-2) var(--ease-out),
              background-color var(--dur-2) var(--ease-out);
}
.contact-grid button[type="submit"]:hover { box-shadow: var(--shadow-lg); }
.contact-grid button[type="submit"]:active { transform: translateY(1px) scale(.99); }

@media (prefers-reduced-motion: reduce) {
  .fact-chip, .reach-item::after, .contact-grid button[type="submit"] {
    transition: none !important;
  }
  .fact-chip:hover { transform: none; }
  .reach-item:hover::after { transform: translateY(-50%); }
  .contact-grid button[type="submit"]:active { transform: none; }
}

/* ==========================================================================
   24. TOUCH

   The build already has a considered `@media (pointer: coarse)` block in
   craft.css covering .brand, .pcard-save, .season-tab and the carousel dots.
   Two controls were missed by it and two more are new here, so they get the
   same treatment rather than being left a few pixels short.

   Note this is keyed to `pointer: coarse`, NOT to a width. A narrow window on
   a desktop is still a mouse, and a 44px chip row under a mouse looks clumsy.
   ========================================================================== */
@media (pointer: coarse) {
  .season-pill { min-height: 44px; display: inline-flex; align-items: center; }
  .cutoff { padding: var(--s-4); }
  .pincheck-row button { min-height: 44px; }
  .fact-chip { padding: var(--s-4); }
  .chain-dot { width: 38px; height: 38px; }
  .chain-dot .ico { width: 17px; height: 17px; }
  /* the plate's cells are not controls, so they need no hit area — but the
     hover-only flourishes never fire on touch, so the resting state has to be
     the finished one */
  .spec-ticks i { --h: .78; }
  .spec-ticks i.is-core { --h: 1; }
}

/* ==========================================================================
   25. VERIFICATION HATCH — extends refine.css's for the components here.
   ========================================================================== */
[data-flat] .chain::after { transform: scaleX(1) !important; }
[data-flat] .sk::after { animation: none !important; }
[data-flat] .season-cell.is-now .season-m::before { animation: none !important; }
[data-flat] .lineup-panel.is-swap { animation: none !important; opacity: 1 !important; }
/* The plate's decoration is transform-driven and starts short. Without this
   the checker measures it mid-grow and reports a moving target. */
[data-flat] .spec-ticks i { transform: scaleY(var(--h)) !important; }
[data-flat] .spec-merc { left: 13.3% !important; }
[data-flat] .spec-week i { transform: scale(1) !important; opacity: 1 !important; }
