/* Weave Sprint hub route styles. Links after tokens.css, uses only shared
   tokens, and is loaded by exactly one page: /sprint/.

   BRANDCRAFT v4, batch 2 lane L-B. The idiom is the library cluster (DEC-002):
   /sprint is a shelf of books, and library/index.html is the living precedent
   for presenting books. The vitrine, hallmark, book-render and shelf-card
   PATTERNS are ported here rather than reused by reference: library.css is
   route-scoped to /library/ and is not loaded on this page, so every rule the
   markup needs is restated below.

   Stock: one continuous green sheet (--paper-green) from the hero through the
   rights band, torn into at the top, torn out of above the crisis block. The
   search band is a white card floating ON the sheet, not a sheet change: the
   chrome needs scanning contrast, the page does not need another seam.

   Legibility, measured, both themes. On the light green sheet only --ink
   clears the body floor (7.08:1); --ink-2 is 3.88 and the house --marker is
   1.94. So nothing on the sheet runs anything but ink, and the one accent is
   --sp-accent-ink #6B2A16, measured 4.91 on the green and 10.67 on white.
   Dark theme keeps --marker, which measures 5.58 on the deep green and 6.61
   on the dark card. Same ratified per-lane pattern as --lib-accent-ink.

   Rotation: containers only, and only three of them. The hero art plate, the
   two pen annotations, and the fact stamps. Vitrines, shelf cards, chips and
   the rights band are all 0deg: dense reference surfaces and live controls,
   the same ruling library gave its own vitrines and shelf.

   Shadows are the offset-solid ink tint from tokens, never blur glows.
   No dashed or dotted rule is declared anywhere in this file. */

/* ------------------------------------------------------------------ route */

.sprint {
  --sp-accent-ink: #6B2A16;
  --sp-card: var(--cloth-card);
  font-size: 1.0625rem;
  line-height: 1.62;
  color: var(--ink);
}
[data-theme="dark"] .sprint { --sp-accent-ink: var(--marker); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .sprint { --sp-accent-ink: var(--marker); }
}

/* Long unbreakable drug and syndrome names wrap rather than widen a card. */
.sprint :where(p, li, h1, h2, h3, .shelf-card__title, .vitrine__title) {
  overflow-wrap: anywhere;
  word-break: break-word;
}
.sprint :where(img, svg) { max-width: 100%; }
.sprint-defs { position: absolute; width: 0; height: 0; overflow: hidden; }

/* Seam law: the tear is the sheet's first child, its fill is the sheet ABOVE,
   and the sheet carries no top padding. .sheet-green already sets that. The
   green-to-quiet tear rides its own wrapper so the copied crisis section
   stays byte-identical to the study and library instances. */
.torn--cloth { color: var(--cloth); }
.torn--green { color: var(--paper-green); }
.sprint-seam { background: var(--paper-quiet); line-height: 0; }
.sheet-pad { padding-top: var(--space-section); }

.sprint-shell {
  width: min(1184px, calc(100% - 2.5rem));
  margin-inline: auto;
}

.sprint-eyebrow {
  display: block;
  margin: 0 0 0.9rem;
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 0.875rem;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}

/* Pen annotations. Caveat, 24px floor enforced by the .pen token; ink on the
   sheet, never the marker terracotta, which does not clear the floor here.
   Two on the page, far apart, both on containers. */
.sprint-annot {
  display: inline-block;
  margin: 1.4rem 0 0;
  transform: rotate(-1.2deg);
}
.sprint-annot--shelf { margin-top: 1rem; transform: rotate(1.1deg); }

/* ------------------------------------------------------------------- hero */

.sprint-hero {
  width: min(1184px, calc(100% - 2.5rem));
  margin-inline: auto;
  padding: clamp(7rem, 13vw, 10rem) 0 clamp(2.5rem, 5vw, 4rem);
}
.sprint-hero__grid { display: grid; gap: clamp(2rem, 5vw, 4rem); }
.sprint-hero__text { max-width: 44rem; }

.sprint-title {
  margin: 0 0 0.6rem;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: clamp(2.6rem, 7vw, 4.4rem);
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.04;
  padding-bottom: 0.28em;
}
/* The scribble runs the darkened terracotta: the house --marker measures
   1.94:1 on this sheet and would read as a smudge. */
.sprint-title .scribbled path { stroke: var(--sp-accent-ink); }

.sprint-sub {
  margin: 0 0 1rem;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 2.2vw, 1.45rem);
  font-weight: 700;
  line-height: 1.35;
}
.sprint-lede {
  max-width: 62ch;
  margin: 0;
  color: var(--ink);
  font-size: 1.0625rem;
  line-height: 1.6;
}
.sprint-stamps { margin-top: 1.5rem; }
.sprint-stamps .stamp { font-size: 0.84375rem; }

.sprint-hero__art { justify-self: center; }
.sprint-plate { width: min(100%, 26rem); }
/* Riso ink sits ON the paper. The blend lives on the img, never on a wrapped
   ancestor (stacking-context law), and the plate behind it is white so the
   illustration keeps its ground on the coloured sheet (L22). */
.sprint-plate > img { mix-blend-mode: multiply; }
[data-theme="dark"] .sprint-plate > img { mix-blend-mode: normal; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .sprint-plate > img { mix-blend-mode: normal; }
}

/* --------------------------------------------------------- search band */

/* Sticky because 44 items is a lot to scan and the search should never be
   more than a glance away. Sits below the floating nav pill, above the sheet,
   and is a white card rather than a sheet change.

   Every declaration on .sprint-search is important, and that is not defensive
   habit. The page's own script tags this element .pf-up when the custom
   element defines and .pf-down when it fails, so from that moment the class
   attribute contains " pf-" and the component sheet's catch-all reset

     :is(*, #\#):is(*, #\#) :is([class^="pf-"], [class*=" pf-"]) { all: revert }

   matches the band at (2,1,1) and reverts every property this rule sets. That
   is what shipped: measured on the upgraded page the band had no width limit
   (full bleed, 48px out of line with every other block on the route), no
   bottom margin at all (its border sat ON the filter chips, 0px clearance)
   and no sticky. An important author declaration outranks a normal one
   whatever its specificity, so importance is the fix, not higher specificity.
   Same reason every .pf-* rule below is important: the component pins those
   properties at (3,1,0) with its own :is(*, #\#) hack.

   Sticky top clears the floating nav pill rather than tucking under it. The
   pill measures 76.77px to its bottom edge below 768px and 95.16px at and
   above it (the wider pill carries the CTA button), so the two values below
   leave 11.2px and 12.8px of sheet showing between pill and card. The old
   76px/88px pair put the card 0.77px to 19.2px BEHIND the pill, which is what
   read as the bar touching the nav text. */
.sprint-search {
  position: sticky !important;
  z-index: 40 !important;
  top: 88px !important;
  box-sizing: border-box !important;
  width: min(1184px, calc(100% - 2.5rem)) !important;
  /* Above: adds to the hero's own 2.5rem-4rem bottom padding, so the card
     clears the stat chips by 64px on a phone and 104px on a desktop. Below:
     lifts the card off the filter chips while keeping band and chips reading
     as one finder rather than two sections. */
  margin: clamp(var(--space-6), 3vw, var(--space-10)) auto
    clamp(var(--space-8), 4vw, var(--space-12)) !important;
}
.sprint-search__inner {
  display: flex;
  flex-wrap: wrap;
  gap: 0.7rem;
  align-items: center;
  padding: 0.8rem 0.9rem;
  border: 2px solid var(--ink);
  border-radius: var(--wonky-1);
  background: var(--sp-card);
  box-shadow: var(--shadow-lift);
}
/* The custom element stays undefined until the cross-origin module runs, and
   an undefined element is zero-height: the card opened as a 29.6px sliver and
   jumped to 77.6px on upgrade, shoving the whole shelf down. Reserving the
   field height here makes the card the same size in both states, measured to
   the pixel. The 18rem basis is also what wraps the trigger onto its own row
   below about 530px, which is inside the phone breakpoint that then makes the
   wrapped button full width. */
.sprint-searchbox {
  display: block;
  flex: 1 1 18rem;
  min-width: 0;
  min-height: 48px;
}
.sprint-search__trigger {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  min-height: 48px;
}
/* pagefind-modal-trigger is an unknown element, so it lays out inline and the
   button's own width:100% resolves against a shrink-to-fit box. Making it a
   flex item lets it fill whatever the wrapper is given: content width in the
   desktop row, the whole row once it wraps on a phone. */
.sprint-search__trigger > * { flex: 1 1 auto; min-width: 0; }

/* Pagefind's component CSS pins .pf-searchbox to max-width 480px and
   inline-block, so the width override has to be hard, exactly as on /study. */
.sprint-searchbox .pf-searchbox {
  display: block !important;
  width: 100% !important;
  max-width: none !important;
}
.sprint-searchbox .pf-searchbox-input-wrapper {
  width: 100% !important;
  max-width: none !important;
}
/* The field is a Brandcraft object, matched to #filter-text sixty pixels
   below it: 2px ink border, radius-sm, paper card, UI face, 16px exactly (any
   smaller and iOS Safari zooms the page on focus). The offset solid is the
   one thing #filter-text does not carry, and it is what marks this as the
   primary control rather than the secondary one. Before this the field was
   the component's own 36px hairline box, which in dark theme was a #1D1A17
   field on a #1D1A17 card: the search bar had no visible edge at all.
   padding-inline-end is deliberately left alone, because the component swaps
   it between 72px and 12px to clear the shortcut cap and that logic is worth
   keeping. */
.sprint-searchbox .pf-searchbox-input {
  box-sizing: border-box !important;
  width: 100% !important;
  max-width: none !important;
  min-height: 48px !important;
  padding-block: 0 !important;
  padding-inline-start: 40px !important;
  border: 2px solid var(--ink) !important;
  border-radius: var(--radius-sm) !important;
  background: var(--sp-card) !important;
  color: var(--ink) !important;
  font-family: var(--font-ui) !important;
  font-size: 1rem !important;
  box-shadow: var(--shadow-soft) !important;
}
/* The magnifier is inset to sit with a 2px border rather than a 1px one. Its
   vertical placement is the component's own calc against --pf-input-height,
   which is why that variable is set below rather than the height overridden
   here: set the variable and the icon stays centred for free. */
.sprint-searchbox .pf-searchbox-input-wrapper::before {
  inset-inline-start: 14px !important;
}
/* House focus ring, the same 3px sage the rest of the site uses, in place of
   the component's teal 2px box-shadow. Offset 3px so the ring still clears
   the card border inside 0.7rem of padding. Measured 4.57:1 on the light card
   and 8.77:1 on the dark one. */
.sprint-searchbox .pf-searchbox-input:focus,
.sprint-searchbox .pf-searchbox-input:focus-visible {
  border-color: var(--ink) !important;
  outline: 3px solid var(--thread-sage) !important;
  outline-offset: 3px !important;
  box-shadow: var(--shadow-soft) !important;
}
/* A keyboard shortcut cap on a device with no keyboard, eating 72px of the
   field's typing width. Hidden where the pointer is coarse, and the padding
   it reserved handed back: on a 375px phone that returns the placeholder from
   eleven characters to a readable line. The shortcut itself still works. */
@media (pointer: coarse) {
  .sprint-searchbox .pf-searchbox-input-wrapper .pf-trigger-shortcut,
  .sprint-search__trigger .pf-trigger-shortcut {
    display: none !important;
  }
  .sprint-searchbox .pf-searchbox-input { padding-inline-end: 14px !important; }
}

/* The modal trigger is a live control the size of a chip, sitting one row
   above a row of chips, so it takes the page's compact button idiom
   (.paper-chips .chip) rather than the 52px Kalam .button: same ink border,
   same hand-cut radius, same uppercase UI label. The finder then reads as one
   set of controls instead of a stock widget above a hand-drawn one. */
.sprint-search__trigger .pf-trigger-btn {
  min-height: 48px !important;
  padding: 0 16px !important;
  border: 2px solid var(--ink) !important;
  border-radius: 4px 9px 5px 10px !important;
  background: var(--sp-card) !important;
  color: var(--ink) !important;
  font-family: var(--font-ui) !important;
  font-size: 0.9375rem !important;
  font-weight: 700 !important;
  letter-spacing: 0.08em !important;
  text-transform: uppercase !important;
  box-shadow: var(--shadow-soft) !important;
  transition: background-color var(--duration-fast) var(--ease-weave) !important;
}
.sprint-search__trigger .pf-trigger-btn:hover {
  background: var(--fill-cool) !important;
}
.sprint-search__trigger .pf-trigger-btn:focus-visible {
  outline: 3px solid var(--thread-sage) !important;
  outline-offset: 3px !important;
}
.sprint-search__trigger .pf-trigger-icon { background-color: var(--ink) !important; }
.sprint-search__trigger .pf-trigger-text { color: var(--ink) !important; }
/* The component's own sheet sets the key cap at 11px, under the 12px floor,
   and pins it with a three-ID specificity hack. Overridden hard. */
.pf-trigger-key { font-size: 0.75rem !important; }
.pf-filter {
  position: absolute !important;
  clip: rect(0 0 0 0);
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  border: 0;
  white-space: nowrap;
}

/* The component renders into the light DOM, not a shadow root; these --pf-*
   variables are how it is themed without fighting its (3,1,0) selectors.
   Three of them were hardcoded #0F766E, which is the LIGHT teal: on the dark
   card that measures 3.17:1, so the result highlight failed the 4.5:1 body
   floor and the focus border only just cleared the 3:1 UI floor. Routed
   through --thread-teal they follow the theme, measuring 5.47:1 light and
   8.97:1 dark. --pf-input-height is the single lever that sets the field
   height, the trigger height AND the magnifier's centring calc, so the field
   grows to the 48px this page gives every other control without any of the
   three being overridden by hand. */
:root {
  --pf-font: var(--font-ui);
  --pf-text: var(--ink);
  --pf-text-secondary: var(--ink-2);
  --pf-text-muted: var(--ink-3);
  --pf-background: var(--cloth-card);
  --pf-border: var(--hairline);
  --pf-border-focus: var(--thread-teal);
  --pf-hover: color-mix(in srgb, var(--thread-sage) 6%, var(--cloth));
  --pf-mark: var(--thread-teal);
  --pf-border-radius: var(--radius-sm);
  --pf-input-height: 48px;
  --pf-modal-backdrop: rgba(42, 34, 24, .45);
  --pf-modal-top: 8vh;
  --pf-result-title-font-size: 1.05rem;
  --pf-outline-focus: var(--thread-teal);
  --pf-outline-width: 2px;
}

/* Degraded state. The index is cross-origin on R2, so a bucket, CORS or
   network failure means the custom element never defines. That is a
   different thing from a search returning nothing, and it says so: one quiet
   line naming the four papers, never an empty box the reader types into.

   The note and the field are mutually exclusive states, never co-visible: in
   .pf-down the box and the trigger are display:none and this replaces them.
   So the separation that matters is from the card edge, not from an input.
   The block padding puts the sentence on the same optical margin the field
   sits on, and holds the card near its 77.6px up-state height at desktop so a
   failed load does not resize the page. */
.sprint-search__down {
  display: none;
  margin: 0;
  padding-block: 0.15rem;
  color: var(--ink);
  font-size: 1rem;
  line-height: 1.5;
}
/* The fallback links are the only route left when search is down, so they
   carry the same 44px tap height the chips do, without leaving the sentence. */
.sprint-search__down a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 2px;
  color: var(--sp-accent-ink);
  font-weight: 600;
  white-space: nowrap;
  text-underline-offset: 0.18em;
}
.sprint-search__sep { color: var(--ink-3); }
.sprint-search.pf-down .sprint-search__inner { display: block; }
.sprint-search.pf-down .sprint-searchbox,
.sprint-search.pf-down .sprint-search__trigger { display: none; }
.sprint-search.pf-down .sprint-search__down { display: block; }

/* --------------------------------------------------------------- controls */

.sprint-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.1rem;
  align-items: center;
  width: min(1184px, calc(100% - 2.5rem));
  margin: 0 auto 0.6rem;
}
.paper-chips { display: flex; flex-wrap: wrap; gap: 0.5rem; }
/* Filter chips are stamps, minus the stamp wobble. These are live controls
   whose fill swaps under the pointer, and a rotated chip flipping state
   mid-click reads as a glitch rather than as a hand. The stamp voice carries,
   the tilt does not. Same departure /study made, same reason. */
.paper-chips .chip {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 4px 14px;
  border: 2px solid var(--ink);
  border-radius: 4px 9px 5px 10px;
  background: var(--sp-card);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 0.9375rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums lining-nums;
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-weave),
    color var(--duration-fast) var(--ease-weave);
}
.paper-chips .chip:hover { background: var(--fill-cool); }
.paper-chips .chip.active {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--cloth-card);
}
.sprint-controls__r {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}
.sprint-filter {
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 0.9375rem;
  font-weight: 600;
}
#filter-text {
  min-width: 12rem;
  min-height: 44px;
  padding: 6px 12px;
  border: 2px solid var(--ink);
  border-radius: var(--radius-sm);
  background: var(--sp-card);
  color: var(--ink);
  font-family: var(--font-ui);
  /* 16px exactly: anything smaller makes iOS Safari zoom the page on focus. */
  font-size: 1rem;
}
.sprint-count,
.sprint-empty {
  width: min(1184px, calc(100% - 2.5rem));
  margin: 0 auto var(--space-8);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 0.9375rem;
  font-variant-numeric: tabular-nums lining-nums;
}
.sprint-empty {
  padding: var(--space-8) 0;
  font-size: 1.0625rem;
  text-align: center;
}

/* --------------------------------------------------------------- vitrines */

.sprint-papers { display: block; }
.vitrine {
  position: relative;
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(1.75rem, 5vw, 4rem);
  align-items: center;
  width: min(1184px, calc(100% - 2.5rem));
  margin-inline: auto;
  padding: clamp(2.25rem, 5vw, 3.5rem) 0;
  border-bottom: 2px solid var(--ink);
  scroll-margin-top: 150px;
  background-image: radial-gradient(58% 68% at var(--spot-x, 30%) 44%,
    color-mix(in srgb, var(--stage, var(--thread-teal)) 12%, transparent),
    transparent 64%);
}
.sprint-papers .vitrine:last-of-type { border-bottom: 0; }
.vitrine__text { position: relative; z-index: 1; max-width: 46rem; }
.vitrine__eyebrow {
  display: block;
  margin: 0 0 1rem;
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 0.875rem;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}
.vitrine__title {
  margin: 0 0 0.5rem;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: clamp(1.9rem, 4vw, 2.9rem);
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.06;
}
.vitrine__subtitle {
  margin: 0 0 1rem;
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 1rem;
  font-weight: 600;
}
.vitrine__promise {
  margin: 0 0 1.2rem;
  color: var(--sp-accent-ink);
  font-family: var(--font-display);
  font-size: clamp(1.2rem, 2.2vw, 1.4rem);
  font-style: italic;
  line-height: 1.4;
}
.vitrine__what {
  max-width: 62ch;
  margin: 0 0 1.6rem;
  color: var(--ink);
  font-size: 1.0625rem;
  line-height: 1.62;
}
.vitrine__cta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
}
.vitrine__author {
  margin: 1.1rem 0 0;
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 0.9375rem;
}
.vitrine__stage {
  display: flex;
  align-items: center;
  justify-content: center;
}
.book { position: relative; }
/* The transparent-film render carries no baked shadow; the offset solid drop
   is applied here so it tracks the theme. */
.book__render {
  display: block;
  width: auto;
  max-width: 100%;
  height: clamp(300px, 42vh, 470px);
  filter: drop-shadow(6px 8px 0 rgb(43 35 32 / 18%));
}

/* Hand-ruled hallmark: a matched top-and-bottom pair, never a side tab.
   Pages and Size run tabular so the four rows line up down the set. */
.hallmark {
  margin: 0 0 1.7rem;
  padding: 0.35rem 0;
  border-top: 2px solid var(--ink);
  border-bottom: 2px solid var(--ink);
}
.hallmark__row {
  display: flex;
  gap: 1rem;
  align-items: baseline;
  justify-content: space-between;
  padding: 0.6rem 0.1rem;
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 22%, transparent);
}
.hallmark__row:last-child { border-bottom: 0; }
.hallmark__k {
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 0.875rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.hallmark__v {
  color: var(--ink);
  font-family: var(--font-display);
  font-size: 1.0625rem;
  font-weight: 700;
  text-align: right;
}
.hallmark__v--num { font-variant-numeric: tabular-nums lining-nums; }

/* ------------------------------------------------------------------ shelf */

.sprint-shelf,
.sprint-print {
  width: min(1184px, calc(100% - 2.5rem));
  margin-inline: auto;
  padding: clamp(3rem, 6vw, 5rem) 0 0;
}
.sprint-shelf__head,
.sprint-print__head { max-width: 62ch; margin: 0 0 2rem; }
.sprint-shelf__title,
.sprint-print__title {
  margin: 0 0 0.7rem;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: clamp(1.8rem, 3.4vw, 2.6rem);
  font-weight: 700;
  line-height: 1.08;
}
.sprint-shelf__lede,
.sprint-print__lede {
  margin: 0;
  color: var(--ink);
  font-size: 1.0625rem;
}
.sprint-cluster { margin-bottom: clamp(2rem, 4vw, 3rem); }
.sprint-cluster__head {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1rem;
  align-items: baseline;
  justify-content: space-between;
  margin: 0 0 0.9rem;
  padding-bottom: 0.5rem;
  border-bottom: 2px solid var(--ink);
}
.sprint-cluster__name {
  color: var(--ink);
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 700;
}
.sprint-cluster__meta {
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 0.875rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums lining-nums;
}
.sprint-cluster__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.9rem;
}

/* Every tile is a white card pasted on the sheet: ink border carrying its own
   paper accent, uneven radius alternating down the grid, offset solid shadow.
   Zero rotation, this is a dense scanning surface. */
.shelf-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  overflow: hidden;
  padding: 1.05rem 1.15rem 1.1rem;
  border: 2px solid color-mix(in srgb, var(--tile-accent, var(--ink)) 34%,
    var(--ink));
  border-radius: var(--wonky-1);
  background: var(--sp-card);
  box-shadow: var(--shadow-soft);
  transition: transform var(--duration-ui) var(--ease-weave),
    border-radius var(--duration-ui) var(--ease-weave),
    box-shadow var(--duration-ui) var(--ease-weave);
}
.shelf-card:nth-child(even) { border-radius: var(--wonky-2); }
.shelf-card:hover {
  transform: translateY(-3px);
  border-radius: var(--wonky-3);
  box-shadow: var(--shadow-lift);
}
.shelf-card > *:not(.tile-deco) { position: relative; z-index: 1; }
.tile-deco {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  color: var(--tile-accent, var(--ink));
  opacity: 0.14;
  pointer-events: none;
}
.shelf-card:hover .tile-deco { opacity: 0.22; }
.shelf-card__n {
  display: inline-block;
  align-self: flex-start;
  padding: 4px 10px 3px;
  border: 2px solid color-mix(in srgb, var(--tile-accent, var(--ink)) 55%,
    var(--ink));
  border-radius: 4px 9px 5px 10px;
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 0.875rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  line-height: 1.2;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums lining-nums;
}
.shelf-card__title {
  flex: 1;
  margin: 0.15rem 0 0;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: 1.0625rem;
  font-weight: 700;
  line-height: 1.24;
}
.shelf-card__meta {
  display: flex;
  gap: 0.45rem;
  align-items: baseline;
  margin: 0;
  color: var(--ink-2);
  font-family: var(--font-ui);
  font-size: 0.875rem;
  font-variant-numeric: tabular-nums lining-nums;
}
.shelf-card__dot { color: var(--ink-3); }
.shelf-card__cta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.2rem 1.1rem;
  margin: 0.35rem 0 0;
}
.shelf-card__link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--sp-accent-ink);
  font-family: var(--font-ui);
  font-size: 0.9375rem;
  font-weight: 700;
  text-decoration: none;
}
.shelf-card__link:hover { text-decoration: underline; text-underline-offset: 0.2em; }
.shelf-card__link:active { transform: scale(.97); }

/* ------------------------------------------------------------------- print */

/* The printed set borrows the shelf outright: same head, same cluster rules,
   same white tile, same accents. Only three things are new, because only three
   things have no precedent on this page. The two advisory bands are the rights
   card at card width rather than measure width, so a reader can hold the NND
   route and the do-it-yourself route side by side above the files. The brief is
   a list because it is read standing at a print counter. The close line is the
   Trijog mark, set as one quiet ui line, never a banner.

   Print tiles carry no data-item, so the reader-lane filter and every 44-count
   pass straight over them; the group-hiding pass skips any group with nothing
   filterable inside it. Rotation stays at 0deg here as everywhere else on this
   page that a reader has to scan or click. */

.sprint-print__head p + p { margin-top: 0.9rem; }
.print-bands {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
  margin: 0 0 clamp(2rem, 4vw, 3rem);
}
.print-band {
  padding: clamp(1.15rem, 2.6vw, 1.6rem);
  border: 2px solid var(--ink);
  border-radius: var(--radius-card);
  background: var(--sp-card);
}
.print-band__title {
  margin: 0 0 0.6rem;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 700;
  line-height: 1.16;
}
.print-band p {
  margin: 0;
  color: var(--ink-2);
  font-size: 1rem;
}
.print-brief {
  margin: 0.7rem 0 0;
  padding-inline-start: 1.15rem;
  color: var(--ink-2);
  font-size: 1rem;
}
.print-brief li { margin: 0 0 0.45rem; }
.print-brief li:last-child { margin-bottom: 0; }
.print-close {
  margin: 0.3rem 0 0;
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 0.9375rem;
  font-weight: 600;
}

/* ------------------------------------------------------------- cross band */

/* The /100 route (added 2026-08-29). Deliberately the quietest block on the
   page: a white card at measure width carrying one kicker, one sentence and
   one link. It is not a second hero and it is not a shelf, because it hands
   over nothing to download; it points at another hub. 0deg, like every other
   surface here a reader clicks. */
.sprint-cross {
  width: min(1184px, calc(100% - 2.5rem));
  margin-inline: auto;
  padding: clamp(2.5rem, 5vw, 3.5rem) 0 0;
}
.sprint-cross__inner {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.4rem;
  align-items: baseline;
  justify-content: space-between;
  padding: clamp(1.15rem, 2.6vw, 1.5rem);
  border: 2px solid var(--ink);
  border-radius: var(--radius-card);
  background: var(--sp-card);
}
.sprint-cross__kick {
  flex: 0 0 100%;
  margin: 0 0 0.35rem;
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 0.875rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.sprint-cross__text {
  flex: 1 1 22rem;
  max-width: 58ch;
  margin: 0;
  color: var(--ink);
  font-size: 1.0625rem;
}
.sprint-cross__link {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  min-height: 44px;
  color: var(--sp-accent-ink);
  font-family: var(--font-ui);
  font-size: 0.9375rem;
  font-weight: 700;
  white-space: nowrap;
}

/* ------------------------------------------------------------------ rights */

/* Licence text at 0deg always, zero annotation, zero decoration. Disciplined
   on the page's own paper rather than switched to the quiet surface, which is
   reserved for the zones DESIGN-V4 part 7 names explicitly. */
.sprint-rights {
  width: min(1184px, calc(100% - 2.5rem));
  margin: 0 auto;
  padding: clamp(3rem, 6vw, 5rem) 0 clamp(3.5rem, 7vw, 6rem);
}
.sprint-rights__inner {
  max-width: 68ch;
  margin-inline: auto;
  padding: clamp(1.5rem, 4vw, 2.5rem);
  border: 2px solid var(--ink);
  border-radius: var(--radius-card);
  background: var(--sp-card);
  transform: none;
}
.sprint-rights__title {
  margin: 0 0 1rem;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 3vw, 2.2rem);
  font-weight: 700;
  line-height: 1.12;
}
.sprint-rights p {
  margin: 0 0 1rem;
  color: var(--ink-2);
  font-size: 1.0625rem;
  line-height: 1.62;
}
.sprint-rights p:last-child { margin-bottom: 0; }
.sprint-rights__by { color: var(--ink) !important; font-weight: 600; }
.sprint-rights .sprint-eyebrow { color: var(--ink-2); }

/* ------------------------------------------------------------- responsive */

@media (min-width: 600px) {
  .sprint-cluster__grid { grid-template-columns: repeat(2, 1fr); }
}
/* The nav pill gains its CTA button at 768px and grows from 76.77px to
   95.16px tall, so the sticky offset has to step at 768 and not at 900. The
   900px version left the card 19.2px behind the pill for the whole
   768-899 range. */
@media (min-width: 768px) {
  .sprint-search { top: 108px !important; }
}
@media (min-width: 900px) {
  .sprint-cluster__grid { grid-template-columns: repeat(3, 1fr); }
  /* The two print routes sit side by side once there is room to compare them
     without scrolling one out of sight. */
  .print-bands { grid-template-columns: repeat(2, 1fr); }
  .sprint-hero__grid {
    grid-template-columns: minmax(0, 7fr) minmax(16rem, 5fr);
    align-items: center;
  }
  .vitrine { grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); }
  .vitrine:nth-of-type(even) { --spot-x: 70%; }
  .vitrine:nth-of-type(even) .vitrine__stage { order: 1; }
  .vitrine:nth-of-type(even) .vitrine__text { order: 2; }
}
@media (min-width: 1200px) {
  .sprint-cluster__grid { grid-template-columns: repeat(4, 1fr); }
}
@media (max-width: 599px) {
  .sprint-search__inner { padding: 0.7rem; }
  /* The field takes the first row on its own; the trigger takes the second
     one whole rather than sitting as a 153px stub beside a field squeezed to
     184px, which is what it did. */
  .sprint-search__trigger { flex: 1 1 100%; }
  /* Once it owns the row it is a full-width action, so the label centres
     rather than hugging the left edge of an otherwise empty button. */
  .sprint-search__trigger .pf-trigger-btn { justify-content: center !important; }
  .sprint-search__trigger .pf-trigger-text { flex: 0 0 auto !important; }
  /* Static on a phone. Two rows of controls is 133px, and 133px of card
     pinned under a 77px nav pill would hold a third of a 667px screen for the
     whole page. Sticky buys least where it costs most, so it is spent on the
     widths that can afford it. Nothing is lost that a reader has today: the
     component's reset has been stripping position from this element since it
     shipped, so the band has never actually stuck anywhere. */
  .sprint-search { position: static !important; }
  /* Tighter chips on a phone so a filter row is a row, not a stack. The 44px
     tap height and the 14px label floor both survive; only the side padding
     and the tracking give way. */
  .sprint-controls { gap: 0.6rem; }
  .paper-chips { gap: 0.4rem; }
  .paper-chips .chip {
    padding: 4px 10px;
    font-size: 0.875rem;
    letter-spacing: 0.05em;
  }
  .sprint-controls__r { width: 100%; }
  #filter-text { flex: 1; min-width: 0; }
  .book__render { height: clamp(260px, 46vw, 340px); }
  .sprint-hero { padding-top: 6.5rem; }
}

/* ------------------------------------------------------------ motion floor */

@media (prefers-reduced-motion: reduce) {
  .shelf-card,
  .paper-chips .chip { transition: none; }
  .shelf-card:hover { transform: none; }
  .shelf-card__link:active { transform: none; }
  /* The trigger's hover fade is the only transition the band adds. The
     component ships its own border-color and box-shadow fades on the same
     button, so both are silenced here. Nothing in the band moves on its own
     in either case. */
  .sprint-search__trigger .pf-trigger-btn,
  .sprint-searchbox .pf-searchbox-input { transition: none !important; }
}
