/* ==========================================================================
   PROJECT PAGE TEMPLATE
   CLAUDE.md §1 (the governing split), §6 (image system), §7 (brand
   containment).

   THIS FILE IS DELIBERATELY QUIET. §1 splits the effort: the shell announces
   itself, the project pages get out of the way so the work can be examined.
   Interviewers skip the shell and come straight here, so anything that
   competes with an image is a cost with no return.

   What that rules out, concretely:
     - no spine, no coils, no scroll-linked reveals
     - no entrance animation of any kind — every element is at full strength
       on load, so nothing is withheld from someone scrolling fast
     - no hover motion on images beyond what the shell already defines
   The only transitions here are on interactive controls, where a state
   change needs to be legible as a state change.

   FOUR SECTION TYPES, and every project slots into them:
     1. full-bleed image        .pj-figure.pj-figure--bleed
     2. image + caption         .pj-figure  >  .frame  +  .pj-caption
     3. section header + body   .pj-section >  .pj-section__head + .pj-body
     4. closing statement       .pj-closing
   Anything that does not fit one of those is a fifth type in disguise and
   should be argued for rather than added.

   The image system itself is not redefined here. .frame, .row and the ratio
   buckets come from layout.css and are shared with the homepage, which is
   the whole point of §6 — one normalising system, not a per-page one.
   ========================================================================== */


/* --- Page rhythm ---------------------------------------------------------
   One vertical rhythm for the whole page, set once. Sections do not carry
   their own margins; the flow gap between them is the rhythm, so nothing can
   drift out of step by being reordered. Generous by instruction: §1 asks for
   large images and generous space, and the gap between blocks is most of
   what "generous" means in practice. */

.pj {
  /* TWO TIERS, NOT ONE. Every vertical gap used to be the same size, which
     meant nothing grouped: a section label sat equidistant between the block
     it followed and the block it introduced, so it read as belonging to
     neither. The total whitespace is unchanged — it is redistributed.

     TIGHT holds a unit together: label to its image, image to its caption,
     body to the content it explains.
     LOOSE separates units: before a new label, and between one finished
     section and the next.

     The ratio is roughly 1:3. Both are here so it tunes from one place. */
  --stack-tight: clamp(var(--sp-s), 3vh, var(--sp-m));
  --stack-loose: clamp(var(--sp-xl), 13vh, var(--sp-2xl));

  /* Kept: --pj-tight is the INTERNAL gap inside a section (head to lead, lead
     to body). It is the tight tier by another name and is aliased so the two
     cannot drift. */
  --pj-tight: var(--stack-tight);
  --pj-rhythm: var(--stack-loose);

  display: flow-root;
  padding-block-start: calc(var(--masthead-h) + var(--stack-loose));
  padding-block-end: var(--stack-loose);
}

/* Default is LOOSE — the safe end. Anything that should be closer says so. */
.pj > * + * { margin-block-start: var(--stack-loose); }

/* A section carrying only a label introduces whatever follows it, so the
   thing it introduces comes close. Identified structurally rather than with a
   new class, so the six pages need no markup change: a label-only section is
   one with a head and neither a lead nor a body. */
.pj > .pj-section:not(:has(.pj-body)):not(:has(.pj-lead)) + * {
  margin-block-start: var(--stack-tight);
}

/* Body that explains the figure above it belongs to that figure. */
.pj > .pj-figure + .pj-section,
.pj > .pj-contained + .pj-section {
  margin-block-start: var(--stack-tight);
}

/* ...but a section that opens a NEW unit is loose again, even straight after
   a figure. Last in the cascade so it wins over the rule above. */
.pj > * + .pj-section:not(:has(.pj-body)):not(:has(.pj-lead)) {
  margin-block-start: var(--stack-loose);
}


/* --- Project head --------------------------------------------------------
   Title, subtitle, year. Quiet by comparison with the homepage hero: this is
   a label on the work, not an announcement. Centred, matching the card
   stacks the visitor just came from, so arriving here reads as continuous. */

.pj-head {
  text-align: center;
  max-width: 46rem;
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* Up from --type-micro (12px). At that size the project number read as a
   printing artefact rather than as part of the head; it is the one piece of
   numbering the page carries and it should be legible as such. */
.pj-head__index {
  display: block;
  font-size: clamp(0.95rem, 1.3vw, 1.25rem);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--kumkumam);
  margin-block-end: var(--sp-s);
}

.pj-head__title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--type-title);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-tight);
  margin: 0;
}

.pj-head__sub {
  margin: var(--sp-s) auto 0;
  max-width: 38ch;
  font-size: var(--type-lead);
  line-height: var(--lh-snug);
  color: var(--ink-muted);
}

.pj-head__year {
  display: block;
  margin-block-start: var(--sp-s);
  font-size: var(--type-micro);
  letter-spacing: var(--ls-wide);
  color: var(--ink-muted);
}

/* --- Speculative-work label ----------------------------------------------
   REQUIRED ON EVERY PAGE THAT REBRANDS SOMEONE ELSE'S COMPANY. Mailchimp and
   Spotify are unsolicited concepts: the copy argues about a brand Prateek
   does not own, using that brand's marks and name. A subtitle like "A
   Homecoming to Where It All Started" reads as admiring rather than as
   hypothetical, and a reader skimming could take the whole page for client
   work.

   Outlined rather than filled, so it reads as a status tag and not as
   something to click — the one filled control on the page is the link-out,
   and two filled kumkumam shapes would compete. CLAUDE.md §10 already
   requires the same visible label on the Toyota Supra entry for the same
   reason; this is that rule generalised. */

.pj-head__tag {
  display: inline-block;
  margin-block-start: var(--sp-m);
  padding: 0.45em 1em;
  border: 1px solid var(--kumkumam);
  color: var(--kumkumam);
  font-size: var(--type-micro);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
}

.pj-head__disclaimer {
  margin: var(--sp-s) auto 0;
  max-width: 42ch;
  font-size: var(--type-small);
  line-height: var(--lh-snug);
  color: var(--ink-muted);
}


/* --- TYPE 3: section header + body --------------------------------------- */

.pj-section {
  padding-inline: var(--gutter);
}

/* §6: "Section headers in --kumkumam, generously spaced." The space is in
   the letter-spacing as well as around it — this is the same treatment
   "Selected work" gets on the homepage, so the two read as one system. */
.pj-section__head {
  margin: 0 0 var(--pj-tight);
  text-align: center;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: clamp(0.95rem, 1.5vw, 1.2rem);
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--kumkumam);
  /* NOT --lh-tight. As an h2 this inherited 0.92, which base.css calls out as
     display-only — set solid so a 6rem project title does not open up. At
     0.95rem it is 15px of leading, and a head long enough to wrap put two
     rows of tracked-out caps almost into each other. The tracking is what
     makes it worse: 0.26em spreads the glyphs sideways, so the row reads as
     an open band and the gap between rows has to answer that.

     Every one-line head is unaffected — this only ever shows on the heads
     that wrap, which on a phone is most of them. */
  line-height: 1.5;
}

/* The insight line. One or two sentences, set larger than the body and
   lighter than a heading — it is the argument in brief, and it earns display
   type where the paragraphs under it do not. */
.pj-lead {
  max-width: 34ch;
  margin: 0 auto var(--pj-tight);
  text-align: center;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.4rem, 2.6vw, 2.2rem);
  line-height: 1.3;
  letter-spacing: -0.015em;
  text-wrap: balance;
}

/* CENTRED, matching the homepage. The whole shell centres — hero stack, card
   stacks, About — so a project page that set its copy flush left would read
   as a different site rather than as the inside of this one.

   The measure does the work that alignment normally does. Centred text loses
   the consistent left edge the eye returns to on each line, so the shorter
   the line, the less that costs; --measure-narrow is 46ch rather than the
   62ch used for flush-left copy elsewhere. Anything longer than this wants
   to be left-aligned instead, and that is the signal to split it.

   WIDENED. At --measure-narrow (46ch, ~372px at 1280) the prose ran about
   29% of the viewport and read as a footnote beside full-width images. The
   measure now takes the smaller of 66ch and 92vw. The 66ch cap is the upper
   end of comfortable line length — past that, reading gets worse rather than
   better, so this is not simply "as wide as the images"; on desktop 66ch is
   what binds, at ~615px and 48% of a 1280 viewport.

   THE SECOND TERM IS 92vw, NOT 50vw. A 50vw cap reads as "half the screen",
   which is right on a desktop and wrong on a phone — it bound first at 375
   and squeezed the prose to 188px, half the column, which is narrower than
   it was before this change. 92vw never binds on desktop and gets out of the
   way on mobile, where the section's own gutters already set the width.
   Captions are untouched and stay narrow. */
.pj-body {
  max-width: min(66ch, 92vw);
  margin-inline: auto;
  text-align: center;
  font-size: var(--type-body);
  line-height: var(--lh-body);
  text-wrap: pretty;
}

.pj-body > p { max-width: none; }
.pj-body > p + p { margin-block-start: var(--sp-m); }

/* --- Metadata block ------------------------------------------------------
   Client, role, scope, dates, status — facts rather than argument, so they
   are set as a definition list and not as prose. Each row is centred on the
   page axis like everything else, term above value, so the block does not
   introduce a left edge into a centred page. The hairlines are what make it
   read as a record rather than as a run of short paragraphs. */
.pj-meta {
  max-width: 46rem;
  margin-inline: auto;
  text-align: center;
}

.pj-meta__row {
  padding-block: var(--sp-s);
  border-block-start: 1px solid var(--rule);
}

.pj-meta__row:last-child { border-block-end: 1px solid var(--rule); }

.pj-meta dt {
  margin-block-end: 0.3rem;
  font-size: var(--type-micro);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--ink-muted);
}

.pj-meta dd {
  max-width: 60ch;
  margin: 0 auto;
  font-size: var(--type-body);
  line-height: var(--lh-body);
  text-wrap: pretty;
}

/* --- A finding pulled out above a list ---------------------------------
   One item that needs acting on, set apart from the ones that are worth
   knowing. Ruled top and bottom rather than filled, so it reads as emphasis
   in the copy and not as a control. */
.pj-finding {
  padding-block: var(--sp-m);
  border-block: 2px solid var(--kumkumam);
  font-size: var(--type-lead);
  line-height: var(--lh-snug);
}

/* --- A list inside body copy ---------------------------------------------
   Centred like the prose around it, and ruled rather than bulleted: a
   bullet hangs off a left edge a centred column does not have. */
.pj-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.pj-body > p + .pj-list { margin-block-start: var(--sp-m); }

.pj-list > li {
  padding-block: var(--sp-s);
  border-block-start: 1px solid var(--rule);
  text-wrap: pretty;
}

.pj-list > li:last-child { border-block-end: 1px solid var(--rule); }


/* --- TYPE 1 and 2: images ------------------------------------------------
   §6: images run near-full-width when landscape; a portrait hero gets the
   tall container centred, with negative space either side rather than being
   stretched wide. The two cases are .pj-figure--wide and .pj-figure--tall
   below; neither invents a ratio, both defer to layout.css. */

.pj-figure {
  margin-inline: auto;
  padding-inline: var(--gutter);
  max-width: var(--w-wide);
}

/* THE PACKAGING BAND CLOSES UP AROUND ITSELF.
   The three renders are ~460px native and must not be upscaled, so they stay
   the size they are — what changes is the space around them. The mats now
   abut instead of sitting in three islands with paper between, so the shared
   --surface reads as one grouped block, and the row's own width comes down by
   exactly the gap total so no frame gets wider and no image scales up.

   The vertical space closes too: the band belongs to the copy beneath it, so
   it takes the tight tier below rather than the loose one. */
.pj-figure--band .row--native {
  --row-gap: 0px;
  max-width: 56.75rem;   /* 60rem less the two 25.6px gaps just removed */
}

.pj-figure--band + .pj-section { margin-block-start: var(--stack-tight); }

/* TYPE 1 — full bleed. Edge to edge, no gutter, no mat. For the one or two
   images per project that carry the whole result. */
.pj-figure--bleed {
  max-width: none;
  padding-inline: 0;
  width: 100vw;
  margin-inline: calc(50% - 50vw);
}

/* A portrait image does not become a landscape one by being put in a wide
   box. It keeps the tall bucket and the page gives it room on both sides —
   the negative space is the presentation, not wasted width. */
.pj-figure--tall {
  max-width: min(var(--w-content), 34rem);
}

/* A PORTRAIT HERO STILL HAS TO ANCHOR ITS PAGE. The solo exemption means an
   image sizes to its own ratio, not that it stays small — the Global Retail
   show-floor photo was rendering 416x549 and read as lonely at the top of a
   page whose other heroes run 576 wide.

   Width was the binding constraint, not height: the 34rem cap left 416px of
   usable width after the gutters, so raising --solo-max-h alone did nothing.
   Both move together here. */
.pj-figure--tall-hero {
  max-width: min(var(--w-content), 44rem);
}

/* §6: "Captions quiet and small beneath images." Naming what is shown, not
   explaining a decision — a caption that argues belongs in body copy, and
   the section above it is where that argument goes. */
/* Captions sat at 46ch under figures three times that wide. --measure is the
   body measure and reads correctly under a wide figure; a short caption still
   renders short, so nothing narrow gets stretched.

   margin-inline: auto is what puts the BOX on the page axis. text-align
   centres the text inside its box; without auto margins the box itself sits
   left, which is how a centred paragraph ends up off-axis. */
.pj-caption {
  margin: var(--sp-s) auto 0;
  max-width: var(--measure);
  font-size: var(--type-small);
  line-height: var(--lh-snug);
  color: var(--ink-muted);
}

/* Centred under a centred image; flush left under a bleed, where there is no
   container edge to centre against and a centred line would float. */
.pj-figure:not(.pj-figure--bleed) .pj-caption { margin-inline: auto; text-align: center; }
.pj-figure--bleed .pj-caption { margin-inline: auto; padding-inline: var(--gutter); max-width: var(--w-wide); }

/* A label rather than a sentence — "Before", "After". Kumkumam is allowed
   here and nowhere else in a caption, because the label is doing structural
   work and needs to be findable at a glance. */
.pj-caption__label {
  display: block;
  font-size: var(--type-micro);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--kumkumam);
  margin-block-end: 0.35rem;
}


/* --- TYPE 4: closing statement -------------------------------------------
   Not used on the two link-out pages (§10 cuts it there). Defined once here
   so the pages that do carry it are not each inventing their own. */

/* text-align centres the TEXT; base.css caps every <p> at --measure with no
   auto margins, so a bare paragraph in here sits 280px left of the axis while
   still being "centred". The child selector is the guard: the component works
   whether or not the author remembers __body. */
.pj-closing {
  padding-inline: var(--gutter);
  text-align: center;
}

.pj-closing > p { margin-inline: auto; }

.pj-closing__body {
  max-width: 32ch;
  margin-inline: auto;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.35rem, 2.4vw, 2rem);
  line-height: 1.34;
  letter-spacing: -0.015em;
  text-wrap: balance;
}


/* --- The link-out button -------------------------------------------------
   The whole purpose of a link-out page. It is the one element here allowed
   to look like a control, because it is one. */

.pj-cta {
  text-align: center;
  padding-inline: var(--gutter);
}

.pj-cta__link {
  border-radius: var(--radius-control);
  display: inline-flex;
  align-items: baseline;
  gap: 0.6em;
  padding: 0.95em 1.9em;
  background: var(--kumkumam);
  color: var(--paper);
  font-size: var(--type-small);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  text-decoration: none;
  border: 1px solid var(--kumkumam);
  transition: background-color var(--t-quick) var(--ease),
              border-color var(--t-quick) var(--ease);
}

/* THE COLOUR HAS TO BE RESTATED HERE, and this was a genuine failure rather
   than a preference. base.css carries `a:hover { color: var(--kumkumam-lit) }`
   at specificity (0,1,1), which beats this rule's base `color: var(--paper)`
   at (0,1,0). So on hover the label turned kumkumam-lit on a kumkumam-lit
   ground — 1:1 contrast, the text and its arrow vanishing completely while
   the pointer was on the button.

   Restating it on the hover state itself is (0,2,0), which wins. Paper on
   kumkumam-lit measures 4.76:1, clearing the AA floor §12 sets. */
.pj-cta__link:hover {
  background: var(--kumkumam-lit);
  border-color: var(--kumkumam-lit);
  color: var(--paper);
}

.pj-cta__link:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

.pj-cta__note {
  margin: var(--sp-s) auto 0;
  max-width: 44ch;
  font-size: var(--type-small);
  color: var(--ink-muted);
}


/* --- Back to the work ----------------------------------------------------
   The masthead handles navigation, but a long page ends a long way from it,
   and asking someone to scroll back up to leave is a tax. */

.pj-back {
  text-align: center;
  padding-inline: var(--gutter);
}

.pj-back a {
  font-size: var(--type-small);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--kumkumam);
  text-decoration: none;
  border-block-end: 1px solid transparent;
  transition: color var(--t-quick) var(--ease),
              border-color var(--t-quick) var(--ease);
}

.pj-back a:hover {
  color: var(--kumkumam-lit);
  border-block-end-color: var(--kumkumam-lit);
}


/* ==========================================================================
   §7 — THE CONTAINED BRAND BLOCK

   "Each case study's internal content keeps its own brand system... The
   shell is Prateek's; the work inside each case study belongs to its own
   brand." Containment is what lets both be true at once: inside this block
   the page is the brand's, and the block has a hard edge so nothing leaks.

   THE PALETTE IS DECLARED, NEVER INHERITED. Every brand sets the same four
   custom properties on its own modifier and nothing inside references a
   shell token. That is the quarantine: a rule written here cannot
   accidentally pick up --kumkumam, and a brand colour cannot escape into the
   shell, because the only place these names resolve is inside this block.
   ========================================================================== */

.brand {
  width: 100vw;
  margin-inline: calc(50% - 50vw);
  padding-block: clamp(var(--sp-xl), 10vh, var(--sp-2xl));
  padding-inline: var(--gutter);
  background: var(--b-anchor);
  /* --b-on-anchor, with --b-surface as the fallback. On a brand whose surface
     is a light content colour — Mailchimp's cream — the fallback is exactly
     right. On one whose surface is a second dark tone — Spotify's Mid Purple
     on Deep Purple — it is nearly invisible, which is what it rendered as.
     Those brands state the text colour outright. */
  color: var(--b-on-anchor, var(--b-surface));
}

.brand__inner {
  max-width: var(--w-content);
  margin-inline: auto;
}

/* The eyebrow is the seam. It names whose system this is, in the brand's own
   colours, so the boundary is stated rather than left for the reader to
   infer from a colour change. */
.brand__eyebrow {
  margin: 0 0 var(--sp-s);
  max-width: none;
  text-align: center;
  font-size: var(--type-micro);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  opacity: 0.7;
}

.brand__title {
  margin: 0 0 var(--sp-m);
  text-align: center;
  font-family: var(--b-display, var(--font-display));
  font-weight: var(--b-display-weight, 400);
  font-size: clamp(2rem, 5vw, 3.5rem);
  line-height: 1.05;
  letter-spacing: var(--b-display-tracking, -0.02em);
}

.brand__intro {
  max-width: 66ch;
  margin: 0 auto var(--sp-xl);
  text-align: center;
  font-size: var(--type-body);
  line-height: var(--lh-body);
}

/* Numbered subsections, matching how a real identity sheet is organised. */
.brand__group + .brand__group { margin-block-start: clamp(var(--sp-xl), 8vh, var(--sp-2xl)); }

/* Centred between two rules. The markup carries one <hr> for the right-hand
   rule; the left one is a pseudo-element, so the label sits in the middle of
   the span rather than hanging off its left end.

   `color: inherit` IS LOad-BEARING, not tidiness. base.css sets
   `h3 { color: var(--kumkumam) }` at (0,0,1), and this is an h3 — so the
   shell's accent was leaking straight into the quarantined brand block,
   which §7 forbids in as many words ("kumkumam never inside it"). It went
   unnoticed on Mailchimp's cream sheet, where kumkumam happens to measure
   6.4:1 and simply looked like a choice; on Spotify's Mid Purple sheet the
   same leak measured 1.69:1 and was unreadable. Inheriting from the sheet
   means the brand's own on-surface colour wins, on every brand. */
.brand__group-head {
  color: inherit;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.7rem;
  margin: 0 0 var(--sp-l);
  /* Up from --type-micro. Note there is exactly ONE font-size here now — the
     first pass added a second declaration above the original, and the later
     one in the same rule simply won, so the change measured as no change. */
  font-size: clamp(0.85rem, 1.15vw, 1.05rem);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  /* Same correction as .pj-section__head, and found by sweeping for the
     condition rather than by spotting it: an h3 inheriting --lh-snug (1.14)
     while tracked out to --ls-wide. Only one of these wraps at 375 today —
     "The colorway reversal" — but the next one that does gets it right
     without anyone noticing this rule again. */
  line-height: 1.45;
}

/* Same trap as the container above: a surface-on-anchor pairing is only
   legible when the surface is the light one. */
.brand__group-num {
  display: grid;
  place-items: center;
  /* Sized in em off the head, so raising the head's size scales the marker
     with it rather than leaving a 12px disc beside larger type. */
  font-size: 1.15em;
  width: 1.7em;
  height: 1.7em;
  /* A SQUARE THAT CANNOT BE SQUEEZED. The head is a flex container, so the
     width above is only a starting size — the chip keeps the default
     flex-shrink and gets compressed whenever the heading beside it wraps.
     Measured at 375: "1 The same rule, applied three times" rendered its chip
     18x27 and "1 The colorway reversal" 25x27, against 27x27 everywhere the
     head fits on one line. Both read as a badge that had been sat on.
     Latent on Mailchimp and Spotify too — their heads are short enough not to
     wrap today, which is not a guarantee. */
  flex: none;
  background: var(--b-num-bg, var(--b-surface));
  color: var(--b-num-ink, var(--b-anchor));
  font-weight: 600;
}

.brand__group-head::before {
  content: '';
  height: 1px;
  background: currentColor;
  opacity: 0.28;
  flex: 1;
}

.brand__rule {
  height: 1px;
  border: 0;
  margin: 0;
  background: currentColor;
  opacity: 0.28;
  flex: 1;
}

/* --- The sheet -----------------------------------------------------------
   Content sits on the brand's own content surface rather than directly on
   the anchor colour, because that is what the surface is FOR — Founder's
   White is labelled "Content Surface / The Base" in Mailchimp's own system.
   Rebuilding the identity means using the roles as defined, not just the
   hex values. */

/* --b-on-surface, not --b-anchor, with --b-anchor as the fallback. Mailchimp's
   sheet is cream and its text is the anchor colour, so the fallback is right
   there. Spotify's sheet is Mid Purple and its anchor is Deep Purple — dark
   on dark, unreadable — so that page states the text colour explicitly. A
   brand whose container and its text are the same family needs this; one
   whose surface is light does not. */
.brand__sheet {
  background: var(--b-surface);
  color: var(--b-on-surface, var(--b-anchor));
  padding: clamp(var(--sp-m), 4vw, var(--sp-xl));
}

/* §8: "Lead each identity section with the primary mark, not the lockup."
   The mark gets the room; the lockup follows as a variant of it. */
.brand__mark {
  display: block;
  width: clamp(120px, 18vw, 200px);
  height: auto;
  max-width: none;
  margin-inline: auto;
}

.brand__lockup {
  display: block;
  width: clamp(200px, 34vw, 380px);
  height: auto;
  max-width: none;
  margin: var(--sp-l) auto 0;
}

.brand__logo-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(var(--sp-m), 4vw, var(--sp-xl));
  margin-block-start: var(--sp-xl);
  align-items: start;
}

.brand__logo-cell {
  display: grid;
  justify-items: center;
  text-align: center;
  gap: var(--sp-s);
}

/* SIZED BY HEIGHT, NOT WIDTH, and it has to be both selectors.
   This rule named `svg` only, while the markup uses `<img>` — so it matched
   nothing, base.css's `img { max-width: 100% }` took over, and the envelope
   painted at the full column width, several times the size of the mark it
   sits beside. Naming both means it cannot silently miss again if a mark is
   later inlined.

   Height rather than width because these two marks are different shapes: the
   lockup is 3.7:1 and the envelope is 1:1. Matching their widths would make
   the envelope tower over the lockup; matching their heights is what makes
   them read as the same size. */
.brand__logo-cell img,
.brand__logo-cell svg {
  height: clamp(56px, 7vw, 84px);
  width: auto;
  max-width: 100%;
}

.brand__role {
  font-size: var(--type-micro);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  font-weight: 600;
}

/* A CLOSING NOTE NEEDS AIR ABOVE IT.
   margin-block is 0 because most notes sit inside a flow that already spaces
   them — a logo cell's own gap, for instance. Two placements have nothing
   between them and what they follow, and both ran together: measured, each
   note's top sat at the same y as the previous element's bottom.

   Scoped to those two adjacencies rather than applied to every note, so the
   placements that are already spaced keep their own rhythm. The swatch grid
   gets the larger gap: it closes a whole subsection, where the substitute
   line is a caption the note continues from. */
/* ONE FLOW RULE, NOT A LIST OF ADJACENCIES.
   Space was declared pair by pair — swatches+note, substitute+note — so every
   pairing nobody had thought of came out at zero: a note touched the table,
   the lockup, the sizedemo, the comparison and the mark with no gap at all.
   Enumerating pairs cannot be completed; a flow rule cannot be incomplete. */
.brand__group > * + * {
  margin-block-start: var(--sp-m);
}

/* The specimen columns are the other prose container in the sheet, and they
   were the casualty of replacing the adjacency pairs with a group-scoped flow
   rule: .brand__substitute + .brand__note lived here, not in .brand__group,
   so Spotify's UI note went to a 0px gap. Tighter step than the group's — a
   face, its substitute line and its note are one thought, not three. */
.brand__type-col > * + * {
  margin-block-start: var(--sp-s);
}

/* A note closing a subsection gets the larger step: it ends the thought
   rather than continuing it. */
.brand__swatches + .brand__note,
.brand__table + .brand__note,
.sizedemo + .brand__note,
.ba + .brand__note,
.lockup + .brand__note {
  margin-block-start: var(--sp-l);
}

/* ONE READING MEASURE FOR THE SHEET.
   This was 34ch, which under a 1050px table or swatch row rendered a 297px
   column floating in 753px of empty space — the single biggest cause of
   blocks reading as unaligned on these pages. 66ch is a real measure and
   still a measure: matching the table's own width would mean 145-character
   lines, which is not a fix, it is the opposite defect. */
.brand__note {
  max-width: 66ch;
  margin-inline: auto;
  /* Bottom only. margin-block: 0 here sits later in the file than the group's
     flow rule and silently cancelled it, which is why notes still touched the
     mark and each other after the flow rule was added. */
  margin-block-end: 0;
  text-align: center;
  font-size: var(--type-small);
  line-height: var(--lh-snug);
  text-wrap: pretty;
}

/* --- Colour system -------------------------------------------------------
   Live swatches with hex and role, per §8. The role label is the point: a
   colour system is a set of jobs, and a swatch without its job is decoration
   with a hex code under it. */

.brand__swatches {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(var(--sp-s), 2.5vw, var(--sp-m));
}

.brand__swatch {
  display: grid;
  gap: var(--sp-2xs);
  text-align: center;
  /* ROWS SIZE TO CONTENT, NOT TO THE TALLEST CELL IN THE ROW.
     Each swatch stretches to its grid row's height, and with every row here
     auto-sized the leftover was being shared out among them — so the chip's
     row grew by however little text sat under it. Since the chip is sized by
     aspect-ratio with no width of its own, its WIDTH then followed that
     height: measured at 375, chips came out 134, 126, 130, 126 and 126 square
     inside columns that were all 125.5, with the two largest overflowing.
     align-content: start stops the distribution, so every chip is the column
     width and they all share a top edge. */
  align-content: start;
}

.brand__chip {
  /* Explicit, so aspect-ratio always derives the height FROM the width and
     never the other way round — which is how the sizing inverted above. */
  width: 100%;
  aspect-ratio: 1;
  background: var(--chip);
  /* The lightest chip is the same value as the sheet it sits on, so without
     an outline it would have no edge at all. Every chip carries the same
     hairline rather than only the pale one, so they read as one set. */
  /* --b-edge so a dark sheet can set a light hairline. Derived from the
     anchor by default, which is correct on a light surface and invisible on a
     dark one. */
  border: 1px solid var(--b-edge, color-mix(in srgb, var(--b-anchor) 45%, transparent));
}

.brand__chip-name {
  font-weight: 600;
  font-size: var(--type-small);
  /* A swatch NAME is an identifier and reads as one line or not at all —
     "Warm Paper" breaking after "Warm" makes it look like two swatches. The
     role line beneath is a description and is left free to wrap. */
  white-space: nowrap;
}

.brand__chip-hex {
  font-size: var(--type-small);
  font-variant-numeric: tabular-nums;
}

.brand__chip-role {
  font-size: var(--type-micro);
  line-height: 1.35;
  opacity: 0.78;
}

/* --- Type specimens ------------------------------------------------------ */

.brand__type {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(var(--sp-m), 4vw, var(--sp-xl));
}

.brand__type-col + .brand__type-col {
  padding-inline-start: clamp(var(--sp-m), 4vw, var(--sp-xl));
  border-inline-start: 1px solid var(--b-edge-soft, color-mix(in srgb, var(--b-anchor) 28%, transparent));
}

.brand__specimen-label {
  margin: 0 0 var(--sp-s);
  text-align: center;
  font-size: var(--type-micro);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  font-weight: 600;
  opacity: 0.75;
}

/* Set live at a real size, which is the whole argument for rebuilding this
   natively instead of screenshotting a specimen sheet. */
.brand__specimen--display {
  margin: 0;
  text-align: center;
  font-family: var(--b-display, sans-serif);
  font-weight: var(--b-display-weight, 700);
  font-size: clamp(2.5rem, 7vw, 5rem);
  line-height: 1;
  letter-spacing: var(--b-display-tracking, -0.03em);
}

.brand__specimen--body {
  margin-inline: auto;
  margin-block: 0;
  max-width: 26ch;
  text-align: center;
  font-family: var(--b-body, sans-serif);
  font-size: clamp(1.05rem, 1.6vw, 1.3rem);
  line-height: 1.4;
}

.brand__face {
  margin: var(--sp-m) 0 var(--sp-2xs);
  text-align: center;
  font-family: var(--b-body, sans-serif);
  font-size: clamp(1.1rem, 1.7vw, 1.4rem);
}

/* §8's font budget: the display face is loaded, the body face is not. Saying
   so on the page is more honest than setting it in a lookalike and letting a
   reader assume it is the real thing. */
.brand__substitute {
  display: block;
  max-width: 66ch;
  margin-inline: auto;
  text-align: center;
  font-size: var(--type-micro);
  letter-spacing: 0.04em;
  opacity: 0.7;
}


/* --- Narrow --------------------------------------------------------------
   Four swatches at 375px are four unreadable slivers, and a two-column type
   comparison stops being a comparison when the columns are 150px wide. */

@media (max-width: 46rem) {
  .brand__swatches { grid-template-columns: repeat(2, 1fr); }
  .brand__logo-grid { grid-template-columns: 1fr; }
  .brand__type { grid-template-columns: 1fr; }
  .brand__type-col + .brand__type-col {
    padding-inline-start: 0;
    padding-block-start: var(--sp-l);
    border-inline-start: 0;
    border-block-start: 1px solid var(--b-edge-soft, color-mix(in srgb, var(--b-anchor) 28%, transparent));
  }
}


/* ==========================================================================
   VIDEO — Vimeo embeds and self-hosted clips

   Two different things with one look. A Vimeo iframe is a third-party player
   the viewer decides to press; a self-hosted loop behaves like an image. Both
   sit in a 16:9 or square box that reserves its space before anything loads,
   so nothing on the page jumps as media arrives.
   ========================================================================== */

.pj-video {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--ink);
  overflow: hidden;
}

.pj-video iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* A label that has to stay visible — §10 requires "Not client work —
   self-directed" on the Supra entry. It is a status, not a caption, so it
   takes the same outlined treatment as the speculative tag rather than the
   quiet caption grey. */
.pj-label {
  display: inline-block;
  margin-block-start: var(--sp-s);
  padding: 0.4em 0.9em;
  border: 1px solid var(--kumkumam);
  color: var(--kumkumam);
  font-size: var(--type-micro);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
}

/* Self-hosted loop. Sized by the frame around it, like any image. */
.pj-loop {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}


/* --- Contained dark media ------------------------------------------------
   §10, the Flexx pair: "Both Flexx assets are pure black edge to edge and the
   neon mark needs that darkness to read. Give this section the same contained
   dark frame as Studio Eikira — two black squares dropped onto cream read as
   holes punched in the page."

   The band is the containment; the frames inside it sit on the same ink, so
   the assets' own black meets a black ground rather than a cream edge. */

.pj-contained {
  width: 100vw;
  margin-inline: calc(50% - 50vw);
  padding-block: clamp(var(--sp-l), 7vh, var(--sp-xl));
  padding-inline: var(--gutter);
  background: var(--ink);
  color: var(--paper);
}

.pj-contained__inner {
  max-width: var(--w-content);
  margin-inline: auto;
}

/* The caption inside a dark band cannot use --ink-muted, which is a darkened
   ink and would be invisible here. Paper at reduced strength instead.

   IT ALSO HAS TO BE CENTRED HERE. The centring rule is scoped to
   `.pj-figure:not(.pj-figure--bleed) .pj-caption`, and a caption inside the
   contained band is not in a .pj-figure at all — so it fell through to the
   bare .pj-caption rule, which sets no alignment, and inherited left. Every
   other caption on these pages centres; this one silently did not. */
.pj-contained .pj-caption {
  color: color-mix(in srgb, var(--paper) 78%, transparent);
  margin-inline: auto;
  text-align: center;
}

.pj-contained .frame { --frame-bg: var(--ink); }

/* --- Pure-black containment ----------------------------------------------
   THE MEDIA CANNOT BE REPAINTED, SO THE GROUND MOVES TO MEET THEM.
   Both Flexx assets are pure black edge to edge — sampled off their own
   border pixels, #000000 at alpha 255 — while --ink is #241C18, a warm
   brown-black. That is a 52-step RGB gap, which paints a visible rectangle
   where each asset meets its frame, exactly the "holes punched in the page"
   failure §10 is trying to avoid, only inverted.

   SCOPED, NOT GLOBAL. --ink is the body text colour and the ground for the
   video facades, the skip link and .frame--dark; moving the token would
   repaint all of them and would flatten Studio Eikira's warm dark, which §7
   requires to stay warm. So this is a modifier on the one band that holds
   pure-black media, and both surfaces the media touch are set: the band
   itself and the frames inside it.

   The grain overlay is unaffected and stays wanted: .has-dark-grain screens
   over the whole band at inset 0, so it lifts ground and media by the same
   amount and cannot reintroduce an edge. */
.pj-contained--pure { background: #000; }
.pj-contained--pure .frame { --frame-bg: #000; }

/* Under prefers-reduced-motion the loop must not play. A <video autoplay>
   ignores the media query on its own, so the attribute is neutralised in
   script; this rule is the visual half — the poster is what shows. §10 asks
   for exactly that. */
@media (prefers-reduced-motion: reduce) {
  .pj-loop { animation: none; }
}


/* --- The video facade ----------------------------------------------------
   The poster and its play affordance, sized by .pj-video's 16:9 box so the
   swap to an iframe changes nothing about the layout. */

.pj-video__facade {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  background: var(--ink);
  cursor: pointer;
  /* base.css does not reset button colour, and a UA stylesheet will happily
     hand this buttonface/ButtonText — stated so it cannot inherit either. */
  color: var(--paper);
}

.pj-video__facade img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The stills are 16:9 already, so cover crops nothing — it is here so a
     future poster at a different ratio fills rather than letterboxes. */
}

.pj-video__play {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  translate: -50% -50%;
  display: grid;
  place-items: center;
  width: clamp(56px, 6vw, 78px);
  aspect-ratio: 1;
  border: 1px solid var(--paper);
  border-radius: 50%;
  background: color-mix(in srgb, var(--ink) 55%, transparent);
  backdrop-filter: blur(2px);
  transition: background-color var(--t-quick) var(--ease);
}

/* The triangle, drawn rather than set as a glyph so it inherits colour. */
.pj-video__play::before {
  content: '';
  width: 0;
  height: 0;
  margin-inline-start: 0.22em;
  border-block: 0.55em solid transparent;
  border-inline-start: 0.9em solid var(--paper);
}

.pj-video__facade:hover .pj-video__play,
.pj-video__facade:focus-visible .pj-video__play {
  background: color-mix(in srgb, var(--kumkumam) 85%, transparent);
}

.pj-video__facade:focus-visible {
  outline: 2px solid var(--paper);
  outline-offset: -4px;
}

@media (prefers-reduced-motion: reduce) {
  .pj-video__play { transition: none; }
}


/* ==========================================================================
   CLICK-TO-ZOOM (§6, launch-list item 4)
   The overlay is a <dialog>; Esc, the focus trap and the backdrop come from
   the platform rather than from script.
   ========================================================================== */

.is-zoomable { cursor: zoom-in; }

.is-zoomable:focus-visible {
  outline: 2px solid var(--kumkumam);
  outline-offset: 4px;
}

.zoom {
  /* A dialog is width/height auto and centred by default; this fills the
     viewport so the backdrop area is clickable everywhere outside the image. */
  width: 100vw;
  max-width: 100vw;
  height: 100dvh;
  max-height: 100dvh;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  overflow: hidden;
}

.zoom::backdrop {
  /* --ink is not reachable from inside ::backdrop in every engine, since the
     backdrop is not a descendant of anything — the literal is deliberate. */
  background: rgba(20, 16, 14, 0.94);
}

.zoom__stage {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  overflow: auto;
  padding: clamp(var(--sp-s), 4vw, var(--sp-xl));
  /* Momentum panning on iOS when the image is larger than the screen. */
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}

/* FIT — the default. The whole image, no scrolling, so the viewer sees what
   they clicked before deciding to go closer. */
.zoom__img {
  display: block;
  max-width: 100%;
  max-height: calc(100dvh - 2 * clamp(var(--sp-s), 4vw, var(--sp-xl)));
  width: auto;
  height: auto;
  /* base.css caps every img at max-width:100%; that is exactly right for FIT
     and exactly wrong for ACTUAL, which is why the state below overrides it
     explicitly rather than relying on the cascade. */
}

/* ACTUAL — native pixels, panned. This is the state the phone case needs: a
   2880px screenshot at ~350px on a handset is eight times short of legible,
   and fitting it to the screen again would not help. */
.zoom.is-actual .zoom__img {
  max-width: none;
  max-height: none;
  width: auto;
  height: auto;
  cursor: grab;
}

.zoom.is-actual .zoom__stage { place-items: start; }

.zoom__bar {
  position: absolute;
  inset-block-end: 0;
  inset-inline: 0;
  display: flex;
  justify-content: center;
  gap: var(--sp-s);
  padding: var(--sp-s);
  pointer-events: none;   /* the bar is a container, the buttons are the UI */
}

.zoom__btn {
  border-radius: var(--radius-control);
  pointer-events: auto;
  padding: 0.7em 1.4em;
  border: 1px solid var(--paper);
  background: color-mix(in srgb, var(--ink) 70%, transparent);
  backdrop-filter: blur(6px);
  color: var(--paper);
  font-family: var(--font-body);
  font-size: var(--type-small);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  cursor: pointer;
  transition: background-color var(--t-quick) var(--ease);
}

.zoom__btn:hover { background: var(--kumkumam); border-color: var(--kumkumam); }

.zoom__btn:focus-visible {
  outline: 2px solid var(--paper);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .zoom__btn { transition: none; }
}

/* --- Live site embeds ----------------------------------------------------
   A case study that claims a redesign should be able to show both sites
   running, not two screenshots of them. Each site gets its own iframe, which
   is the seal: three stylesheets — the old site, the new site and this shell
   — cannot collide when each is in its own document.

   SCALED, NOT SHRUNK. A 570px-wide iframe renders a site's TABLET layout, so
   a naive embed would show neither of the things being compared. The frame is
   given a real viewport width (1280 desktop, 390 mobile) and then transformed
   down to fit its column, so what you see is the genuine desktop or mobile
   render at a smaller size. js/embed.js writes --embed-scale from the
   measured column; without the script the facade simply never opens.

   NOTHING LOADS UNTIL ASKED. Both frames sit behind a facade — the same
   pattern the Vimeo embeds use — so the page costs nothing until a reader
   opts in, and the external one never touches the network before that. */

.pj-embed-pair {
  display: grid;
  gap: var(--sp-l);
}

/* STACKED, NOT SIDE BY SIDE. In a row each frame got half the column, which
   made the whole point of embedding a live site — seeing it — half as
   effective, and left a gap between two boxes of different natural shapes.
   Full width each, desktop above mobile: the desktop frame's scale goes from
   0.36 to 0.89, which is most of the way to actual size. */

/* max-width: none is the operative line. base.css caps every <p> at
   --measure, so this label was a 62ch box hugging the left of a 1137px stage
   with its text centred inside itself — 346px off the page axis while
   computing as text-align: center. It labels the frame, so it spans it. */
.pj-embed__label {
  margin: 0 0 var(--sp-2xs);
  max-width: none;
  text-align: center;
  font-size: var(--type-micro);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  font-weight: 600;
  color: var(--kumkumam);
}

.pj-embed__note {
  margin: var(--sp-2xs) 0 0;
  font-size: var(--type-small);
  line-height: var(--lh-snug);
  color: var(--ink-muted);
}

/* The window. overflow hidden is what crops the scaled frame to the box. */
.pj-embed__stage {
  position: relative;
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--rule);
  /* ONE HEIGHT FOR THE PAIR, not one ratio each. Sized by ratio, a 1280x860
     desktop stage came out 310px tall beside a 390x720 mobile stage at 720 —
     a 410px mismatch that read as two misaligned blocks rather than a
     comparison. Both stages are now the same height and each shows a
     viewport-accurate slice of its own site; js/embed.js gives the frame the
     height that fills the box at its scale. */
  /* Taller now that a stage has the full column: there is room to show more
     of each site without the frame competing with anything beside it. */
  height: var(--embed-box-h, clamp(380px, 62vh, 720px));
}

.pj-embed--mobile .pj-embed__stage {
  /* A phone frame is shown at phone size, not stretched to fill a desktop
     column. Capping the width at the viewport it renders means the scale
     lands at 1 and the pixels are the site's own. */
  max-width: 390px;
  margin-inline: auto;
}

/* The frame is laid out at its real viewport width and then scaled. Both the
   width and the scale origin have to agree or the render lands off-centre. */
.pj-embed__stage iframe {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  width: var(--embed-w, 1280px);
  /* Written by the script so the scaled frame exactly fills its stage. */
  height: var(--embed-h, 860px);
  border: 0;
  transform: scale(var(--embed-scale, 1));
  transform-origin: top left;
}

.pj-embed--mobile .pj-embed__stage iframe { --embed-w: 390px; }

/* A DECK IS A FIXED 16:9 CANVAS, not a site with a scroll. Its stage takes
   the canvas's own ratio instead of the shared pair height, so the whole
   slide viewer is visible at any column width and nothing is cropped. */
.pj-embed__stage--deck {
  height: auto;
  aspect-ratio: 16 / 9;
}

/* Below the embed: the file itself, for a phone, where a 1600px canvas
   scaled into a 375px column is too small to read. */
.pj-embed__open {
  margin: var(--sp-2xs) 0 0;
  max-width: none;
  text-align: center;
  font-size: var(--type-small);
}

/* --- The facade ---------------------------------------------------------
   A real button: keyboard-operable, announced, and replaced by the iframe on
   activation rather than layered over it. */
.pj-embed__facade {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  gap: var(--sp-xs);
  width: 100%;
  height: 100%;
  padding: var(--sp-m);
  border: 0;
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
  text-align: center;
  font: inherit;
  transition: background-color var(--t-quick) var(--ease);
}

/* THE WHOLE PANEL IS THE HIT TARGET, AND IT HAS TO LOOK LIKE IT.
   The button is already inset:0 over the stage, so a tap anywhere has always
   worked — but a line of small caps centred on a 520px panel reads as a
   caption, so a reader could sit in front of a control they never tried. The
   ground shifts and the inner ring appears on hover over the ENTIRE panel,
   which is what tells you the whole thing is live rather than just the words. */
.pj-embed__facade:hover,
.pj-embed__facade:focus-visible {
  background: color-mix(in srgb, var(--surface) 88%, var(--kumkumam));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--kumkumam) 40%, transparent);
}

/* Inset by 4px so the ring lands on --surface rather than half-off the
   stage's own 1px border. Measured on the real ground rather than assumed:
   kumkumam #9E2B25 on surface #E4DED3 is 5.55:1, well clear of the 3:1 that
   1.4.11 asks of a focus indicator. Both facade grounds on this page are
   --surface, so that one figure covers the BEFORE and AFTER panels alike. */
.pj-embed__facade:focus-visible {
  outline: 2px solid var(--kumkumam);
  outline-offset: -4px;
}

.pj-embed__facade-title {
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 1.8vw, 1.5rem);
  line-height: var(--lh-snug);
}

/* THE SITE ALREADY HAD A BUTTON, so this is not a new one. Same fill, same
   paper text, same uppercase at --ls-wide, and the same --radius-control as
   .pj-cta__link — the deliberate exception to the square-corner rule, which
   applies to controls and only to controls.

   It is a <span>, not a nested button: the real control is the panel around
   it, and this is the affordance that says so. min-height 44px keeps the
   visible target at the pointer floor even though the actual hit area is the
   whole 520px panel. */
.pj-embed__facade-cue {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  justify-self: center;
  min-height: 44px;
  padding: 0.75em 1.9em;
  border: 1px solid var(--kumkumam);
  border-radius: var(--radius-control);
  background: var(--kumkumam);
  color: var(--paper);
  font-size: var(--type-micro);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  transition: background-color var(--t-quick) var(--ease),
              border-color var(--t-quick) var(--ease);
}

/* Driven by the PANEL's hover, not the pill's, so pointing anywhere on the
   facade lights the control. */
.pj-embed__facade:hover .pj-embed__facade-cue,
.pj-embed__facade:focus-visible .pj-embed__facade-cue {
  background: var(--kumkumam-lit);
  border-color: var(--kumkumam-lit);
}

/* §2: a full bypass, not a faster version. The states still change — they
   are information, not decoration — they just arrive at once. */
@media (prefers-reduced-motion: reduce) {
  .pj-embed__facade,
  .pj-embed__facade-cue { transition: none; }
}

