/* ================================================================
   COMPONENT: CASE STUDY PAGE (draft)
   Long-form case study scaffolding. HIERARCHY SYSTEM:

   Tier 1 (display type)  — cover title, the mid-page thesis
   Tier 2 (chapter scale) — the three chapter headings (+ act-break
                            spacing before their sections)
   Tier 3 (quiet)         — connective beats (brief / outcomes /
                            reflection): mono eyebrow in the rail,
                            medium serif heading at most

   ONE reading grid everywhere: a left rail (`--case-rail`) + a single
   flowing content column (`--case-column`) — reading order is always
   rail → lede → support, top to bottom. Full-width breakouts reclaim
   the rail and span `--case-canvas`; the container that holds them is
   `--case-container`, which is the canvas plus its inset and nothing
   else. Extends the homepage system (warm paper, crease dividers,
   unfold reveals); byakuroku appears only as the JP annotation accent.
   ================================================================ */

/* The page is warm paper end to end (matters for the paper-open
   view transition — the backdrop behind the unfolding sheet). */
.case-page {
  background: var(--surface-page);

  /* ONE CORNER FOR EVERY PICTURE ON A CASE PAGE.
     Screens sit inside mats, mounts and stepper panels, and each of those
     used to name `--radius-sm` (8) for itself — three places to change, and
     an 8px corner reads mean against a mat that has to be at least 28 to
     stay concentric. This is the single value they all take; the mats then
     DERIVE their own radius from it plus their inset, so enlarging the
     picture's corner enlarges the card's corner with it and the two arcs
     keep the same centre. Change it here, nowhere else. */
  --case-shot-radius: var(--radius-lg);   /* 16 */
}

/* ── Cover arrival entrance (GSAP-owned) ────────────────────────── */
/* `[data-entrance]` elements are NOT in the scroll-reveal system — they are
   animated by `case-entrance.js` after the page fold finishes. They are
   visible by default (progressive enhancement: no JS / no GSAP = readable
   page). `html.entrance-armed` is set synchronously by the blocking head
   script and removed by the module the instant it has captured the
   timeline's from-state, so the cover never flashes in at full opacity
   first. A 3s failsafe in the head script also clears it if GSAP's CDN
   fails, so copy can never be permanently hidden. */
html.entrance-armed [data-entrance] {
  opacity: 0;
}

/* THE AIR LADDER — how a case page separates its parts.
   Case pages run denser than the homepage (long-form reading wants a
   continuous flow, not gallery air between every block), but density is
   not the same as flatness. There are exactly two levels of section break
   and they are told apart by AIR first, then by the mark:

     internal gap   48px  (`.case-figure` margins — the largest in-section gap)
     BEAT break    128px  air, no rule, tertiary eyebrow
     ACT break     160px  air + the heavy crease + accent-ink eyebrow

   The fine rule used to carry the beat break, at 96px air — the same air an
   act got, with only ~1px of extra silhouette weight telling them apart. At
   reading distance that difference does not exist, so the page read as one
   undifferentiated run of ruled sections. The crease is now the ACT's mark
   and nothing else's; a beat is separated by paper. Do not reintroduce a
   second rule weight to mark a beat — if a beat needs more separation, it
   needs more air. See DESIGN.md → Dividers → The separation ladder. */
.case-page {
  --case-air-beat:  80px;   /* + the previous section's 48px bottom = 128 */
  --case-air-act:  112px;   /* + the previous section's 48px bottom = 160 */
}

.case-page .section {
  padding-top: var(--case-air-beat);
  padding-bottom: var(--sp-12);
}

/* An act is any section that opens with the crease. Structural, not a class
   to keep in sync: the air and the mark can never disagree, because the mark
   is what selects the air. Where `:has()` is unsupported an act simply keeps
   the beat's air — it still has the crease, the accent eyebrow, and the
   chapter-size heading, so the level survives. */
.case-page .section:has(> .section__divider) {
  padding-top: var(--case-air-act);
}

/* Colored italic highlight in headings — the footer wordmark's move
   (`.footer__cta-em`): PP Editorial New Ultralight italic in accent
   teal, one accent word per landmark/chapter title. */
.case-em {
  font-weight: 200;
  font-style: italic;
  /* Large serif on paper: AA wants 3:1 and --accent lands at 2.7:1.
     --accent-dk (3.7:1) is the same hue at ink weight. Matches
     `.footer__cta-em`, which this device is modelled on. */
  color: var(--accent-dk);
}

/* ONE separator, one meaning. The heavy crease (.section__divider) is the
   house-mark and on a case page it marks an ACT — the reframe and the three
   chapters, the page's structural spine. There is deliberately no second,
   lighter section rule: `.section__divider--fine` was removed from every
   case page because at ~1px against the crease's ~2.3px the two weights
   were indistinguishable while the air on both sides was identical. */
.case .section__divider {
  margin-bottom: calc(var(--sp-8) - var(--crease-inset));
}

/* -- Cover ------------------------------------------------------- */
/* Top padding is 120px rather than the old 160px because the cover now
   opens on a media plate: the image supplies the air the padding used to,
   and the title still clears the fold. */
.case-cover {
  padding: 120px var(--gutter-inner) var(--sp-12);
  max-width: var(--case-container);
  margin: 0 auto;
}

/* -- Cover media — the shared element's landing plate -------------- */
/* This is where the clicked project card's image comes to rest (see
   `css/components/flyer.css`). Its geometry deliberately mirrors
   `.project-card__media`: same radius, same squircle, same overflow, same
   placeholder fill — so the flight is a change of size and position only,
   never a change of material.

   It carries its own image on a direct load too, so the plate is a real
   part of the cover, not a transition artifact. */
/* Spans the CANVAS, not the container — so its right edge lands on the same
   line as every body block below it. */
/* THE HEIGHT IS A RATIO, NOT A NUMBER — and the ratio is ~1.72:1.
   At 420 this plate was 948×420 = **2.26:1**, and `object-fit: cover` against
   a 1.6 source showed only the middle 1418 source px. Pindo's phone is 1608
   source px tall, so the cover sliced 85px off its top and 105px off its
   bottom — the status bar and the 立即跟团 button. 550 opens the band to 1857:
   the whole device stands inside it with 43px of air above and 30px of table
   below (CSS px), which is 5.5% of the plate.

   ~1.72 IS NOT ARBITRARY — IT IS THE ONE STEP THAT WAS ALREADY RIGHT. The
   ≤768 plate has always been 343×200, and it was the only one of the six card
   and cover steps that never clipped the phone. Every other step held a fixed
   height against a width that follows the grid, so the shape drifted: 2.26
   here, 2.13 at ≤1024, 1.91 and **2.93** on the cards. All six now sit at
   1.70–1.76 and give the phone the same ~5.5% of table under it. If a new
   plate needs a height, divide its width by 1.72 — do not pick one.

   IT ALSO ALMOST DELETES THE FLIGHT'S COUNTER-SCALE. The card is 726×420
   (1.729) and this plate is 948×550 (1.724) — 0.3% apart, where 1.911 vs
   2.257 was 18%. The two-layer counter-scale in `flyer.css` still runs, but it
   is now correcting almost nothing, so the shared element reads as one object
   changing size rather than one object also changing shape. Move one of these
   two heights and you must re-check the other. */
.case-cover__media {
  position: relative;
  width: 100%;
  max-width: var(--case-canvas);
  height: 550px;
  margin-bottom: var(--sp-10);
  border-radius: var(--radius-card);
  corner-shape: squircle;
  overflow: hidden;
  /* On-stock plate — keep in sync with .project-card__media and .pt-flyer.
     The flyer lands here, so a mismatch pops at the end of the flight. */
  background-color: var(--paper-tint-3);
}

.case-cover__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center center;
}

/* Held blank while a flyer is inbound — the flyer IS the image until it
   lands, and two copies of it would be visible at once. Set synchronously
   by the head script before first paint, cleared when the flight ends. */
html.pt-flight .case-cover__media {
  opacity: 0;
}

/* Same device as `.case-eyebrow` — an accent-ink label above a title — so it
   carries the same style, and the two move together. Both briefly left the
   mono register for the body voice and both are back in it; see
   `.case-eyebrow` for why the argument that moved them no longer holds. */
.case-cover__kicker {
  font-family: var(--font-mono);
  font-size: var(--type-body2-size);
  font-weight: var(--weight-medium);   /* moves with `.case-eyebrow` — same device */
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-accent-ink);
  margin-bottom: var(--sp-6);
}

/* ONE MEASURE FOR THE COVER'S TEXT.
   The cover used to stack four different widths at the same left edge:
   media 1040 → title 726 (`14ch`) → thesis 560 → meta 1040. It opened full
   width, pinched twice by amounts that related to nothing else on the page,
   then snapped back — and none of the right edges lined up. Title and thesis
   now share `--case-column`, the same measure the body grid uses below, so
   the cover reads as one text block bracketed by two full-width elements
   (the media plate and the meta strip) instead of four unrelated widths.

   `14ch` also scaled with the font, so the title's measure changed at every
   breakpoint independently of the grid — which is why nothing ever aligned. */
.case-cover__title {
  font-family: var(--font-display);
  font-weight: var(--weight-regular);
  font-size: var(--type-h1-size);   /* the site's top scale (hero h1) */
  line-height: 1.05;
  color: var(--text-primary);
  max-width: var(--case-column);
  margin-bottom: var(--sp-8);
}

.case-cover__thesis {
  font-family: var(--font-body);
  font-weight: var(--weight-book);
  font-size: 19px;
  line-height: 1.55;
  color: var(--text-body);
  max-width: var(--case-column);
  margin-bottom: var(--sp-8);
}

/* Meta strip — role / scope / team / status */
/* The line closing the cover is DRAWN, like every other line on the site.
   It was a 1px `--border-light` rule — cool grey on warm paper, and the only
   ruled edge left on a case page. Same silhouette as the section crease at
   the fine box, so it joins the family instead of sitting outside it. */
.case-meta {
  position: relative;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--sp-6);
  max-width: var(--case-canvas);   /* same right edge as the media + body */
  padding-top: var(--sp-6);
}

.case-meta::before {
  content: "";
  position: absolute;
  top: calc(var(--rule-box-fine) / -2);
  left: 0;
  right: 0;
  height: var(--rule-box-fine);
  background-color: var(--divider-content);
  -webkit-mask: var(--rule-silhouette) no-repeat 50% 50% / 100% 100%;
          mask: var(--rule-silhouette) no-repeat 50% 50% / 100% 100%;
}

.case-meta__item dt {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin-bottom: var(--sp-1);
}

.case-meta__item dd {
  font-size: var(--type-body1-size);
  font-weight: var(--weight-book);
  color: var(--text-primary);
}

/* -- Section scaffolding ----------------------------------------- */
.case-section {
  max-width: var(--case-container);
  margin: 0 auto;
}

/* `.case-section--chapter` carries no spacing of its own any more — act air
   comes from `:has(> .section__divider)` above, which also covers the
   reframe (an act that never had the chapter class and so sat on beat air).
   The class is kept as a semantic hook only. */

/* -- The reading grid: rail + single content column --------------- */
.case-grid {
  display: grid;
  grid-template-columns: var(--case-rail) minmax(0, var(--case-column));
  gap: var(--sp-5) var(--sp-12);
}

/* consecutive grids (around a breakout) keep the column alignment */
.case-grid + .case-grid {
  margin-top: var(--sp-8);
}

/* THE RAIL IS FLUSH-LEFT, ON THE CANVAS AXIS.
   It was briefly set flush-right, to hang the note off the text block the way
   a shoulder note sits in a book — the argument being that a short label alone
   at x=0 left ~140px of dead paper between it and the column it belongs to.
   That argument was made when the rail was filled on EVERY section; with the
   label now on chapters only, the reverse is true. Flush-left puts it on the
   canvas's left axis — the same line as the crease's left end, the figure
   mats, and the media plates — so the label belongs to the SHEET's structure
   rather than floating beside the paragraph. One axis, which is the rule the
   case grid is built on. Do not right-align this again. */
.case-rail {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  text-align: left;
  gap: var(--sp-3);
}

/* ── THE VERTICAL CREASE — the sheet folded the other way ────────────
   The house-mark has only ever run ACROSS the page, marking one act from the
   next. This runs WITH it, down the gutter between the label margin and the
   reading column, and it does a different job: not separation, but structure
   — it states where the column begins, for the length of the section.

   It sits in the MIDDLE of the 48px gap, touching neither the label nor the
   text. Full-width blocks pass IN FRONT of it: each carries an opaque mat, so
   the crease disappears behind a plate and picks up again in the paper
   between, the way a fold runs under a card laid on the sheet. That is what
   stopped `--wide` blocks reading as misaligned — the line makes the column
   edge explicit, so leaving it is visibly a choice.

   WHY A GRADIENT AND NOT `--rule-silhouette`. The house silhouette is a
   300x12 raster whose taper runs along its WIDTH; stretched into a tall
   narrow box that taper would run across the 2px instead of down the length,
   and CSS cannot rotate it without knowing each section's height. Re-cutting
   it as a vertical SVG re-opens the staircase this project already fought
   (tokens.css → the drawn rule). So the taper is a mask gradient: at 1–2px a
   fade and a taper are indistinguishable, and the pressure profile that
   carries the hand horizontally is invisible at this width anyway. Warm ink
   plus a catch-light keeps it the same material.

   Below 1025 the rail collapses to a label row and there is no gutter to
   mark, so the element is inert — said out loud rather than left unstyled. */
.case-crease { display: none; }

@media (min-width: 1025px) {
  /* PAINT ORDER IS THE WHOLE TRICK, AND IT IS NOT AUTOMATIC.
     An absolutely-positioned element paints in the POSITIONED layer, which
     sits ABOVE ordinary in-flow content — so the first build drew the crease
     straight over the plates it was meant to run beneath, captions included.
     Opacity was never the problem; stacking was. `isolation: isolate` makes
     the section its own stacking context and `z-index: -1` then places the
     crease above the section's (transparent) background but below every
     in-flow child. Remove either and the line comes back through the artwork.
     The plates are all `position: static` / `z-index: auto` — give one a
     `z-index` and it re-enters the positioned layer, above the crease again. */
  .case-page .case-section {
    position: relative;
    isolation: isolate;
  }

  /* A REAL ELEMENT, AND THE SECTION'S FIRST CHILD — not a `::before`.
     It is the fold that opens each section, so it exists in the markup where
     it happens; and **GSAP cannot animate a pseudo-element** (CLAUDE.md), so
     the scroll-pressed draw in `js/modules/case-crease.js` is only possible
     on a real node. Being first costs nothing in flow — it is absolutely
     positioned — and it is what lets the crease be the first thing a section
     renders. `.case-next-wrap` gets no crease element at all: a single
     full-width control leaving the page, with no rail beside it and no column
     to announce. The cover is excluded the same way. */
  .case-page .case-crease {
    display: block;
    position: absolute;
    z-index: -1;
    top: var(--sp-8);
    bottom: var(--sp-8);
    left: calc(var(--gutter-inner) + var(--case-rail) + var(--sp-12) / 2);
    width: 2px;
    background:
      linear-gradient(to right, var(--crease-highlight) 0 1px, var(--fold-line) 1px 2px);
    -webkit-mask: linear-gradient(to bottom, transparent 0, #000 7%, #000 93%, transparent 100%);
            mask: linear-gradient(to bottom, transparent 0, #000 7%, #000 93%, transparent 100%);
    transform-origin: 50% 0;   /* it is pressed downward, from the top */
    pointer-events: none;
  }
}

/* IN THE LABEL REGISTER — mono, uppercase, `--tracking-label`, accent ink.
   This briefly left the register for the body voice, on the argument that
   "14px mono UPPERCASE tracked, repeated NINE times down a left margin, is
   the most template-worn device on the web." The count was load-bearing in
   that argument, and it no longer holds: the eighteen labels that only
   restated their headings are gone, so this appears **three or four times a
   page, on chapters only** — while every other label on the sheet
   (`.case-meta dt`, `.case-metric__label`, `.chapter-nav__label`,
   `.case-cover__kicker`) is mono. Keeping one label in a private voice bought
   less than the whole register costs to fracture.

   So the fix for "it looks generated" was the DELETION, not the typeface.
   Restated another way: the register is fine, filling it on every section was
   not. If this ever starts reading as template again, cut labels — do not
   restyle the survivors.

   INK IS `--text-secondary`, AND THE TEAL IT REPLACED WAS A RULE VIOLATION.
   This carried `--text-accent-ink` and the comment here called the result
   "accepted: one is the site's accent, one is the product's own word." It was
   not acceptable and it was not what the system says: *don't re-add teal to
   the margin — `.case-cover__kicker` is the page's only accent label.* On
   Pindo and Japan the eyebrow stacks directly above a `.cn-label` /
   `.jp-label`, so the rail put the SITE's accent (teal) immediately over the
   PRODUCT's accent (`--pindo-rose`) — two unrelated brand colours, 8px apart,
   in a 220px margin. Different sizes and shapes do not rescue that.

   THE RAIL ON A CASE PAGE BELONGS TO THE PRODUCT, NOT TO THE PORTFOLIO'S
   CHROME. The product's own word keeps its colour; the site's accent steps
   out. Generalises past this component: when two accent systems meet, the one
   describing the subject wins and the one describing the container gives way.

   `--text-secondary` (#5f5952, 6.46:1) rather than tertiary because the swap
   should cost the label nothing but hue — teal-ink read 5.91:1 on paper, so
   matching the LUMINANCE keeps the eyebrow exactly as present as it was while
   removing the clash. Tertiary (5.14:1) is the caption tier (`.viz-label`) and
   would have quietened a wayfinding label as a side effect.

   IT NO LONGER MATCHES `.exp-block__label` ON THE HOMEPAGE, which keeps the
   accent. That divergence is deliberate: the homepage rank marker has no
   product colour beneath it to collide with, and rule 32 governs the case
   margin only. Everything else about the two still agrees — mono, 14px, 24px
   leading, `--tracking-label`, uppercase, `--weight-medium`. If the homepage
   ever grows a product-coloured chip in that column, revisit both together. */
.case-eyebrow {
  font-family: var(--font-mono);
  font-size: var(--type-body2-size);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-secondary);
  /* CAP-LINE ALIGNMENT — the label starts on the same optical line as the
     chapter heading beside it, not on the same box top. ZERO is the measured
     answer here, and it is not the 6px this rule carried before: that 6px was
     tuned when the rail sat beside a LEDE, and the label now only ever sits
     beside a 52px chapter title. This rule also states no `line-height`, so
     it inherits body's ABSOLUTE 24px — a 10px half-leading on a 14px face,
     which already pushes the cap line down almost exactly the right amount.
     Measured residual at 1440: 0.44px. If the type ladder, this face, or that
     inherited leading changes, re-measure — DESIGN.md → Case Study Pages →
     The rail. */
  padding-top: 0;
}

/* AN OCCUPIED MARGIN IS THE LEVEL SIGNAL — `.case-eyebrow--act` is GONE.
   The eyebrow used to appear on every section and carry the level in its ink:
   accent on an act, tertiary on a beat. Two problems with that. It meant the
   margin had to be filled on every section, and five labels per page existed
   only to be filled in — "The bet", "The reframe", "Craft", "Where it
   stands", "Reflection" — each one restating a heading that already said it
   ("Reflection" over *What building it taught*). Announcing your own
   document's structure is what a generated outline does; a written essay just
   concludes. And it put teal in the margin three or four times a page, right
   above the `.cn-label` / `.jp-label` state words, so the rail carried two
   colours stacked.

   Both are fixed by the same cut. The eyebrow now exists on ONE kind of
   section — a chapter — where it does a job the heading cannot: it names
   WHICH PART OF THE PRODUCT this is ("Progress bar", "Smart intake") while
   the heading names the insight ("The bar that finally moved"). It is also
   the wayfinding link back to `.chapter-nav`, which lists exactly these.
   Everywhere else the margin is empty.

   So the level is stated by PRESENCE, not by ink — occupied margin = act,
   empty margin = beat — which is the same principle the dividers follow
   (a mark's presence or absence, never two weights of one mark), and it
   frees the ink to stay quiet. Do not reintroduce an ink variant, and do not
   add an eyebrow to a beat: if a beat has something worth putting in the
   margin, it is not a label, and it needs writing.
   See DESIGN.md → Case Study Pages → The rail. */

/* THE TWO CJK CHIPS — `.jp-label` and `.cn-label`. One component, two page
   accents, and now ONE TYPE REGISTER: sans on both.

   BOTH WERE SERIF (Mincho / Songti) AND BOTH ARE NOW SANS. The split that
   briefly stood here — serif for Japan because the case is editorial about a
   market, sans for Pindo because 拼团中 is lifted off a running app — argued
   itself into one component speaking in two voices, which is the thing the
   label register is supposed to prevent. Two measured reasons decide it:

   1. THE CHIP STACKS UNDER A SANS LATIN EYEBROW. In the rail, `.case-eyebrow`
      (`--font-body`, a grotesque) sits directly above the chip. A CJK book
      face underneath it put two type registers in a 220px margin — exactly
      the stacking problem that took teal out of the eyebrow.
   2. A BOOK FACE IS NOT A 14px FACE. Mincho and Songti carry hairline
      horizontals and true serifed terminals; at `--type-body2-size` in a
      24px chip those strokes fall below reliable rasterisation and the
      glyph reads as noise before it reads as a word. Both families are cut
      for long-form print at reading sizes, which a chip never is.

   THE STACKS STAY SEPARATE, THOUGH — DO NOT MERGE THEM. `.jp-label` is
   `lang="ja"` and `.cn-label` is `lang="zh"`, and the same code point is
   drawn differently in the two locales. Feeding Japanese through a Noto
   Sans SC or PingFang fallback renders kanji in Chinese glyph forms, which
   is a localisation error on a page whose subject IS localisation. Each
   chip leads with the platform's own regional face (Hiragino Sans /
   PingFang SC) and falls back within its own locale.

   Everything else about the chips is unchanged: ground, ink, 24px min-height,
   `--type-body2-size` and the 0.12em tracking. Only the register moved.
   See DESIGN.md → Case-page components. */

/* JP annotation label — the page-local byakuroku accent. Meaningful,
   not decorative: localization is the subject. Never interactive. */
.jp-label {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  padding: 0 var(--sp-3);
  border-radius: var(--radius-sm);
  corner-shape: squircle;
  background-color: color-mix(in srgb, var(--byakuroku) 32%, var(--paper));
  color: var(--teal-hero);
  font-family: "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Yu Gothic",
               "Noto Sans JP", "Source Han Sans JP", sans-serif;
  font-size: var(--type-body2-size);
  letter-spacing: 0.12em;
}

/* CN state label — the Pindo case's page-local accent, mirroring
   .jp-label. Carries the Chinese pool-state name (拼团中 / 入库 /
   设计系统) in Pindo's own red. The subject is a Chinese-language
   product, so the label is meaningful, never decorative.

   THE MIX RATIOS ARE THE MUTING, AND THAT IS THE ONLY PLACE IT BELONGS.
   `--pindo-rose` is now the true brand red (#cb1d45), so a chip cannot be
   brand-on-brand-tint: at a 15% ground the pure brand red measures **4.46:1
   and fails AA**. The ink is therefore the brand walked down toward
   `--gray-900` — which costs almost no chroma (0.72 against the brand's
   0.75, hue identical at 346°) and buys a lot of contrast.

   82%, NOT 88%. The ratio inherited from the muted era was 88%, which lands
   at 4.75:1 on the chip — AA by a quarter of a point. 82% is 5.19:1 and is
   the same colour to the eye (0.72 vs 0.73 saturation). A step that is
   indistinguishable and measurably safer is not a trade-off, it is just
   better; the reverse of that argument is what took the token itself to the
   full brand red in `tokens.css`.

   It stays a step BELOW the page accent on purpose — 6.62:1 on paper against
   the accent's 5.16:1. The chip lives in the rail, and the rail is a margin:
   it may carry the brand's hue, never the brand's loudness.

   PingFang SC leads the stack because it is what Pindo actually renders in
   on iOS — the rail quotes the product in the product's own typeface. See
   the shared note above `.jp-label` for why both chips are sans, and why
   the two locale stacks must not be merged. */
.cn-label {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  padding: 0 var(--sp-3);
  border-radius: var(--radius-sm);
  corner-shape: squircle;
  background-color: color-mix(in srgb, var(--pindo-rose) 15%, var(--paper));
  color: color-mix(in srgb, var(--pindo-rose) 82%, var(--gray-900));
  font-family: "PingFang SC", "Hiragino Sans GB", "Noto Sans SC",
               "Source Han Sans SC", "Microsoft YaHei", sans-serif;
  font-size: var(--type-body2-size);
  letter-spacing: 0.12em;
}

/* STRICT GRID (all case pages).  Every block shares ONE content axis: the
   same left edge as the body text (rail 220px + 48px gap = 268px in from the
   section). The rail stays a label gutter; nothing juts left of the
   prose. Media plates and text share the reading column width (680px)
   so left AND right edges line up — a single, calm vertical spine.
   Desktop only; ≤1024px the grid already collapses to one column. */
@media (min-width: 1025px) {
  .case-page .case-thesis,
  .case-page .case-stats,
  .case-page .fold-compare,
  .case-page .course-stepper,
  .case-page .case-figure {
    margin-left: calc(var(--case-rail) + var(--sp-12));   /* content-column offset */
    margin-right: 0;
  }
  .case-page .case-stats,
  .case-page .fold-compare,
  .case-page .course-stepper,
  .case-page .case-figure {
    max-width: var(--case-column);   /* match the reading column exactly */
  }
}

/* The same spine below 1025. The rail collapses, so the canvas IS the reading
   column — but the SECTION is still as wide as the container, and without
   this the plates filled it (761px) while the text column stayed capped at
   680, putting the same right-edge overhang back at tablet that the cover had
   at desktop. Everything spans the canvas; the canvas is whatever the grid
   actually spans at that breakpoint. */
@media (max-width: 1024px) {
  :root { --case-canvas: var(--case-column); }

  .case-page .case-stats,
  .case-page .case-metrics,
  .case-page .fold-compare,
  .case-page .course-stepper,
  .case-page .case-figure,
  .case-page .case-options,
  .case-page .case-cover__media,
  .case-page .case-meta {
    max-width: var(--case-canvas);
  }
}

/* TYPE HIERARCHY (all case pages).  The shared desktop scale sets h1 72 / h2 64
   — only 8px apart — so the tier-1 landmarks (cover title, mid-page thesis) and
   the tier-2 chapter headings read at nearly the same size, and the page looks
   flat. Widen the steps on the case page: landmarks noticeably bigger, chapters
   a clear tier down, beats quieter still, and the lede a real step above body.
   Slight negative tracking sharpens the large serif. Case-scoped so the
   homepage keeps the shared tokens.

   The ladder is now token-driven and UNCONDITIONAL — it applies at every
   width, and the breakpoints only re-point the tokens (below). Previously
   these rules lived inside `@media (min-width: 1025px)` with a second set at
   `≤768px` and nothing in between, so at 769–1024px the page fell back to
   the shared scale and tiers collided: the beat heading and the
   next-project card both rendered 32px, the pull quote and the component
   h3s both 24px, and the Pindo metric numerals matched the cover title
   exactly. Never scope this ladder to a range again. */
.case-page .case-cover__title {
  font-size: var(--type-case-landmark);
  line-height: var(--type-case-landmark-line);
  letter-spacing: -0.015em;
}
.case-page .case-thesis {
  font-size: var(--type-case-thesis);
  line-height: var(--type-case-thesis-line);
  letter-spacing: -0.015em;
}
.case-page .case-chapter__title {
  font-size: var(--type-case-chapter);
  line-height: var(--type-case-chapter-line);
  letter-spacing: -0.01em;
}
.case-page .case-beat__title {
  font-size: var(--type-case-beat);
}
.case-page .case-cover__thesis,
.case-page .case-body .case-lede {
  font-size: var(--type-case-lede);
}
.case-page .case-quote p {
  font-size: var(--type-case-quote);
  /* A pull quote is display type, not prose — it wants even line lengths
     rather than just a protected last line, so it takes `balance` over the
     `pretty` it would inherit as a paragraph (reset.css). */
  text-wrap: balance;
}

/* THE MIDDLE RUNG — the band that was missing entirely. Ratios track the
   desktop ladder (1.68× landmark→chapter, 1.36× chapter→beat) so the
   hierarchy reads the same on a tablet as it does on a desktop. */
@media (max-width: 1024px) {
  :root {
    --type-case-landmark:  64px;
    --type-case-thesis:    48px;
    --type-case-chapter:   38px;
    --type-case-beat:      28px;
    --type-case-quote:     23px;
    --type-case-component: 21px;
    --type-case-lede:      19px;
    --type-case-numeral:   34px;
  }
}

@media (max-width: 768px) {
  :root {
    --type-case-landmark:  44px;
    --type-case-landmark-line: 1.05;
    --type-case-thesis:    38px;
    --type-case-thesis-line: 1.12;
    --type-case-chapter:   33px;
    --type-case-chapter-line: 1.1;
    --type-case-beat:      25px;
    --type-case-quote:     21px;
    --type-case-component: 19px;
    --type-case-lede:      17px;
    --type-case-numeral:   30px;
  }
}

/* Content column: single flow — lede first, support after */
.case-body p {
  font-size: var(--type-body1-size);
  font-weight: var(--weight-book);
  line-height: 1.65;
  color: var(--text-body);
}

.case-body p + p {
  margin-top: var(--sp-4);
}

/* INLINE CODE — an endpoint path or a literal, named inside a sentence.
   The label register already owns uppercase mono; this is the lowercase
   one, so it takes the mono family WITHOUT the tracking that marks a
   label (`--tracking-label` would turn a path into a heading). Sized at
   0.92em rather than a fixed px so it tracks whatever tier hosts it —
   body copy today, a lede if one ever needs it. No box, no tint: a
   surface here would read as a fourth plate idiom on a page already at
   its plate ceiling (DESIGN.md → Case Study Pages → Watch the plate
   count). The family alone is enough to say "this is a literal". */
.case-body code {
  font-family: var(--font-mono);
  font-size: 0.92em;
  letter-spacing: 0;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}

/* AN OUTBOUND LINK IN RUNNING PROSE — the page's third and last inline
   device, after `<em>/<strong>` and `<q>` (DESIGN.md → Inline emphasis).
   It exists because Pindo's design system is PUBLIC, and a browsable
   showcase is evidence the page would otherwise only be able to assert.

   IT UNDERLINES, AND THAT IS THE POINT. `.case-footer a` can drop the
   underline and lift the ink instead, because a slim bar of link text is
   read as chrome; a link buried mid-sentence has nothing telling the reader
   it is clickable except its own mark. Colour alone would also be the one
   thing *Color* forbids — a signal carried by hue with no second channel.

   TEAL AT INK STRENGTH. `--text-accent-ink` is the small-text teal (the
   third value in the accent's legibility ladder); `--teal-accent` is a
   display value and fails on 16px prose. The rule stays soft at rest and
   firms on hover — the underline is the state, the colour is constant, so
   nothing shifts weight or position. Focus is inherited from focus.css's
   global `:focus-visible` outline; do not add a box-shadow ring here. */
.case-link {
  color: var(--text-accent-ink);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  text-decoration-color: color-mix(in srgb, var(--text-accent-ink) 32%, transparent);
  transition: text-decoration-color var(--dur-normal) var(--ease-out);
}
.case-link:hover {
  text-decoration-color: currentColor;
}

/* THE LEDE — a standfirst, not a big first paragraph.
   It used to differ from the support copy by size alone (21 vs 16) plus a
   faint ink step, at the SAME weight, the SAME 680px measure, and separated
   by 21px where two ordinary paragraphs sit 20px apart. Three of the four
   signals that mark a standfirst were missing, so the block read as one slab
   of grey text that happened to change size halfway down.

   It is now HOSTED IN A PLATE, at the full column width:
   1. The plate is the primary signal — `--surface-alt` on `--radius-card`,
      the same warm-paper plate every other case component sits on
      (`.case-figure__mat`, `.course-stepper`, `.fold-compare__face`). No
      border, no shadow at rest.
   2. Full 680 column width, edge-aligned with the body copy. An earlier pass
      narrowed the measure to 560px instead; the plate replaces that, so the
      lede lines up with everything else in the column.
   3. Weight stays `--weight-book` (350), same as the body. An intermediate
      pass raised it to 400 to force a contrast, which required carving an
      exception into the "never 400 on PP Neue Montreal" rule. Once the plate
      arrived that contrast was redundant — the surface separates the tier, so
      the type can stay light and the house rule stays intact. Size, ink, and
      the plate carry the hierarchy; the weight does not need to. */
.case-body .case-lede {
  font-size: var(--type-case-lede);
  font-weight: var(--weight-book);
  line-height: 1.55;
  color: var(--text-primary);
  background: var(--surface-alt);
  border-radius: var(--radius-card);
  corner-shape: squircle;
  padding: var(--sp-8);
}

.case-body .case-lede + p {
  margin-top: var(--sp-6);
}

/* Tier 2 — chapter headings (matches the homepage section scale) */
.case-chapter__title {
  font-family: var(--font-display);
  font-weight: var(--weight-regular);
  font-size: var(--type-h2-size, 64px);
  line-height: 1.1;
  color: var(--text-primary);
  margin-bottom: var(--sp-5);
}

/* Tier 3 — connective-beat headings (clearly smaller than chapters) */
.case-beat__title {
  font-family: var(--font-display);
  font-weight: var(--weight-regular);
  /* The `.case-page` ladder above supplies the real size. This base value
     used to read `var(--type-h3-size, 32px)`, whose fallback was 8px off the
     token it guarded — documentation of an intent the CSS never delivered. */
  font-size: var(--type-case-beat);
  line-height: 1.25;
  color: var(--text-primary);
  /* Matches the chapter heading's 20px. At 16px a 32px serif line sat right
     on top of a four-line lede block, which made the heading read as part of
     the same slab rather than as its lid. */
  margin-bottom: var(--sp-5);
}

/* -- Tier 1: the mid-page thesis ---------------------------------- */
.case-thesis {
  font-family: var(--font-display);
  font-weight: var(--weight-regular);
  font-size: var(--type-h1-size);   /* tier 1 — never shares a screen with the cover */
  line-height: 1.12;
  color: var(--text-primary);
  max-width: 20ch;
  margin: var(--sp-8) 0;
}

/* -- Figures (full 1120 width — the page's loudest punctuation) --- */
.case-figure {
  margin: var(--sp-12) 0;
}

/* TWO PLATE FAMILIES, NOT ELEVEN COPIES OF ONE.
   `.case-lede` (x6 on Japan) and `.case-figure__mat` (x5) were the same
   object: `--surface-alt`, `--radius-card`, squircle, 680px vs 649px wide —
   31px apart, which reads as one tier wobbling rather than as two. Eleven
   near-identical rounded plates on a single page is the "too many large
   rounded cards" failure, and it is why the case pages feel repetitive.

   They are not the same thing and should not look it:
     `--surface-alt`   = a RECESSED PANEL. Text you read sits in it — the
                         lede, the stepper, the fold-compare face.
     `--paper-tint-2`  = a MOUNT. A deeper stock behind an image, the way a
                         photograph is mounted on board. Nothing to read.

   Same radius, same squircle, same no-shadow — still ONE container idiom, so
   this is not a second card style. Only the stock changes, which is enough to
   tell a reading surface from a framing one at a glance.

   This also revives `--paper-tint-2`, which DESIGN.md listed as a dead token.
   The warm ramp existed for exactly this and was never used. */
/* THE BAND IS `--sp-6` (24), THE SAME ENCLOSURE EVERY OTHER CARD USES.
   This card frames its pictures — the inset runs on all four sides, so every
   picture keeps its own edge. That matters most where there is more than one:
   a lid makes a `--pair` or a `--stack` read as one image sliced by gaps,
   because the outer boundary belongs to the card and only the seams belong to
   the pictures. Framed, each screen is a separate object again, which is what
   those figures are arguing.

   IT WAS THE STOCK, NOT THE WIDTH. The band was `--sp-5` (20) on
   `--paper-tint-2` (#ece6da) and read as dirt, so the width was blamed first
   and taken down to 12 — which did fix it, but only by making the offending
   surface small. That put the figure card at 12 while every other enclosure on
   a case page sat at 24–40, so it was the one container on the page not on the
   ladder, and a 12px band on a 948px card reads as a hairline of trim rather
   than as a card holding something.

   `--surface-alt` is what actually fixed it. Once the stock is only 3.7 L*
   under the page instead of 5.8, and lighter than every picture it holds
   rather than darker, the band can be as generous as the rest of the system:

     `.case-lede`               32
     `.case-next`               40
     `.course-stepper` plate    24
     `.course-stepper__panel`   24   ← the same card as this one
     `.case-figure__mat`        24   ← now

   Two things are still true and still load-bearing: **do not put a deeper
   stock back in this band** (20px of `--paper-tint-2` is where this started,
   and `--ink-900` was tried too — it separates at 80.9 L* but puts 7–8
   near-black blocks down a page whose thesis is that it reads as one sheet),
   and **do not solve a band that reads wrong by widening it**. Width is what
   puts it on the ladder; stock is what decides whether it reads at all.

   `--mat-inset` IS THE MODULE'S ONLY SPACING VALUE, and there is only one
   MECHANISM carrying it. The mat is a grid whose `gap` is the inset, so the
   gutter between stacked screens, the gutter between paired ones, and the gap
   above the caption are all the same declaration — not three rules that have
   to be kept equal. Three things read the inset and nothing else in the figure
   states a distance:

     the band        `padding: var(--mat-inset)`
     every gutter    `gap: var(--mat-inset)`
     the corner      `calc(--case-shot-radius + --mat-inset)`

   That is what makes the card balanced rather than merely padded: the white
   closes at the same distance on all four sides, between pictures, and under
   the last one, so the copy sits inside the same frame the pictures do.

   It was three values doing one job — gutters at `--sp-4` (16) against a
   smaller band, and a caption `margin-top` of 16 on top of that. On a grid mat
   the margin ADDED to the row gap, so the caption sat 24 below a picture that
   was 12 from every other edge, and the band read as tighter than everything
   it contained. The margin is gone; the gap does it, once, everywhere. Never
   write a raw length inside this module — change the inset. The ≤768 block
   sets it once, to 16.

   CORNERS ARE DERIVED, so the two arcs share a centre: outer = inner + inset
   = `--case-shot-radius` + `--mat-inset` (16 + 24 = 40, which is also
   `.course-stepper`'s plate radius — the same arithmetic reaching the same
   number, not a coincidence to keep in sync by hand). It must be
   `--case-shot-radius`, never `--radius-card` — that steps 16/14/12 while the
   shot radius holds at 16, and the pair would invert at tablet. `corner-shape`
   has to match on both boxes or a squircle nested in a circular corner never
   hugs. */
.case-figure__mat {
  --mat-inset: var(--sp-6);
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--mat-inset);
  background: var(--surface-mount);
  border-radius: calc(var(--case-shot-radius) + var(--mat-inset));
  corner-shape: squircle;
  padding: var(--mat-inset);
  overflow: hidden;
}

.case-figure__mat img,
.case-figure__mat .case-shot {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--case-shot-radius);
  corner-shape: squircle;
}

/* Several screens on ONE mat under ONE caption — they argue a single point
   together, so they are one figure, not a run of plates. Used twice: the POS
   order at two moments (opened, then finished), and the three settings
   sections a bundle inherits (taxes / cost / item tracking).

   The POS pair is a pair, NOT a before/after: bundles are a new feature, so
   there is no prior state to compare against.

   STACKED, not side by side. Two-up inside the 680 reading column gives a
   960-wide POS screen 312px — a 32% scale, at which none of the interface
   text is legible and the figure becomes decoration. Stacked, each screen
   gets the full measure (~71%) and stays readable. The alternative was
   breaking the pair out to the full 1120, which is worse: it would put a
   second vertical axis back on a page whose whole grid rule is that
   everything shares one. Vertical space is the cheapest thing here.

   `--stack` HAS NO RULE OF ITS OWN any more — one column at `--mat-inset` is
   what every mat does, so stacking is simply what a mat with two children
   already is. The class stays as the semantic hook (and as the anchor for
   this note); `--pair` below is the only variant that actually declares
   anything, because it is the one that changes the column count. */

/* THE ORIENTATION RULE: landscape screens STACK, portrait screens sit in a ROW.
   `--stack` above is written for 1440x1024 captures, where one column is the
   only way to keep interface text legible. A portrait phone capture inverts
   that — 786x1704 at the full 680 column is 1,474px tall, and two of them is
   most of a screen's scroll for one comparison. `--pair` puts them side by
   side on `--wide` (948), where each lands at ~446px: a 57% scale, which holds
   the sheet's own heading and its billing line.

   Same mat, same stock, same radius, same gutter — this is a COLUMN COUNT and
   nothing else, which is why it declares nothing else. Three portrait screens
   already have a home in `.case-options`; the difference is that options carry
   a verdict because one of them lost, and these two are both shipped states of
   one component. */
.case-figure--pair .case-figure__mat {
  grid-template-columns: repeat(2, 1fr);
  align-items: start;
}

/* THE DETAIL CROP — a tall screen shown at the part that carries the argument.
   Declared rather than re-exported as a second asset, so the slice can be
   retuned without going back to the source file, and the whole screen stays
   available if a later chapter wants a different part of it.

   Both numbers are percentages of the image's OWN WIDTH (`aspect-ratio` on the
   mount, `margin-top` on the image), so the crop holds identically at every
   breakpoint with no media query. The radius moves to the mount because the
   image now overflows it — `.case-figure__mat img` still supplies the rest. */
.case-shot {
  overflow: hidden;
  border-radius: var(--case-shot-radius);
  corner-shape: squircle;   /* matches the mat's corner, or the two can't nest */
}

.case-shot img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 0;   /* the mount clips now */
}

/* -- The wide figure — rhythm from MEASURE, not decoration -------- */
/* Every figure on every case page rendered at exactly `--case-column` (680),
   so a 5,000px page had one width for its whole length and the only variety
   left was the plates. `--wide` reclaims the rail: the block starts at the
   section's own left edge and still ends on the spine, so the right edge —
   the one the eye tracks down the page — never moves. 680 → 948 is +39%,
   which is a real change of scale without a single new line, plate, or skin.

   Use it on the one or two figures per page that carry the argument. If
   every figure is wide, nothing is.

   Below 1025 the rail has already collapsed and `--case-canvas` IS
   `--case-column`, so this is a no-op — correct, since there is no room to
   widen into. */
@media (min-width: 1025px) {
  /* `.case-options` is wide by default — three portrait screens need the
     room, and comparing them is the whole point of the block. */
  .case-page .case-figure--wide,
  .case-page .case-options {
    margin-left: 0;
    max-width: var(--case-canvas);
  }
}

.case-figure figcaption {
  margin-top: var(--sp-3);
  font-size: var(--type-body2-size);
  color: var(--text-tertiary);
  max-width: 560px;
}

/* THE CAPTION BELONGS TO THE CARD, NOT TO THE PAGE.
   Outside the mat a caption is loose type sitting on paper: it answers to the
   page's measure rather than the picture's, and on Pindo it was the one piece
   of content the vertical crease still crossed. Inside, it is bound to the
   thing it describes, it takes the mat's own padding, and it runs the FULL
   width of the card — a caption for a two-up figure is describing both halves,
   so capping it at 560 would leave it hanging under the left-hand screen.

   `grid-column: 1 / -1` matters on `--pair`, the one mat with more than one
   column: without it the caption takes a single cell and lands beside the
   right-hand screen instead of beneath the set. Everywhere else it is inert.

   THE MARGIN IS CANCELLED, NOT INHERITED. `.case-figure figcaption` above
   carries `margin-top: var(--sp-3)` for the three captions that stand on
   paper (Pindo's stepper and viz blocks), where nothing else supplies the
   gap. Inside a mat the grid's own `gap` already puts the caption
   `--mat-inset` below the last picture and the padding puts it the same
   distance above the card's bottom edge, so that margin ADDS — which is
   exactly what made the caption sit 24 under a picture that was 12 from every
   other edge. Zero it here and let the one mechanism do it. */
.case-figure__mat figcaption {
  grid-column: 1 / -1;
  max-width: none;
  margin-top: 0;
}

/* -- Options — the block that shows a DECISION -------------------- */
/* Explored options side by side, with the outcome stated on each. This
   replaced a `--stack` figure holding three screenshots under one caption:
   the images were there, but the fact that they were *alternatives* — and
   that one was rejected — lived only in the prose above them. A portfolio is
   read for judgement, so the judgement should be the thing you can see.

   No new container: each option's image sits on the same `--paper-tint-2`
   mount every other figure uses. The chosen/not-chosen signal is INK, the
   same device the case eyebrows use — `--text-accent-ink` against
   `--text-tertiary`. No badge, no border, no highlight fill, and above all
   no dimming of the rejected options: they are evidence, and evidence stays
   legible. */
.case-options {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-5);
  margin: var(--sp-12) 0;
}

/* Same concentric rule as `.case-figure__mat`, one step tighter inset. */
/* AN OPTION IS A FIGURE CARD THAT ALSO CARRIES A VERDICT. The stock, the band
   and the corner all come from `.case-figure__mat` — same 12px inset, same
   derived corner — so the three options sit in the same object as every figure
   above them. The picture is framed here for the same reason it is framed
   there: three screens side by side have to read as three objects.

   The mount is just the clip that holds one screen; the CARD is `.case-option`,
   because the verdict and the note belong to it and they sit on the stock. */
.case-option__mount {
  border-radius: var(--case-shot-radius);
  corner-shape: squircle;
  overflow: hidden;
}

.case-option {
  --mat-inset: var(--sp-6);
  background: var(--surface-mount);
  border-radius: calc(var(--case-shot-radius) + var(--mat-inset));
  corner-shape: squircle;
  overflow: hidden;
  padding: var(--mat-inset);
}

.case-option__mount img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 0;   /* the mount clips */
}

.case-option__verdict {
  margin-top: var(--mat-inset);   /* same band, same distance — see `--mat-inset` */
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-tertiary);
}

.case-option--chosen .case-option__verdict {
  color: var(--text-accent-ink);
}

.case-option__note {
  margin-top: var(--sp-2);
  font-size: var(--type-body2-size);
  line-height: 1.5;
  color: var(--text-secondary);
}

@media (max-width: 768px) {
  .case-options { grid-template-columns: 1fr; gap: 24px; margin: 40px 0; }
  /* One step under the desktop band, matching `.case-figure__mat` below. */
  .case-option { --mat-inset: 16px; }
}

/* -- Pull quote — lives INSIDE the body flow (one reading axis) --- */
.case-quote {
  margin: var(--sp-6) 0;
}

.case-quote p {
  font-family: var(--font-display);
  font-weight: var(--weight-regular);
  font-size: 24px;
  line-height: 1.4;
  color: var(--text-primary);
}

/* PROVENANCE, NOT A CAPTION. One `<cite>` string was doing four jobs — who
   said it, in what study, when, and through whom. Splitting it in two puts
   the person in the prose register and the apparatus in the same mono label
   register the rail uses, so evidence reads as ONE system wherever it
   appears: rail counter, ledger stamp, quote provenance.

   The gap this closes: a Servicing quote came through a Spanish interpreter
   and that caveat lived three paragraphs upstream in prose. It now has a
   permanent slot on the quote itself.

   `.case-quote cite` keeps its base declarations for any quote still using
   a single string — the two-line form is opt-in via the spans. */
.case-quote cite {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  margin-top: var(--sp-4);   /* was --sp-2; two lines need the room */
  font-family: var(--font-body);
  font-style: normal;
  font-size: var(--type-body2-size);
  color: var(--text-tertiary);
}

.case-quote__who {
  font-family: var(--font-body);
  font-weight: var(--weight-book);
  font-size: var(--type-body2-size);
  line-height: 1.45;
  color: var(--text-secondary);   /* a person, not a label */
}

.case-quote__how {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  line-height: 1.45;
  color: var(--text-tertiary);    /* the apparatus */
}

/* -- Outcome stats ------------------------------------------------ */
/* SEGMENTED BY TONE, NOT BY LINES.
   Three per-column toppers put three more horizontal rules on a screen that
   already carries two section creases — five lines in one viewport, and the
   three repeating ones carried the least meaning. The row is now one tonal
   plate and the columns are separated by space alone.

   The creases stay: they are the house-mark and they mark SECTION boundaries.
   What went is the decorative repetition inside a section. Rule of thumb for
   this page: creases separate acts, tone separates components, space
   separates items. */
.case-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-6);
  margin-top: var(--sp-8);
  background: var(--surface-alt);
  border-radius: var(--radius-card);
  corner-shape: squircle;
  padding: var(--sp-6);
}

/* No topper at all. This went `border-top: 2px solid` → drawn silhouette →
   nothing: the first change made the line correct, this one removed the need
   for a line. The tonal plate above and the column gap do the segmenting. */
.case-stat {
  min-width: 0;   /* let long phrases wrap instead of forcing the track wider */
}

.case-stat dt {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-accent-ink);
  margin-bottom: var(--sp-2);
}

/* Short phrases, NOT numerals (`Merchants creating sets`) — this is the
   forward-looking "what we'll measure" list, so it stays at the h4 step and
   does not join the numeral register. `font-weight` is explicit: without it
   this inherited `--weight-book` (350) from `body`, and PP Editorial New
   ships no 350 cut, so the browser was synthesising the weight. */
.case-stat dd {
  font-family: var(--font-display);
  font-weight: var(--weight-regular);
  /* Shares the lede STEP (21/19/17), not the lede's role — a stat value is a
     statement, and at the component step (24px) three of them sat only 8px
     under the 32px beat heading and read as competing sub-headlines. */
  font-size: var(--type-case-lede);
  /* These are phrases, so they wrap — and left to itself the browser drops a
     single orphan word onto line two ("Merchants creating / sets"). `balance`
     evens the lines, which is most of what made the row look untidy. */
  text-wrap: balance;
  line-height: 1.3;
  color: var(--text-primary);
}

/* -- Chapter nav — minimal side rail (wide desktop only) ----------
   A vertical stack of ticks, one per chapter; the active chapter's tick
   extends and reveals its label. It lives in the right margin so it
   orients the reader without echoing the top nav. Labels reserve their
   space (opacity/transform only) so a tick never shifts as the label
   fades in. Shown only where the right margin is clear; below that the
   case reads linearly with no page nav.

   THE THRESHOLD IS MEASURED, NOT CHOSEN. It was ≤1279 back when the
   container was a hardcoded 1120 and the canvas sat flush-left inside it,
   which parked 92px of dead paper on the right for the nav to borrow. Now
   that the container derives from the canvas and the page is centred, that
   borrowed space is gone: at 1280 an active label (126px wide, right-inset
   24) landed 16px from the reading column's right edge — against a page
   whose narrowest real air is 48. The nav returns at the width where its
   own rule is true again. */
.chapter-nav {
  position: fixed;
  right: 24px;
  top: 50%;
  transform: translateY(-50%) translateX(8px);
  z-index: 90;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--sp-4);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out);
}

.chapter-nav.is-visible {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(-50%);
}

.chapter-nav__link {
  display: flex;
  flex-direction: row-reverse;   /* tick on the right, label to its left */
  align-items: center;
  gap: var(--sp-3);
  text-decoration: none;
}

.chapter-nav__link::before {     /* the tick */
  content: "";
  flex: none;
  width: 22px;
  height: 2px;
  border-radius: 2px;
  background: var(--border-mid);
  transition: width var(--dur-normal) var(--ease-out), background-color var(--dur-normal) var(--ease-out);
}

.chapter-nav__label {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--text-tertiary);
  opacity: 0;
  transform: translateX(6px);
  /* Was `0.35s` — a fourth value for the same class of micro-transition, and
     off the ladder. `--dur-normal` (320ms) is the step it was approximating,
     and it is what the tick beside it already used. */
  transition:
    opacity   var(--dur-normal) var(--ease-out),
    transform var(--dur-normal) var(--ease-out),
    color     var(--dur-normal) var(--ease-out);
}

.chapter-nav__link:hover::before { width: 34px; background: var(--text-secondary); }
.chapter-nav__link:hover .chapter-nav__label { opacity: 1; transform: none; color: var(--text-secondary); }

.chapter-nav__link.is-active::before { width: 46px; background: var(--text-primary); }
.chapter-nav__link.is-active .chapter-nav__label { opacity: 1; transform: none; color: var(--text-primary); }

@media (max-width: 1359px) { .chapter-nav { display: none; } }

/* -- Next-project card -------------------------------------------- */
.case-next {
  display: flex;
  align-items: center;
  gap: var(--sp-6);
  background: var(--surface-alt);
  border-radius: var(--radius-panel);
  corner-shape: squircle;
  padding: var(--sp-10);
  text-decoration: none;
  position: relative;
  isolation: isolate;   /* own stacking context — lets ::before sit at z-index:-1 */
}

/* THIS ONE IS PAPER, SO IT DOES NOT FILL. It is a card you point at, not a
   control you press, so it takes `.project-card::before`'s gesture: a mat
   growing outward past the edges while it fades up, on `--dur-normal` /
   `--ease-out`, radius staying concentric (panel radius + the outward inset)
   so the expanded mat hugs the corners. It stands alone in its section, so
   unlike `.exp-item` it can grow on all four sides, exactly like the card.
   DESIGN.md → Two hover families.

   THE MAT IS `--paper-tint-2`, NOT `--surface-alt`. The card's mat works
   because a project card has no resting background — here the card already IS
   `--surface-alt`, so fading that same tone in behind it would say nothing.
   One step deeper on the paper ramp reads as the sheet lifting off its mount.
   This is the "change the stock by role, never the skin" rule: no new shape,
   no border, no shadow of its own (`data-paper` already owns the shadow). */
.case-next::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--radius-panel);
  corner-shape: squircle;
  background: var(--paper-tint-2);
  opacity: 0;
  z-index: -1;
  transition:
    inset         var(--dur-normal) var(--ease-out),
    border-radius var(--dur-normal) var(--ease-out),
    opacity       var(--dur-normal) var(--ease-out);
}

.case-next:hover::before,
.case-next:focus-visible::before {
  inset: calc(-1 * var(--sp-4));
  border-radius: calc(var(--radius-panel) + var(--sp-4));
  opacity: 1;
}

.case-next__label {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-accent-ink);
}

/* A navigational card, not a heading — so it sits AT the beat step rather
   than above it. It used to resolve to `--type-h3-size` (40px), which made a
   `<span>` outrank every beat `<h2>` on the page. The explicit weight fixes
   the same missing-400 defect as `.case-stat dd`. */
.case-next__title {
  font-family: var(--font-display);
  font-weight: var(--weight-regular);
  font-size: var(--type-case-beat);
  /* MUST be stated. `body` sets an ABSOLUTE 24px leading, which inherits
     verbatim — a 32px title was running on 24px lines and collided with
     itself the moment the project name wrapped. Matches the beat heading. */
  line-height: var(--leading-snug);
  color: var(--text-primary);
  flex: 1;
}

.case-next__arrow {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: var(--paper);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-primary);
}

.case-next__arrow svg {
  width: 16px;
  height: 16px;
}

/* -- Slim footer --------------------------------------------------- */
.case-footer {
  display: flex;
  justify-content: space-between;
  max-width: var(--case-container);
  margin: 0 auto;
  padding: var(--sp-8) var(--gutter-inner) var(--sp-10);
  font-size: var(--type-body2-size);
  color: var(--text-tertiary);
}

/* AN INK LIFT, NOT A FILL. This is 90px of inline text in a slim footer bar;
   sweeping ink through it would be the loudest thing on the page's quietest
   line. It takes the site's other state device instead — the ink states the
   level — tertiary (5.1:1) to primary (13.9:1). It had NO hover at all, and
   with `text-decoration: none` at tertiary it did not read as a link either. */
.case-footer a {
  color: var(--text-tertiary);
  text-decoration: none;
  transition: color var(--dur-normal) var(--ease-out);
}

.case-footer a:hover,
.case-footer a:focus-visible {
  color: var(--text-primary);
}

/* -- Responsive ---------------------------------------------------- */
@media (max-width: 1024px) {
  .case-cover { padding: 112px var(--gutter-inner) 56px; }
  /* THE PLATE KEEPS ITS SHAPE, NOT ITS HEIGHT. 320 against the 680 column was
     2.13:1 — a letterbox even narrower than the old desktop step, and it cut
     29px off the top of Pindo's phone and 73px off the bottom. The plate's
     width already follows the column, so holding the height fixed is what let
     the ratio wander from breakpoint to breakpoint. 395 is 680 ÷ 1.72 — the
     same shape as the other two steps, not a number chosen for this one. */
  .case-cover__media { height: 395px; margin-bottom: 40px; }

  /* Mat expands to the MIDDLE of the side spacing, concentric radius — the
     card's own responsive rule, mirrored. */
  .case-next:hover::before,
  .case-next:focus-visible::before {
    inset: calc(var(--gutter-inner) / -2);
    border-radius: calc(var(--radius-panel) + var(--gutter-inner) / 2);
  }
  .case-meta { grid-template-columns: repeat(2, 1fr); gap: 20px; }

  /* Rail stacks above content — eyebrow becomes a block label */
  .case-grid { grid-template-columns: minmax(0, var(--case-column)); gap: 12px; }
  .case-grid + .case-grid { margin-top: 32px; }
  /* The rail stops being a margin and becomes a label row above the content,
     so the hanging alignment reverts to flush-left with the column. */
  /* The rail stops being a margin and becomes a label row above the content.
     No `text-align` reset needed — the base is flush-left, and no cap-line
     reset either: that offset is 0, since nothing sits beside the label here. */
  .case-rail { flex-direction: row; align-items: center; gap: 12px; }


  /* Air ladder re-pointed, not redefined: beat break = 64 + 48 = 112,
     act break = 88 + 48 = 136. Both still clear the 40px figure gap by a
     wide margin, which is what makes a break read as a break. */
  .case-page { --case-air-beat: 64px; --case-air-act: 88px; }
  /* Stacked inside one plate with no rules between items, 16px read as one
     run-on list — space is the only separator left, so it has to be real. */
  .case-stats { grid-template-columns: 1fr; gap: 28px; margin-top: 32px; padding: 24px; }
  .case-figure { margin: 40px 0; }
}

@media (max-width: 768px) {
  /* beat break = 48 + 32 = 80, act break = 68 + 32 = 100. The section's
     bottom padding drops to 32 here, so the top values carry more of the
     break than they do on desktop. */
  .case-page { --case-air-beat: 48px; --case-air-act: 68px; }
  .case-page .section { padding-bottom: 32px; }

  .case-cover { padding: 96px var(--gutter) 40px; }
  /* Matches the project card's own mobile media height (200px), so the
     flight stays a translation rather than a resize on small screens. */
  .case-cover__media { height: 200px; margin-bottom: 28px; }
  /* Sizes come from the case ladder tokens now — only spacing here. */
  .case-cover__thesis { margin-bottom: 24px; }
  .case-meta { grid-template-columns: 1fr 1fr; gap: 14px; }
  .case-figure { margin: 32px 0; }
  /* SET THE INSET AND NOTHING ELSE. Padding, corner radius, both gutters and
     the caption's gap all derive from it, so one value re-spaces the whole
     module at this breakpoint — writing any of them here would silently
     un-hug the corners or break the balance the inset exists to hold. One
     step under the desktop 24 — a 343px card cannot spare the band a 948px
     one can, and 16 sits inside the mobile philosophy's 12–24 range for
     internal padding while the page's other enclosures here run 20–24.
     (Three `gap` overrides used to live here and are gone.) */
  .case-figure__mat { --mat-inset: 16px; }
  /* Two portrait screens share a 343px canvas here — 165px each, at which the
     sheet's own body copy stops being readable and the pair becomes decoration.
     One column: the row was only ever a way to fit portrait screens into the
     desktop measure, and on a phone there is no measure to fit them into. */
  .case-figure--pair .case-figure__mat { grid-template-columns: 1fr; }
  /* The lede plate keeps its full column width; only the inset compresses,
     per the mobile spacing philosophy (internal padding shrinks, the block
     itself still runs edge to edge). */
  .case-body .case-lede { padding: 20px; }
  .case-quote { margin: 24px 0; }
  .case-next { padding: 24px; gap: 16px; }

  /* Half the gutter, centred in it. The desktop `--sp-4` is 16px and so is
     `--gutter` here, so the mat's edge landed exactly on the screen edge —
     measured 0 → 375 at 375px wide. Same fix, same reason, as
     `.project-card` and `.exp-item`. */
  .case-next:hover::before,
  .case-next:focus-visible::before {
    inset: calc(var(--gutter) / -2);
    border-radius: calc(var(--radius-panel) + var(--gutter) / 2);
  }
  .chapter-nav { display: none; }   /* linear read on mobile */
  .case-footer { padding: 24px var(--gutter) 32px; }
}
