/* ========================================================================
   Fifty — punk-zine design system
   ------------------------------------------------------------------------
   Shared stylesheet for every page under docs/:
     - docs/index.html                (landing / theme rows)
     - docs/concepts/index.html       (concept queue grid)
     - docs/<theme>/[<page>/]index.html  (Playground redirectors)
     - docs/snaps/index.html + per-theme grids load this via the snap CSS

   Direction: warm cream paper, near-black ink, an electric cobalt accent,
   a lime highlighter, and a hot-pink spot color used sparingly. Display
   sets in Archivo Black (heavy industrial sans), body in Inter, brush
   script accents in Caveat Brush. Reads as an indie zine with bold
   lockups and highlighter callouts — the visual identity that ships when
   "magazine quarterly" gets traded in for "stencilled fanzine".

   Token contract: every page-level color and font flows from a single
   :root block below. Renaming one token cascades through the landing,
   concept queue, redirector, AND the snap gallery (which loads this
   same stylesheet plus its own snap-specific overrides). The legacy
   `--serif-display`/`--serif-text` aliases are kept as aliases of the
   new `--display`/`--body` tokens so any caller that hasn't migrated
   yet still picks up the new fonts.

   Generated and regenerated by bin/build-redirects.py — edits here are
   safe (the script copies this file verbatim).
   ======================================================================== */

:root {
  color-scheme: light;

  /* Palette — five paints, no in-between greys for chrome.
     - paper:     warm cream sheet (slight cream cast keeps it off-white
                  so the black ink reads as ink, not as #000-on-#fff
                  glare).
     - ink:       the heavy black for text + rules + pill straps.
     - accent:    electric cobalt — used for the wordmark period, hover
                  borders, the boot spinner, and per-row arrows.
     - highlight: lime highlighter — sits BEHIND key words/CTAs as a
                  thick rectangle so the foreground ink stays legible.
                  Never used for text on cream (would fail contrast).
     - hot:       hot pink — reserved for one ornament per page (e.g.
                  the wordmark's diacritic dot) so it stays a punctuation
                  mark, not a theme color.
     - muted:     softened ink for inactive metadata. */
  --paper: #f4efe1;
  --ink: #0a0a0a;
  --rule: #0a0a0a;
  --accent: #2c52f5;
  --highlight: #d4ff34;
  --hot: #ff3fa3;
  --muted: #5c5c5c;

  --hairline: 2px;
  --measure: 38rem;

  /* Type stack — three weights doing four jobs.
     - display: the heavy industrial grotesk that sets the wordmark, the
                theme-row names, and section heads. ALL CAPS feels too
                shouty at this weight, so we keep these in lowercase
                wherever the previous serif system did.
     - body:    the workhorse text face. Inter at 400/500/700 covers the
                deck, the lede, the CTA labels, and every form of
                running prose.
     - script:  brush-script accent, used SPARINGLY — the wordmark
                signature line, the brand-mark over the cobalt period,
                and the eyebrow tagline. Never used for body or for any
                text smaller than ~1.5rem (the strokes blow out below
                that and lose all of their swagger).
     - mono:    IBM Plex Mono survives only for `<code>` blocks and the
                tiny strap-pill metadata. Everywhere else has migrated to
                Inter caps so the mono sticks out as code, not as decor. */
  --display: 'Archivo Black', 'Helvetica Neue', Arial, sans-serif;
  --body: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --script: 'Caveat Brush', 'Caveat', 'Marker Felt', cursive;
  --mono: 'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, Monaco, Consolas, monospace;

  /* Backwards-compatible aliases — bin/build-snap-gallery.py and
     anything else that still references the old "magazine cover" token
     names will pick up the new fonts automatically. New code should use
     the `--display` / `--body` names directly. */
  --serif-display: var(--display);
  --serif-text: var(--body);
}

*, *::before, *::after { box-sizing: border-box; }

html, body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  font-size: 17px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

a { color: inherit; }
img { max-width: 100%; display: block; }
code { font-family: var(--mono); font-size: .9em; background: transparent; }

/* Every top-level page region (masthead, main, colophon) shares the same
   centered "page" frame so the rule lines, headlines, and footer columns
   all hang off the same gutters. The horizontal padding scales with the
   viewport — never less than 1.25rem on phones, never more than 3rem on
   wide displays — so the content always has at least a thumb's width of
   breathing room from the edge of the screen. */
.page-frame,
main,
.masthead,
.colophon {
  max-width: 90rem;
  margin-left: auto;
  margin-right: auto;
  padding-left: clamp(1.25rem, 4vw, 3rem);
  padding-right: clamp(1.25rem, 4vw, 3rem);
}

/* ------------------------------------------------------------------------
   Strap pill — small black rounded rectangle with white all-caps Inter.
   Reusable on every page wherever a tiny eyebrow / kicker / status label
   is needed. Replaces the old "mono caps with a hairline rule" eyebrow
   pattern: in the punk-zine system, those tags are stickers stuck to
   the page, not headers ruled into it.
   ------------------------------------------------------------------------ */
.strap {
  display: inline-block;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--body);
  font-weight: 700;
  font-size: .68rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  padding: .35rem .7rem .3rem;
  border-radius: 999px;
  text-decoration: none;
  line-height: 1;
}
.strap.cobalt { background: var(--accent); }
.strap.hot { background: var(--hot); color: var(--ink); }

/* `<mark>`-style highlighter. Wrap a phrase in `<span class="hi">…</span>`
   to drop a thick lime stripe behind it — the same visual move the mockup
   uses under the brush-script wordmark. We use a multi-stop linear
   gradient (transparent → highlight → highlight → transparent at the
   bottom 70%) so the highlighter only paints behind the lower 2/3 of the
   line, mimicking a real wide-tip marker pulled across a word. */
.hi {
  background-image: linear-gradient(
    180deg,
    transparent 0%,
    transparent 30%,
    var(--highlight) 30%,
    var(--highlight) 92%,
    transparent 92%,
    transparent 100%
  );
  padding: 0 .15em;
  /* Tighten line-box around the highlight so multi-line wraps don't get
     visual gaps between stripes. */
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}

/* ------------------------------------------------------------------------
   Masthead — top strip on every page (left tag · center tag · right tag).
   Replaces the old hairline-rule mono-caps strip with a heavier rule
   below the strip and Inter caps in the slots themselves. The right
   slot's repo link gets a hover state in the lime highlighter so the
   "click to GitHub" affordance reads even at thumbnail size.
   ------------------------------------------------------------------------ */
.masthead {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 1rem;
  padding-top: 1rem;
  padding-bottom: 1rem;
  border-bottom: var(--hairline) solid var(--rule);
  font-family: var(--body);
  font-weight: 700;
  font-size: .72rem;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.masthead a { text-decoration: none; }
.masthead a:hover { background: var(--highlight); padding: 0 .15em; }
.masthead .left { text-align: left; }
.masthead .center { text-align: center; white-space: nowrap; }
.masthead .right { text-align: right; }

@media (max-width: 720px) {
  .masthead { grid-template-columns: 1fr; gap: .25rem; text-align: left; }
  .masthead .left, .masthead .center, .masthead .right { text-align: left; }
}

/* ------------------------------------------------------------------------
   Cover — split-lockup zine cover.
   ------------------------------------------------------------------------
   Layout choices and why:

   1. WORDMARK + DECK SHARE A BASELINE in a 2-column grid (.lockup). The
      hairline rule sits BELOW the lockup (as the lockup's border-bottom)
      — not between wordmark and deck. The shared baseline is what makes
      it read as a real cover lockup instead of two stacked elements.
      `align-items: end` pins the wordmark's descender baseline to the
      deck's last-line baseline. Below 880px the lockup collapses to a
      single column and the wordmark shrinks to keep its descender from
      blowing out narrow viewports.

   2. WORDMARK is now Archivo Black (the heavy industrial grotesk) at
      clamp(5rem, 14vw, 12rem). That weight is what carries the
      "stencilled fanzine" register the mockup is shooting for; the
      previous DM Serif Display masthead read as a quarterly. The
      cobalt period is unchanged — it's the one piece of the old
      visual identity we kept on purpose, because the dot is the
      brand mark, not the wordmark itself.

   3. SCRIPT SIGNATURE sits under the wordmark in Caveat Brush — a
      hand-lettered "on rails." with a lime highlighter stripe behind
      "rails", mirroring the mockup's "WordPress." brush mark. Keeps
      the type system from feeling like "two grotesks and nothing else"
      and gives the page an obvious hand in the loop. The signature is
      `aria-hidden="true"` so screen readers don't read decoration as
      heading content.

   4. DECK is now Inter medium (was DM Serif Text italic) and uses
      `text-wrap: balance` to wrap evenly across viewports. Italic is
      gone deliberately: italic in a sans-only system reads as accident,
      and the brush-script signature is doing the "human voice" job
      that italic used to do. A `<span class="hi">…</span>` wraps the
      key emphasis phrase ("closing that gap") so it gets the lime
      stripe — the punk-zine equivalent of italicising for emphasis.

  5. CTA-ROW renders as a set of fat pill buttons. The first CTA gets
     the lime highlighter as a filled background — the visual primary —
     and inverts to ink-on-lime on hover.
   ------------------------------------------------------------------------ */
.cover {
  padding: clamp(1.5rem, 4vw, 3rem) 0 clamp(2rem, 4vw, 3rem);
  --cover-rhythm: clamp(1.25rem, 3vw, 2.25rem);
}
.cover > * + * { margin-top: var(--cover-rhythm); }

.cover .lockup {
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  align-items: end;
  gap: clamp(1.5rem, 4vw, 3rem);
  padding-bottom: clamp(1.5rem, 3.5vw, 2.5rem);
  border-bottom: var(--hairline) solid var(--rule);
}

/* Brand column inside the lockup: wordmark stacked over the brush-script
   signature. We use a flex column rather than putting the signature
   inside the wordmark `<h1>` so screen readers don't read the signature
   as part of the heading text — it's decoration, not content. */
.cover .brand {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0;
}

.cover .wordmark {
  font-family: var(--display);
  font-weight: 400; /* Archivo Black is already at weight 900 by design */
  font-size: clamp(5rem, 14vw, 12rem);
  line-height: .85;
  letter-spacing: -.04em;
  margin: 0;
  text-transform: lowercase;
}

/* Brush-script signature stuck under the wordmark — the line "on rails."
   in Caveat Brush with a lime highlighter behind "rails". Negative top
   margin pulls it up so it tucks against the wordmark's descender; a
   ~3deg rotation keeps it from looking like it was set on a baseline
   grid. The highlighter stripe is the same visual move the mockup uses
   under its brush-script wordmark. */
.cover .signature {
  font-family: var(--script);
  font-weight: 400;
  font-size: clamp(1.75rem, 4.2vw, 3.25rem);
  line-height: 1;
  letter-spacing: -.005em;
  margin: -.35em 0 0 .25em;
  transform: rotate(-3deg);
  transform-origin: 0 50%;
  pointer-events: none;
}

.cover .deck {
  font-family: var(--body);
  font-weight: 500;
  font-size: clamp(1.4rem, 2.6vw, 2.1rem);
  line-height: 1.2;
  letter-spacing: -.01em;
  margin: 0 0 .25rem;
  /* `text-wrap: balance` distributes characters evenly across all lines,
     so every viewport gets a clean wrap. Markup ships as a single
     sentence with no hardcoded <br> tags. */
  text-wrap: balance;
}

.cover .lede {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 clamp(1.5rem, 4vw, 3.5rem);
  max-width: 56rem;
  font-family: var(--body);
  font-weight: 400;
  font-size: 1rem;
  line-height: 1.6;
}
.cover .lede p { margin: 0; }
.cover .lede p + p { margin-top: 1rem; }
.cover .lede a {
  color: var(--ink);
  text-decoration: none;
  font-weight: 500;
  border-bottom: 2px solid var(--accent);
  padding-bottom: 1px;
}
.cover .lede a:hover { background: var(--highlight); border-bottom-color: var(--ink); }

.cover .cta-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .65rem;
}
/* The first CTA in the row is the visual primary — gets the lime
   highlighter behind it so the page has one obvious "do this" button. */
.cover .cta-row .cta {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  min-height: 2.65rem;
  padding: .62rem 1.15rem .58rem;
  border: var(--hairline) solid var(--ink);
  border-radius: 999px;
  background: transparent;
  font-weight: 700;
  font-size: .85rem;
  letter-spacing: .12em;
  line-height: 1;
  text-transform: uppercase;
}
.cover .cta-row .cta:first-child {
  background: var(--highlight);
}
.cover .cta-row .cta:first-child:hover,
.cover .cta-row .cta:first-child:focus-visible {
  background: var(--ink);
  color: var(--paper);
  outline: none;
}
.cover .cta-row .cta:first-child:hover .arrow,
.cover .cta-row .cta:first-child:focus-visible .arrow { color: var(--highlight); }

/* Below the split-lockup breakpoint, the wordmark stacks above the deck
   and shrinks to keep its descender inside the viewport. The script
   signature sits BELOW the wordmark and its rotation is dialled down
   so it doesn't push past the column edge on phones. */
@media (max-width: 880px) {
  .cover .lockup {
    grid-template-columns: 1fr;
    align-items: start;
    gap: 1rem;
  }
  .cover .wordmark { font-size: clamp(5rem, 22vw, 9rem); }
  .cover .signature {
    margin-top: -.25em;
    font-size: clamp(1.75rem, 7vw, 2.5rem);
    transform: rotate(-2deg);
  }
}

@media (max-width: 720px) {
  .cover .lede { grid-template-columns: 1fr; }
  .cover .lede > p:first-child + p { margin-top: 1rem; }
  .cover .cta-row { flex-direction: column; align-items: flex-start; gap: .85rem; }
}

.cta {
  font-family: var(--body);
  font-weight: 500;
  font-size: 1.05rem;
  text-decoration: none;
  color: var(--ink);
  display: inline-flex;
  align-items: baseline;
  gap: .55rem;
  border-bottom: 2px solid var(--ink);
  padding-bottom: 2px;
  transition: background-color .12s ease, border-color .12s ease, color .12s ease;
}
.cta:hover, .cta:focus-visible {
  background: var(--highlight);
  outline: none;
}
.cta .arrow { color: var(--accent); font-size: 1.05em; line-height: 1; }

/* ------------------------------------------------------------------------
   Theme rows — replaces the old grid of pill-CTA cards.
   Each theme is a full-width row: strap pill index · giant name · blurb
   · arrow. Hovering a row paints the entire row with the lime
   highlighter, so the row reads as one big sticker tagged with "OPEN
   THIS ONE".
   ------------------------------------------------------------------------ */
.theme-rows { border-top: var(--hairline) solid var(--rule); }

.theme-row {
  display: grid;
  grid-template-columns: 7rem minmax(0, 1fr) minmax(15rem, 24rem) 7rem;
  align-items: center;
  gap: clamp(1rem, 3vw, 2.5rem);
  padding: clamp(1.5rem, 3vw, 2.25rem) clamp(.5rem, 2vw, 1rem);
  border-bottom: var(--hairline) solid var(--rule);
  text-decoration: none;
  color: inherit;
  transition: background-color .12s ease;
}
.theme-row:hover, .theme-row:focus-visible { background: var(--highlight); outline: none; }

.theme-row .index {
  /* The index slot uses the strap-pill pattern (matches the .strap
     helper) — a black rounded sticker with white tiny caps. Inlined
     here rather than composed via .strap because the index is auto-
     generated by build-redirects.py and adding the class there would
     spread design knowledge into the python. */
  font-family: var(--body);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .14em;
  font-size: .68rem;
  align-self: start;
  justify-self: start;
  background: var(--ink);
  color: var(--paper);
  padding: .35rem .7rem .3rem;
  border-radius: 999px;
  white-space: nowrap;
  line-height: 1;
}
.theme-row:hover .index { background: var(--accent); }

.theme-row .name {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(2.5rem, 8vw, 6.5rem);
  line-height: .9;
  letter-spacing: -.04em;
  margin: 0;
  text-transform: lowercase;
}

.theme-row .blurb {
  font-family: var(--body);
  font-weight: 400;
  font-size: 1.05rem;
  line-height: 1.45;
  margin: 0;
}

.theme-row .open {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: .35rem;
  text-align: right;
}
.theme-row .open .arrow {
  color: var(--accent);
  font-size: 4rem;
  line-height: .85;
  font-family: var(--display);
  transition: transform .15s ease, color .12s ease;
}
.theme-row:hover .open .arrow,
.theme-row:focus-visible .open .arrow { transform: translateX(.35rem); color: var(--ink); }
.theme-row .open .label {
  font-family: var(--body);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .14em;
  font-size: .65rem;
}

@media (max-width: 720px) {
  .theme-row {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-rows: auto auto auto;
    gap: .5rem 1rem;
    padding: 1.25rem .5rem;
  }
  .theme-row .index { grid-column: 1 / -1; }
  .theme-row .name { grid-column: 1; grid-row: 2; font-size: 3.25rem; }
  .theme-row .open { grid-column: 2; grid-row: 2; align-self: end; }
  .theme-row .open .arrow { font-size: 2.5rem; }
  .theme-row .open .label { display: none; }
  .theme-row .blurb { grid-column: 1 / -1; grid-row: 3; }
}

/* ------------------------------------------------------------------------
   Colophon — page footer. Three columns of Inter caps. Repo link on the
   left gets the highlighter hover so it matches the masthead.
   ------------------------------------------------------------------------ */
.colophon {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1rem;
  padding-top: 1.5rem;
  padding-bottom: 2.5rem;
  margin-top: clamp(1.5rem, 3vw, 2.5rem);
  border-top: var(--hairline) solid var(--rule);
  font-family: var(--body);
  font-weight: 700;
  font-size: .7rem;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.colophon .center { text-align: center; }
.colophon .right { text-align: right; }
.colophon a { text-decoration: none; }
.colophon a:hover { background: var(--highlight); padding: 0 .15em; }
@media (max-width: 720px) {
  .colophon { grid-template-columns: 1fr; text-align: left; padding-top: 1.25rem; padding-bottom: 2rem; }
  .colophon .center, .colophon .right { text-align: left; }
}

/* ------------------------------------------------------------------------
   Concept queue page — sub-hero, then a hairline grid of concept cells.
   Same type/palette system as the cover; the subhero replaces the old
   serif eyebrow with a strap pill and the heading drops to Archivo
   Black to match the wordmark register.
   ------------------------------------------------------------------------ */
.subhero { padding: clamp(2rem, 5vw, 3.5rem) 0 clamp(1rem, 2vw, 1.5rem); }
.subhero .eyebrow {
  display: inline-block;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--body);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .14em;
  font-size: .68rem;
  padding: .35rem .7rem .3rem;
  border-radius: 999px;
  margin: 0 0 1rem;
  line-height: 1;
}
.subhero h1 {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(3rem, 9vw, 8rem);
  line-height: .9;
  letter-spacing: -.04em;
  margin: 0 0 1rem;
  text-transform: lowercase;
}
.subhero .deck {
  font-family: var(--body);
  font-weight: 500;
  font-size: clamp(1.15rem, 2.2vw, 1.55rem);
  line-height: 1.4;
  margin: 0;
  max-width: 50rem;
}
.subhero .stats {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem 2.5rem;
  margin-top: 1.5rem;
  font-family: var(--body);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .12em;
  font-size: .72rem;
}
.subhero .stats strong { font-weight: 700; color: var(--accent); }

.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  border-top: var(--hairline) solid var(--rule);
  padding: 1.25rem 0 .85rem;
  font-family: var(--body);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .14em;
  font-size: .72rem;
}
.section-head h2 {
  margin: 0;
  font-family: var(--body);
  font-weight: 700;
  font-size: .72rem;
  letter-spacing: .14em;
}
.section-head .count strong { font-weight: 700; color: var(--accent); }

.concept-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: 0;
  border-top: var(--hairline) solid var(--rule);
  border-left: var(--hairline) solid var(--rule);
}
/* `.concept` is a wrapper <div> containing two independent <a>s:
   `.thumb` (opens the lightbox) and `.body` (opens the prefilled
   issue / live demo). The wrapper itself is no longer interactive —
   visual cohesion comes from the shared border + the `.concept:has(.body:hover)`
   highlight below. */
.concept {
  display: flex;
  flex-direction: column;
  color: inherit;
  border-right: var(--hairline) solid var(--rule);
  border-bottom: var(--hairline) solid var(--rule);
  background: var(--paper);
  position: relative;
  transition: background-color .12s ease;
}
.concept .thumb {
  display: block;
  aspect-ratio: 4 / 3;
  /* `contain` (not cover) so the entire mockup is visible — concept
     mockups are full-page renders in unpredictable aspect ratios, and
     cropping them turns the queue into a wall of identical-looking page
     fragments. The faint cream background letterboxes whatever doesn't
     fill the cell. */
  background: #ece6d3 center / contain no-repeat;
  border-bottom: var(--hairline) solid var(--rule);
  /* The thumb is a link upgraded to a lightbox trigger by JS. The
     zoom-in cursor + accent-on-hover outline telegraph that clicking
     here only opens a preview — not the action that the body link
     fires. */
  cursor: zoom-in;
  outline: 2px solid transparent;
  outline-offset: -4px;
  transition: outline-color .12s ease;
}
.concept .thumb:hover,
.concept .thumb:focus-visible { outline-color: var(--accent); }
.concept .body {
  padding: .9rem 1rem 1rem;
  display: flex;
  flex-direction: column;
  gap: .5rem;
  flex: 1;
  text-decoration: none;
  color: inherit;
}
.concept h3 {
  margin: 0;
  font-family: var(--display);
  font-weight: 400;
  font-size: 1.65rem;
  line-height: 1;
  letter-spacing: -.02em;
  text-transform: lowercase;
}
.concept .meta {
  margin: 0;
  font-family: var(--body);
  font-weight: 700;
  font-size: .65rem;
  text-transform: uppercase;
  letter-spacing: .12em;
  display: flex;
  flex-wrap: wrap;
  gap: .35rem .75rem;
  align-items: center;
}
.concept .pick {
  margin: auto 0 0;
  padding-top: .75rem;
  font-family: var(--body);
  font-weight: 700;
  font-size: .68rem;
  text-transform: uppercase;
  letter-spacing: .12em;
}
.concept .pick .arrow { color: var(--accent); }
/* Lime card-wide hover only fires when the BODY link is hovered, not
   the thumb — keeps the visual unit cohesive when you mouse over the
   action area, while the thumb's preview-only intent is signalled by
   its own zoom-in cursor + accent outline (see `.concept .thumb` above).
   `:has()` is widely supported in 2026; older browsers degrade to the
   per-element hover effects, which is acceptable. */
.concept:has(.body:hover) { background: var(--highlight); }
.concept:has(.body:hover) .pick { color: var(--ink); }
/* Single keyboard-focus outline on the whole card whenever any focusable
   child (.thumb or .body) is keyboard-focused. Avoids stacking two
   outlines when the thumb itself has its own focus-visible rule. */
.concept:has(:focus-visible) { outline: 2px solid var(--accent); outline-offset: -2px; }

/* Concept queue badge — scoped under `.concept` on purpose. The snap
   gallery (bin/build-snap-gallery.py) defines its own bare `.cell .badge`
   selectors with a different visual register (mono caps, outlined, no
   background) and the two systems were colliding when this rule used
   the bare `.badge` selector — `background: var(--ink)` and the pill
   border-radius leaked into snap-gallery `.cell .badge` cells, which
   only declare border + color, so `.badge.flow` (ink-on-ink) and
   `.badge.warn` ended up rendering as illegible solid black blobs.
   Keeping the new badge style scoped to `.concept` keeps each surface's
   badges visually consistent within their own page without leaking. */
.concept .badge {
  display: inline-block;
  background: var(--ink);
  color: var(--paper);
  border: none;
  padding: .22rem .55rem .18rem;
  font-family: var(--body);
  font-weight: 700;
  font-size: .58rem;
  text-transform: uppercase;
  letter-spacing: .14em;
  line-height: 1;
  border-radius: 999px;
}
.concept .badge.shipped { background: var(--accent); color: var(--paper); }

.concept.shipped { opacity: .55; }
.concept.shipped:hover { opacity: 1; }

/* Concept-thumbnail lightbox.

   Native <dialog> in modal mode (showModal) provides the backdrop,
   focus trap, and Esc-to-close behaviour out of the box; we just
   style it. The dialog box itself is transparent so the image and
   the close button float visually over the dimmed page — clicking
   anywhere outside the image (the dimmed padding) also closes it,
   handled in the page-level script.

   The mockup sits inside the dialog at full natural size up to a
   90vw / 88vh ceiling; the cream paper background letterboxes any
   leftover dialog area so it looks intentional rather than a glitch
   when the image is much narrower than the cap. */
.lightbox {
  border: 0;
  padding: 0;
  background: transparent;
  margin: auto;
  max-width: 90vw;
  max-height: 90vh;
  overflow: visible;
}
.lightbox::backdrop { background: rgba(10, 10, 10, .88); }
.lightbox img {
  display: block;
  max-width: 90vw;
  max-height: 88vh;
  width: auto;
  height: auto;
  background: var(--paper);
  border: var(--hairline) solid var(--ink);
  box-shadow: 0 18px 48px rgba(0, 0, 0, .35);
}
/* Hot-pink close chip in the top-right of the viewport — the only
   place in the design system the spot colour earns its keep, and
   high-contrast against both the dimmed backdrop and any mockup. */
.lightbox .close {
  position: fixed;
  top: 1rem;
  right: 1rem;
  width: 2.75rem;
  height: 2.75rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--hot);
  color: var(--ink);
  border: var(--hairline) solid var(--ink);
  border-radius: 999px;
  font-family: var(--display);
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
  transition: transform .12s ease;
}
.lightbox .close:hover,
.lightbox .close:focus-visible { transform: scale(1.06); outline: none; }

.empty-note {
  font-family: var(--body);
  font-style: normal;
  font-size: 1.05rem;
  margin: 1.5rem 0 0;
  color: var(--muted);
}

/* ------------------------------------------------------------------------
   Redirector / spinner page — used by every docs/<theme>/[<page>/]index.html
   while Playground boots in the background. Same masthead + colophon as
   the landing; the boot card sits centered with a lime spinner pill and
   the theme name set in Archivo Black.
   ------------------------------------------------------------------------ */
body.redirector { min-height: 100vh; display: flex; flex-direction: column; }
body.redirector main {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
  padding: 2rem clamp(1.25rem, 4vw, 3rem);
}
.boot { max-width: 38rem; margin: 0 auto; }
.boot .eyebrow {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--body);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .14em;
  font-size: .72rem;
  padding: .4rem .8rem .35rem;
  border-radius: 999px;
  margin: 0 0 1rem;
  line-height: 1;
}
.boot h1 {
  font-family: var(--display);
  font-size: clamp(3rem, 9vw, 6.5rem);
  font-weight: 400;
  line-height: .9;
  letter-spacing: -.04em;
  margin: 0 0 1rem;
  text-transform: lowercase;
}
.boot .lede {
  font-family: var(--body);
  font-weight: 400;
  font-size: 1.1rem;
  line-height: 1.55;
  margin: 0 0 1.5rem;
  color: var(--ink);
}
.boot .fallback {
  font-family: var(--body);
  font-weight: 700;
  font-size: .72rem;
  text-transform: uppercase;
  letter-spacing: .12em;
  margin: 0;
}
.boot .fallback a {
  color: var(--ink);
  text-decoration: none;
  border-bottom: 2px solid var(--accent);
  padding-bottom: 1px;
}
.boot .fallback a:hover { background: var(--highlight); }
.boot hr {
  border: 0;
  border-top: var(--hairline) solid var(--rule);
  margin: 2rem auto;
  width: 4rem;
}
.spinner {
  display: inline-block;
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--highlight);
  animation: pulse 1.4s ease-in-out infinite;
  vertical-align: middle;
}
@keyframes pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: .25; transform: scale(.65); }
}
@media (prefers-reduced-motion: reduce) { .spinner { animation: none; } }

/* ------------------------------------------------------------------------
   Concept queue v2 — palette dots, tag chips, filter strip, detail page.

   This block was added when the concept queue grew past 100 entries and
   started reading as an undifferentiated wall of thumbnails. Each
   concept now ships:

     * `.palette-dots`   — 5 auto-extracted hex circles per card so the
                           color identity is visible at thumbnail size.
     * `.chip`           — small-caps sector / era / type tag chips.
     * `.concept-filters` — collapsible filter strip above the grid;
                           buttons toggle data-attr matches with no JS
                           framework.
     * `.concept-detail__*` — per-concept detail page (carousel, palette
                              strip, type spec, CTAs).

   The whole block is scoped under existing `.concept` / `.concept-grid`
   parents wherever possible so it inherits the magazine-cover design
   tokens (--paper, --ink, --accent, --hairline) and a future redesign
   can swap them out in one place.
   ------------------------------------------------------------------------ */

.palette-dots {
  display: inline-flex;
  gap: .25rem;
  align-items: center;
  margin-top: .25rem;
  min-height: .9rem;
}
.palette-dots .dot {
  width: .9rem;
  height: .9rem;
  border-radius: 999px;
  border: var(--hairline) solid rgba(0, 0, 0, .25);
  display: inline-block;
}

/* Generic chip — used for sector/era/type/hero tags everywhere in the
   concept system. Distinct from `.concept .badge` (which is the
   filled "In queue" / "Shipped" pill) so the two register types
   don't fight inside the meta line. */
.chip {
  display: inline-flex;
  align-items: center;
  font-family: var(--body);
  font-weight: 700;
  font-size: .58rem;
  text-transform: uppercase;
  letter-spacing: .12em;
  line-height: 1;
  padding: .22rem .55rem .18rem;
  border: var(--hairline) solid var(--rule);
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink);
}
.chip + .chip { margin-left: .25rem; }
.chip-sector { border-color: var(--ink); }
.chip-era    { border-color: var(--accent); }
.chip-type   { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.chip-hero   { background: var(--highlight); border-color: var(--ink); }
.chip-palette { font-variant-caps: all-small-caps; }

/* The redesigned concept-card meta line packs more into the same
   vertical real estate. Keep wrapping clean by letting chips flow on
   their own row when there isn't room. */
.concept .meta { row-gap: .35rem; }

/* Filter strip — sits between the subhero and the first section
   header. Each axis is a collapsible <details> so the strip stays
   compact on narrow viewports without JS. */
.concept-filters {
  margin: 1.5rem 0 1rem;
  padding: 1rem 0 1.25rem;
  border-top: var(--hairline) solid var(--rule);
  border-bottom: var(--hairline) solid var(--rule);
  display: flex;
  flex-wrap: wrap;
  gap: .75rem 1.5rem;
  align-items: flex-start;
  font-family: var(--body);
}
.concept-filters__group {
  /* Keep each filter group on its own line of summaries until opened. */
  flex: 0 0 auto;
}
.concept-filters__group > summary {
  cursor: pointer;
  font-weight: 700;
  font-size: .72rem;
  text-transform: uppercase;
  letter-spacing: .14em;
  padding: .35rem .25rem;
  list-style: none;
}
.concept-filters__group > summary::-webkit-details-marker { display: none; }
.concept-filters__group > summary::after {
  content: " ▾";
  color: var(--accent);
}
.concept-filters__group[open] > summary::after { content: " ▴"; }
.concept-filters__buttons {
  display: flex;
  flex-wrap: wrap;
  gap: .35rem;
  padding: .5rem 0 0;
  max-width: 38rem;
}
.filter-btn {
  font-family: var(--body);
  font-weight: 700;
  font-size: .62rem;
  text-transform: uppercase;
  letter-spacing: .12em;
  padding: .35rem .65rem .3rem;
  background: var(--paper);
  color: var(--ink);
  border: var(--hairline) solid var(--rule);
  border-radius: 999px;
  cursor: pointer;
  transition: background-color .12s ease, color .12s ease, border-color .12s ease;
}
.filter-btn:hover { border-color: var(--ink); }
.filter-btn[aria-pressed="true"] {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}
.filter-clear {
  align-self: center;
  background: transparent;
  border-color: transparent;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--accent);
}
.filter-clear:hover { background: var(--highlight); border-color: var(--ink); }
.concept-filters__count {
  flex: 1 0 100%;
  margin: .25rem 0 0;
  font-size: .68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--muted);
}
/* Hide cards filtered out by the JS strip (uses the `hidden` HTML attr
   so cards remain accessible to assistive tech that ignores `hidden`
   when navigating the queue with the filter active). */
.concept[hidden] { display: none; }

/* ----------------------------------------------------------------
   Per-concept detail page — concepts/<slug>/index.html
   ---------------------------------------------------------------- */
.concept-detail {
  display: grid;
  gap: clamp(2rem, 4vw, 3.5rem);
  padding: clamp(1.5rem, 4vw, 3rem) 0 4rem;
}
.concept-detail__header { max-width: 60rem; }
.concept-detail__header .eyebrow {
  display: inline-block;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--body);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .14em;
  font-size: .68rem;
  padding: .35rem .7rem .3rem;
  border-radius: 999px;
  margin: 0 0 1rem;
  line-height: 1;
}
.concept-detail__header h1 {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(3rem, 9vw, 7.5rem);
  line-height: .9;
  letter-spacing: -.04em;
  margin: 0 0 1rem;
  text-transform: lowercase;
}
.concept-detail__header .deck {
  font-family: var(--body);
  font-weight: 500;
  font-size: clamp(1.1rem, 2vw, 1.45rem);
  line-height: 1.45;
  margin: 0 0 1.5rem;
}
/* Tag definition list — `<dl>` row of `<div><dt><dd>`. The dl uses
   flex-wrap rather than a grid so chip rows reflow naturally on
   narrow viewports. */
.concept-detail__tags {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: .75rem 1.5rem;
}
.concept-detail__tags > div {
  display: flex;
  flex-direction: column;
  gap: .35rem;
}
.concept-detail__tags dt {
  font-family: var(--body);
  font-weight: 700;
  font-size: .6rem;
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--muted);
}
.concept-detail__tags dd { margin: 0; }

/* Gallery — solo (single image) renders centered + full width; multi-
   slide renders as a horizontally scrollable strip with snap-points
   so a click on a thumbnail pulls it into view. Each slide also acts
   as a "open original PNG in new tab" link via the wrapping <a>. */
.concept-detail__gallery {
  display: flex;
  flex-wrap: nowrap;
  gap: 1rem;
  overflow-x: auto;
  padding-bottom: .75rem;
  scroll-snap-type: x mandatory;
}
.gallery-slide {
  margin: 0;
  flex: 0 0 auto;
  scroll-snap-align: start;
  background: #ece6d3;
  border: var(--hairline) solid var(--rule);
  display: flex;
  flex-direction: column;
}
.gallery-slide--solo {
  flex: 1 1 auto;
  width: 100%;
  align-items: center;
}
.gallery-slide__zoom {
  display: block;
  cursor: zoom-in;
  outline: 2px solid transparent;
  outline-offset: -4px;
  transition: outline-color .12s ease;
}
.gallery-slide__zoom:hover,
.gallery-slide__zoom:focus-visible { outline-color: var(--accent); }
.gallery-slide img {
  display: block;
  max-width: 100%;
  height: auto;
}
.gallery-slide--solo img { max-height: 80vh; }
.gallery-slide:not(.gallery-slide--solo) img {
  width: 22rem;
  height: auto;
}
.gallery-slide figcaption {
  font-family: var(--body);
  font-weight: 700;
  font-size: .65rem;
  text-transform: uppercase;
  letter-spacing: .14em;
  padding: .5rem .75rem;
  background: var(--paper);
  border-top: var(--hairline) solid var(--rule);
}

/* Palette strip — large swatches with hex labels under them. Two-row
   layout: hex swatches on top, curated palette tag chips below so the
   relationship between extracted color and curated tag is visible. */
.concept-detail__palette h2,
.concept-detail__type h2 {
  font-family: var(--body);
  font-weight: 700;
  font-size: .72rem;
  text-transform: uppercase;
  letter-spacing: .14em;
  margin: 0 0 1rem;
}
.swatch-row {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}
.swatch {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .35rem;
}
.swatch__chip {
  width: 4.5rem;
  height: 4.5rem;
  border-radius: 4px;
  border: var(--hairline) solid var(--rule);
  display: block;
}
.swatch code {
  font-family: var(--mono, "IBM Plex Mono", monospace);
  font-size: .72rem;
  letter-spacing: .02em;
}
.palette-tags {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: .35rem;
}

/* Type specimen — set the actual type spec in the body sans for now;
   pulling the named display+body fonts in is left for a future
   iteration that loads each concept's actual web fonts. */
.concept-detail__type .type-genre { margin: 0 0 .75rem; }
.concept-detail__type .type-specimen {
  font-family: var(--body);
  font-size: 1rem;
  line-height: 1.5;
  margin: 0;
  color: var(--ink);
}

/* CTA aside — primary "Pick this one" / "See it live" button + an
   optional secondary action / note. */
.concept-detail__cta {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 1.5rem;
  align-items: center;
  padding-top: 1.5rem;
  border-top: var(--hairline) solid var(--rule);
}
.cta {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  font-family: var(--body);
  font-weight: 700;
  font-size: .85rem;
  text-transform: uppercase;
  letter-spacing: .12em;
  padding: .85rem 1.4rem .8rem;
  border-radius: 999px;
  border: var(--hairline) solid var(--ink);
  text-decoration: none;
  transition: background-color .12s ease, color .12s ease;
}
.cta-primary { background: var(--ink); color: var(--paper); }
.cta-primary:hover { background: var(--accent); color: var(--ink); }
.cta-secondary { background: var(--paper); color: var(--ink); }
.cta-secondary:hover { background: var(--highlight); }
.cta .arrow { color: var(--accent); }
.cta-primary .arrow { color: var(--paper); }
.cta-primary:hover .arrow { color: var(--ink); }
.cta-note {
  margin: 0;
  font-size: .72rem;
  color: var(--muted);
  flex: 1 1 16rem;
}

