/* plinth presentation (SPEC PRES area). Self-hosted, system fonts,
   zero external requests (PRIV-01). Mobile-first (PRES-01). */

:root {
  --bg: #101014;
  --fg: #f2f2f0;
  --muted: #b9b9b4;
  --scrim: linear-gradient(to top, rgba(8, 8, 10, 0.88) 0%,
                           rgba(8, 8, 10, 0.55) 55%, rgba(8, 8, 10, 0) 100%);
  --accent: #d8c9a3;
  /* PRES-18: surfaces are tokens too — :palette overrides append
     after this block, so the cascade takes the owner's values */
  --surface: #1b1b22;
  --surface-2: #22222b;
  --hairline: rgba(255, 255, 255, 0.08);
  --card-backdrop: rgba(10, 10, 13, 0.78);
  /* chrome halo for floating arrows over media — palettes with light
     grounds override it light (2026-08-13) */
  --halo: rgba(0, 0, 0, 0.9);
  /* info-card backdrop blur — configurable via :palette :card-blur
     (per Andrew, 2026-08-13) */
  --card-blur: 12px;
  /* pills + boxed chrome: rectangular with subtle rounding by default
     (the card-tile look, per Andrew); :pill-shape :round → 999px */
  --pill-radius: 6px;
  /* one size for the whole arrow family — ← → ↑ ↓ read as a set only
     if they are drawn at the same size (Andrew, 2026-08-11: the nav
     arrows were 22.4px against the card's 14.7px) */
  --arrow-size: 1.25rem;
  /* the TITLE LINE: where every page type's title box starts (his
     review 2026-09-15: one top line for the home, the kind pages, notes
     and faq; on a desktop the titles sat too close to the pill at
     2.5rem, so the line is 4rem there) */
  --title-line: 3.25rem;
}
@media (min-width: 40rem) { :root { --title-line: 4rem; } }

* { box-sizing: border-box; }
html { height: 100%; }
body {
  margin: 0; min-height: 100%;
  background: var(--bg); color: var(--fg);
  font: 16px/1.55 system-ui, -apple-system, "Segoe UI", sans-serif;
  -webkit-font-smoothing: antialiased;
}
a { color: inherit; }
img, video { max-width: 100%; height: auto; }

/* ---- full-viewport item presentation (PRES-01) ---- */
main.item { min-height: 100svh; position: relative; }
main.item > h1 { position: absolute; left: -9999px; } /* title lives on the card */
main.item figure {
  margin: 0; height: 100svh; display: grid; place-items: center;
  background: var(--bg);
}
/* PRES-01: :cover (default) fills the entire visible viewport;
   :contain letterboxes on the canvas — selectable per portfolio. */
main.m-cover figure img {
  width: 100vw; height: 100svh; object-fit: cover; display: block;
}
main.m-contain figure img {
  max-height: 100svh; max-width: 100vw; width: auto; height: auto;
  object-fit: contain; display: block;
}
/* PRES-24 (0.66.0): video participates in the cover/contain geometry
   exactly like a photograph — an ambient loop becomes the page the
   way a cover image does */
main.m-cover figure video {
  width: 100vw; height: 100svh; object-fit: cover; display: block;
}
main.m-contain figure video {
  max-height: 100svh; max-width: 100vw; width: auto; height: auto;
  object-fit: contain; display: block;
}
/* PRES-01 :framed (0.53.2, Andrew's painter ruling): a painting is
   never full-bleed. It hangs CENTRED at a FIXED width (:work-width,
   default 960px — the img's sizes attribute carries the same width, so the intrinsic size
   is a constant, not a viewport fraction: resizing the window never
   resizes the work), capped at a viewport fraction either way
   (:work-vw/:work-vh percent, default two-thirds — 0.64.0, his
   painting-size ruling), on a selectable neutral ground
   (:ground → --ground: 18% grey / black / white; the palette bg when
   unset), with a thin border in the complement of the work's :accent
   to part it from the ground (--frame-border per figure; the palette
   hairline without an accent). */
main.m-framed figure {
  background: var(--ground, var(--bg));
  /* the work centres in the space ABOVE the closed card, never under it */
  padding-bottom: 5.5rem;
}
/* the arrows take their INK from the wall (Andrew, 2026-08-21): light
   on dark grounds, dark on white — the palette's foreground would
   vanish on a black wall. data-ground names the page's wall. */
main[data-ground="black"], main[data-ground="grey-18"] {
  --wall-ink: #f2f2f0; --wall-halo: rgba(0, 0, 0, 0.9);
}
main[data-ground="white"] {
  --wall-ink: #1b1b1c; --wall-halo: rgba(255, 255, 255, 0.92);
}
/* a framed page's card shows its EXTENT even on a wall of its own
   colour (Andrew's white-wall catch): a soft shadow lifts it off */
main.m-framed details.card { box-shadow: 0 -6px 20px rgba(0, 0, 0, 0.10); }
main.m-framed figure img {
  width: auto; height: auto; display: block;
  max-width: min(var(--work-w, 960px), var(--work-vw, 66.67vw));
  max-height: var(--work-vh, 66.67vh);
  object-fit: contain;
  border: 2px solid var(--frame-border, var(--hairline));
}
main.item video, main.item audio { width: min(92vw, 60rem); }
main.item pre {
  width: min(92vw, 46rem); margin: 4rem auto; padding: 1.5rem;
  overflow-x: auto; background: var(--surface); border-radius: 6px;
  font-size: 0.9rem;
}

/* ---- info card: native details/summary overlay (PRES-04/05).
   No JS: open by default, the summary is the unobtrusive toggle;
   scrim keeps WCAG AA contrast without hiding the media. ---- */
details.card {
  position: fixed; inset: auto 0 0 0; z-index: 2;
  padding: 1.1rem 1.25rem;
  /* distinct backdrop: AA-legible over ANY media (PRES-04) — solid
     enough alone; blur adds separation where supported */
  background: var(--card-backdrop);
  backdrop-filter: blur(var(--card-blur));
  -webkit-backdrop-filter: blur(var(--card-blur));
  /* the palette HAIRLINE, not a fixed white whisper (0.53.2): on light
     grounds the old edge was invisible and the card's extent was lost;
     0.77.0 (PRES-04, his reputat.io review): :palette {:card-edge …}
     names the edge where the card's backdrop matches the page ground */
  border-top: 1px solid var(--card-edge, var(--hairline));
  /* DOM-08 0.80.0: pinned to a home screen there is no browser
     chrome under the card — the home indicator's safe area is padded
     (viewport-fit=cover is emitted with :app-icon; env() is 0 elsewhere) */
  padding-bottom: calc(1.1rem + env(safe-area-inset-bottom, 0px));
}
details.card summary {
  cursor: pointer; list-style: none; display: inline-block;
  /* 0.49.0: the title is the page's most important information on a
     lander — :palette {:summary-size "1.7rem"} grows it */
  font-size: var(--summary-size, 1.15rem);
  font-weight: 600; letter-spacing: 0.01em;
}
details.card summary::-webkit-details-marker { display: none; }
/* COM-10 0.79.0: the For Sale sign — distinctive, at the right of the
   summary line; the name stays left (his review) */
details.card summary span.for-sale {
  order: 2; margin-left: auto; flex: 0 0 auto;
  /* COM-10 0.80.0 (his round 4): a sign coloured like a FOR SALE
     board — :palette :sign, a warm red by default — and a size up */
  font-size: 0.8rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--sign-fg, #ffffff); background: var(--sign, #c8102e); border-radius: 3px; padding: 0.28rem 0.7rem;
}
details.card p.offer { margin: 0.35rem 0 0; font-size: 0.9rem; color: var(--accent); font-weight: 600; }
/* PRES-04 0.79.0: the top fade — scrolling text passes UNDER the switch */
div.top-fade {
  /* PRES-04 0.80.0 (his round 4): the band no longer spans the width —
     it fades from the page ground at the LEFT edge to transparent at
     the centre, and the vertical fade stays, so only the top-left
     corner under the switch is occluded; text at the top right of a
     page is never covered */
  position: fixed; top: 0; left: 0; right: 0; height: 2.9rem; z-index: 0;
  pointer-events: none;
  /* his review 2026-09-15 ("doesn't appear to be present"): the band
     is OPAQUE through the switch's row (0.6rem + the pill, to 2.55rem)
     and ends at 2.9rem, just above every page's title line, so the
     fade hides what scrolls under the switch and never veils a title
     at rest; sideways a plateau across the switch, then the fade to
     60% of the width */
  background: linear-gradient(to right, var(--bg) 25%, transparent 60%);
  -webkit-mask-image: linear-gradient(var(--bg) 88%, transparent);
  mask-image: linear-gradient(var(--bg) 88%, transparent);
}
/* the phone stacks the switch and the cluster in a column: the band
   covers the column (the browse frame's 7rem keeps the title below) */
@media (max-width: 40rem) {
  nav.browse:has(> a) > div.top-fade {
    height: 4.9rem;
    -webkit-mask-image: linear-gradient(var(--bg) 92%, transparent);
    mask-image: linear-gradient(var(--bg) 92%, transparent);
  }
}
/* the fade is a child of the fixed nav: its siblings (the switch, the
   pills) sit ABOVE it in the nav's own stacking context (his catch:
   the menu vanished under the fade) */
nav.browse > details, nav.browse > a, nav.browse > span { position: relative; z-index: 1; }
/* bottom-sheet grammar (Andrew, 2026-08-10): closed shows ↑ (pull up
   to reveal), open shows ↓ (tuck away) — arrows match the card's
   actual motion, where the carets read ambiguously */
details.card summary::after {
  content: " ↓"; color: var(--muted); font-size: var(--arrow-size);
}
/* COM-10 0.79.0: a SIGNED summary is a flex row — the arrow ORDERED
   next to the name (his two catches: a plain flex row carried the arrow
   to the far right; a float left the sign top-aligned), the sign
   centred on the name at the right; every unsigned card keeps the
   inline form above (his question: no change reaches other sites) */
details.card summary.with-sign { display: flex; align-items: center; gap: 0.35rem; }
details.card summary.with-sign::after { order: 1; white-space: pre; }
details.card:not([open]) summary::after { content: " ↑"; }
/* 0.49.0 top-attached card (:presentation {:card-edge :top}): the
   bottom-sheet grammar flips — closed shows ↓ (pull down to reveal),
   open shows ↑ (tuck away upward) */
details.card.card-top {
  /* IN FLOW at the top, not a fixed overlay — an overlay swallows
     whatever scrolls beneath (the feed stream taught us); the
     branding pill carries the always-visible name. BAND layout
     (Andrew's review: floating centered panels read loose): the
     band runs full-bleed, its CONTENT aligns to one shared column */
  position: relative; z-index: 1;
  border-top: none; border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
/* :band-align :center — every band's content on one shared column;
   the default stays LEFT from the edge padding (Andrew's review) */
main.item.bands-center details.card.card-top,
main.item.bands-center section.feed {
  /* percentages of the containing block, never 100vw: a classic
     scrollbar is inside 100vw, so the band's column was 15px narrower
     than the stream pages' and sat 7px to the right of them (his review
     2026-09-15, Chrome and Firefox with scrollbars shown) */
  padding-left: max(1.25rem, calc((100% - 46rem) / 2));
  padding-right: max(1.25rem, calc((100% - 46rem) / 2));
}
/* the lander background: the first image FIXED behind everything —
   in-flow media moved when the card toggled (Andrew's review) */
main.item figure.bg {
  position: fixed; inset: 0; margin: 0; z-index: 0;
}
/* the img is ABSOLUTE against the fixed container: a percentage
   height through the inner figure dies quietly when that figure is
   a grid with auto rows (the general main.item figure rule) — at
   tall viewports the photo fell back to its intrinsic size
   (Andrew's desktop, 2026-08-18). inset:0 is definite, always. */
main.item figure.bg figure { margin: 0; height: 100%; }
main.item figure.bg img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; display: block;
}
/* the feed is a full-bleed BAND on the same column as the card,
   separated by a strip of the photograph — bands, not floating
   panels (Andrew's review) */
main.item section.feed {
  background: var(--card-backdrop);
  backdrop-filter: blur(var(--card-blur));
  -webkit-backdrop-filter: blur(var(--card-blur));
  border-top: 1px solid rgba(255, 255, 255, 0.06);
  margin: 0;
}
/* a lander WITHOUT a photograph (no figure.bg) has no band to lift off
   an image: the feed sits on the page ground like every other page
   (his review 2026-09-15: the home's ground differed from the rest) */
main.item:not(:has(figure.bg)) section.feed {
  background: none; backdrop-filter: none; -webkit-backdrop-filter: none; border-top: 0;
}
/* the lead viewport: card band up top, the photograph owns the rest
   of the FIRST SCREEN; whatever follows begins at the fold — the
   viewport is the boundary, never an arbitrary gap */
main.item div.lead { min-height: 100svh; position: relative; z-index: 1; }
/* no photograph, no fold: the lead is its content and the feed band
   follows at once (his review 2026-09-03) */
main.item div.lead.lead-bare { min-height: auto; }
/* the scroll cue: the arrow family's ↓ at the foot of the lead — a
   quiet, working link to what lies below the fold */
a.scroll-cue {
  position: absolute; bottom: 1.1rem; left: 50%; transform: translateX(-50%);
  width: 2.8rem; height: 2.8rem; display: grid; place-items: center;
  font-size: calc(var(--arrow-size) * 1.25); color: var(--fg);
  text-decoration: none;
  background: var(--card-backdrop); border: 1px solid var(--hairline);
  border-radius: 50%;
  backdrop-filter: blur(var(--card-blur));
  -webkit-backdrop-filter: blur(var(--card-blur));
}
a.scroll-cue:hover, a.scroll-cue:focus-visible {
  border-color: var(--accent); color: var(--accent);
}
@media (prefers-reduced-motion: no-preference) {
  a.scroll-cue { animation: plinth-cue 2.2s ease-in-out infinite; }
  @keyframes plinth-cue {
    0%, 100% { transform: translate(-50%, 0); }
    50% { transform: translate(-50%, 0.35rem); }
  }
  html { scroll-behavior: smooth; }
}

details.card.card-top summary::after { content: " ↑"; }
details.card.card-top:not([open]) summary::after { content: " ↓"; }
details.card .text, details.card .phase-text { max-width: 46rem; }
/* PRES-05 0.82.3 (his ask 2026-09-14): the card's thumbnail — a 6rem
   float at the top left of the card body, the text wrapping around it
   (Q-CT2); the flow-root wrapper contains the float so nothing below
   the text rides beside it. Square with the hairline border by
   default; :card-thumb {:shape :circle} rounds it, {:border false}
   drops the edge (Q-CT3: every combination renders for his pick).
   Specificity, deliberately: the base rule is (0,2,2) and the two
   modifiers (0,3,2), so a modifier always wins; the band rules
   (main.item figure at (0,1,2), main.m-cover figure img at (0,2,2))
   lose to the base rule and its img rule at (0,2,3). His catch of
   2026-09-14: a main.item-prefixed base rule at (0,3,3) had outranked
   .thumb-plain and every "no border" render carried the border. */
details.card .with-thumb { display: flow-root; }
details.card figure.card-thumb {
  float: left; width: 6rem; height: 6rem; margin: 0.25rem 1rem 0.5rem 0;
  display: block; overflow: hidden; box-sizing: border-box;
  border: 1px solid var(--hairline); background: var(--surface-2);
}
details.card figure.card-thumb img {
  display: block; width: 100%; height: 100%; max-height: none; object-fit: cover;
}
details.card figure.card-thumb.thumb-circle { border-radius: 50%; }
details.card figure.card-thumb.thumb-plain { border: 0; }
/* 0.48.0 mobile card: an OPEN card never swallows the small frame —
   its content scrolls inside a cap while the summary stays pinned.
   ::details-content is progressive enhancement: without support the
   card simply grows, exactly as before */
@media (max-width: 40rem) {
  details.card[open]::details-content {
    display: block; max-height: 55vh;
    overflow-y: auto; overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }
}
details.card .text p, details.card .meta { color: var(--muted); }
details.card .meta { font-size: 0.85rem; margin: 0.5rem 0 0; }
details.card .meta a { color: var(--fg); }
p.cta { margin: 0.75rem 0 0; }
p.cta a {
  display: inline-block; padding: 0.45rem 1.1rem; border: 1px solid var(--accent);
  border-radius: 3px; color: var(--accent); text-decoration: none;
}
p.cta .price { margin-left: 0.75rem; color: var(--muted); }
p.cta .price s { opacity: 0.6; margin-right: 0.4rem; }
code.coupon {
  margin-left: 0.75rem; padding: 0.15rem 0.5rem;
  border: 1px dashed var(--muted); border-radius: 3px;
}
p.ended { color: var(--accent); font-size: 0.9rem; margin: 0.4rem 0 0; }
footer.rights { color: var(--muted); font-size: 0.75rem; margin-top: 0.6rem; }

/* ---- 0.48.0 commerce metadata: quiet lines under the card body ---- */
p.notice { color: var(--accent); font-size: 0.9rem; margin: 0.5rem 0 0; }
ul.quotes { list-style: none; padding: 0; margin: 0.6rem 0 0; }
ul.quotes li { font-size: 0.9rem; color: var(--muted); margin: 0.25rem 0 0; }
ul.quotes q { font-style: italic; }
ul.quotes .source a { color: var(--fg); }
ul.links { list-style: none; padding: 0; margin: 0.6rem 0 0;
  display: flex; flex-wrap: wrap; gap: 0.4rem 0.75rem; }
ul.links li { font-size: 0.85rem; }
ul.links a { color: var(--fg); }

/* ---- PRES-21 label-form card (0.53.0): the exhibition-label
   language — white ground (the card-backdrop token), black text, ONE
   accent. Similarity to the label samples, not facsimile (his
   fidelity ruling); the exact look is golden-blessed (TEST-05). ---- */
details.card.card-label summary { display: block; }
/* an open label card never claims more than ~40% of the viewport —
   content past the cap scrolls inside while the label head stays put
   (the 0.48.0 mobile pattern made universal for label cards; Andrew's
   review 2026-08-21: card sizes should read similar page to page, and
   tall cards would break mobile). Progressive enhancement like the
   mobile rule: without ::details-content support the card grows. */
details.card.card-label[open]::details-content {
  display: block; max-height: 40vh;
  overflow-y: auto; overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
/* a closed label card keeps the eased collapse (overflow clip wins
   only while closed — the open rule above restores inner scrolling) */
details.card.card-label:not([open])::details-content { overflow: clip; }
.label-artist { display: block; font-weight: 700; font-size: 1.05rem; }
.label-artist .born { font-weight: 400; }
.label-work { display: block; font-weight: 400; margin-top: 0.1rem; }
.label-work a { text-decoration: none; }
/* the title is the label's loudest line (Andrew, 2026-08-21): larger
   than artist and caption, em-scaled so :palette :summary-size
   overrides keep the proportion; the date stays at line size */
.label-title {
  font-weight: 700; font-style: italic; color: var(--accent);
  font-size: 1.35em; line-height: 1.25;
}
/* PRES-21 :metallic (0.56.0, Andrew's round-2 ruling): a metallic
   colour present in the work — copper, gold, silver — reads as a slow
   sheen across the title: the accent as a gradient clipped to the
   glyphs with a light band sweeping through once every 6 s. Only the
   title moves; under prefers-reduced-motion the band stands still
   (a static two-tone sheen keeps the information, drops the motion —
   PRES-03's discipline). --title-accent arrives inline from the card.
   0.57.0: the same sheen on a METALLIC TAG — its chips and its page
   heading (#gold, #silver, #copper); and the pass is rarer (Andrew:
   "insufficient delay until the next sheen pass") — a 2.4 s sweep,
   then the metal rests for the remaining 9.6 s of a 12 s cycle. */
.metallic {
  --metal: var(--title-accent);
  --sheen: color-mix(in srgb, var(--title-accent) 45%, white);
  color: var(--metal);
  background-image: linear-gradient(100deg,
    var(--metal) 0%, var(--metal) 38%,
    var(--sheen) 50%,
    var(--metal) 62%, var(--metal) 100%);
  background-size: 250% 100%; background-position: 100% 0;
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
  /* the tempo is the COLOUR's (0.57.0, Andrew: the sheens must not
     compete): --sheen-cycle and --sheen-delay arrive inline — derived
     from the colour's lightness and a per-colour stagger, or the
     owner's :tempo — so no two metals on a page pulse together */
  animation: title-sheen var(--sheen-cycle, 12s) linear infinite;
  /* NEGATIVE delay: the stagger offsets the PHASE — each metal starts
     mid-cycle at its own point — rather than postponing the first pass
     (a visitor arriving on #copper saw a flat heading for 10 s) */
  animation-delay: calc(-1 * var(--sheen-delay, 0s));
}
/* a mark glyph inside a metallic chip (#gold) keeps a SOLID fill: its
   opacity makes it a separate layer, which the parent's text-clipped
   gradient cannot paint through — it vanished (Andrew's #gold page) */
.metallic .pfx { -webkit-text-fill-color: var(--metal); }
/* :sheen :dark — for a LIGHT metal (white, silver) on a light card: the
   resting tone is the colour mixed toward black, so the chip stays
   legible between passes, and the band that sweeps through is the
   colour itself — white flashes through a grey "#white" */
.metallic.sheen-dark {
  --metal: color-mix(in srgb, var(--title-accent) 55%, black);
  --sheen: var(--title-accent);
}
/* :sheen :dual (0.71.0, his #black ruling, round 4): a mid-grey base
   swept by BOTH a dark band (the colour) and a light band — ONE
   animated gradient, so the 0.57.0 tempo law holds; the light band
   carries it on black grounds, the grey base + dark band on white */
.metallic.sheen-dual {
  --metal: color-mix(in srgb, var(--title-accent) 50%, #a9a9a9);
  --sheen: color-mix(in srgb, var(--title-accent) 8%, white);
  background-image: linear-gradient(100deg,
    var(--metal) 0%, var(--metal) 30%,
    var(--title-accent) 40%,
    var(--metal) 50%,
    var(--sheen) 60%,
    var(--metal) 70%, var(--metal) 100%);
}
.metallic.sheen-dual .pfx { -webkit-text-fill-color: var(--metal); }
@keyframes title-sheen {
  0%   { background-position: 100% 0; }
  20%  { background-position: -150% 0; }
  100% { background-position: -150% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .metallic { animation: none; background-position: 50% 0; }
}
.label-sep { font-size: 1.35em; line-height: 1.25; font-weight: 400; font-style: normal; color: var(--fg); }
.label-date { font-weight: 400; font-style: normal; color: var(--fg); }
.label-caption { margin-top: 0.6rem; font-size: 0.85rem; }
.label-caption p { margin: 0.15rem 0 0; }
.label-sold {
  color: var(--muted); font-weight: 600; font-size: 0.8rem;
  text-transform: uppercase; letter-spacing: 0.06em;
}
.label-series {
  /* his ruling 2026-08-25: simpler and non-bold, the ∈ mark carrying
     the meaning instead of the weight (the mark itself lightens via
     span.pfx, like § and #) */
  color: var(--muted); font-weight: 400; font-size: 0.9rem;
  margin: 0.6rem 0 0;
}
.label-line { margin: 0.6rem 0 0; font-size: 0.95rem; max-width: 46rem; }
.label-parts { margin: 0.75rem 0 0; }
.label-badge {
  margin: 0; color: var(--muted); font-size: 0.8rem;
  text-transform: uppercase; letter-spacing: 0.06em;
}
.label-parts ul.parts {
  list-style: none; padding: 0; margin: 0.35rem 0 0;
  display: flex; flex-wrap: wrap; gap: 0.25rem 2rem;
}
.label-parts .part-title { font-weight: 600; font-size: 0.9rem; }
.label-parts .part-title a { color: var(--fg); }
.label-parts .label-sold { margin-left: 0.5rem; }
/* the code block: QR left, the SKU strings SIDEWAYS along its right
   edge (the samples' rotated monospace). The SVG is generated
   margin-0, so the MODULES are its edge: the white plate's padding
   is the mandatory quiet zone (0.75rem ≥ 4 modules at this size),
   and the SKU column aligns with the module rows themselves — the
   quiet zone read as misalignment when it lived inside the SVG
   (Andrew's review, 2026-08-21). Host line at the block's foot. */
.label-code {
  /* the SKUs hug the plate (Andrew, 2026-08-21) — the quiet zone is
     the plate's own padding, so text may sit right at its edge */
  display: flex; align-items: flex-start; gap: 0.15rem;
  margin-top: 0.9rem;
}
.label-qr {
  display: block; background: #ffffff; padding: 0.75rem;
}
.label-qr svg { display: block; width: 5rem; height: 5rem; }
/* 0.79.0 (his ruling): the share QR on a lander's card is large enough
   to scan from a phone screen */
details.card .label-code.share .label-qr svg { width: 10rem; height: 10rem; }
/* the SKUs hug the QR's RIGHT edge, the rotated codes ADJACENT to
   each other as one tight group (Andrew's second correction,
   2026-08-24 — stacking them down the page spread them apart) */
ul.label-skus {
  list-style: none; padding: 0; margin: 0.75rem 0 0 0.2rem;
  display: flex; gap: 0.25rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.7rem; color: var(--muted); letter-spacing: 0.04em;
}
ul.label-skus li { writing-mode: vertical-rl; margin: 0; }
/* the QR and its host line stack as one unit — the line sits DIRECTLY
   under the code it describes, left-aligned (his review: it floated
   to the card's far corner) */
.label-code-col { display: flex; flex-direction: column; align-items: flex-start; gap: 0.3rem; }
.label-code-row { display: flex; align-items: flex-start; gap: 0.15rem; }
.label-rights {
  /* the credit sits quietly under the work's own facts, above the
     code block — an exhibition label's convention (RIGHTS-05) */
  color: var(--muted); font-size: 0.78rem; margin: 0.6rem 0 0;
}
.label-host {
  margin: 0;
  /* his ruling 2026-08-25: the host line is not bold — it names where
     the code goes, it does not compete with the work */
  font-weight: 400; font-size: 0.8rem;
}
.label-host a { color: inherit; }
/* CONTACT-02: the obfuscated address link; without JS the anchor has
   no href and the noscript words form speaks instead */
p.contact { margin: 0.75rem 0 0; }
p.contact a.contact-mail {
  display: inline-block; padding: 0.45rem 1.1rem;
  border: 1px solid var(--muted); border-radius: 3px;
  color: var(--fg); text-decoration: none; cursor: pointer;
}
p.contact a.contact-mail:hover { border-color: var(--accent); color: var(--accent); }
p.contact .contact-words { color: var(--muted); margin-left: 0.6rem; font-size: 0.9rem; }
main.linkspage p.contact { margin-top: 1.2rem; }
p.maker, p.seller { color: var(--muted); font-size: 0.8rem; margin: 0.5rem 0 0; }
p.maker a, p.seller a { color: var(--fg); }

/* ---- PRES-20 links mode: one page of outbound buttons ---- */
main.linkspage {
  min-height: 100svh; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 0.4rem;
  padding: 3rem 1.25rem; max-width: 30rem; margin: 0 auto;
  text-align: center; position: relative;
}
/* PRES-20 background photograph: full viewport behind the buttons,
   pipeline-derived; scrim via the palette token */
main.linkspage figure.bg {
  position: fixed; inset: 0; margin: 0; z-index: 0;
}
/* media-el wraps in its own inner <figure>: default figure margins
   (1em/40px) shrank the backdrop into a bordered box (Andrew's
   report) — zero it and let it fill */
/* iOS Safari rotation (Andrew's iPhone, 2026-08-18): the FIXED
   container tracks the true visible viewport, but 100svh on the
   inner image ends where the small-viewport height ends — a cut
   strip in landscape once chrome retracts, invisible in emulators.
   Percentages of the container dodge every vh-variant game. */
main.linkspage figure.bg figure { margin: 0; height: 100%; }
main.linkspage figure.bg img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; display: block;
}
main.linkspage figure.bg.scrimmed::after {
  content: ""; position: absolute; inset: 0; background: var(--scrim);
}
main.linkspage > *:not(figure.bg) { position: relative; z-index: 1; }
main.linkspage figure.bg ~ h1, main.linkspage figure.bg ~ p.tagline {
  text-shadow: 0 0 10px var(--halo);
}
main.linkspage img.avatar {
  width: 6rem; height: 6rem; object-fit: cover;
  border-radius: 50%; border: 1px solid var(--hairline);
  margin-bottom: 0.6rem;
}
main.linkspage h1 { margin: 0; font-size: 1.4rem; letter-spacing: 0.01em; }
main.linkspage p.tagline { margin: 0 0 1.2rem; color: var(--muted); }
ul.linklist { list-style: none; margin: 0; padding: 0; width: 100%; }
ul.linklist li { margin: 0.7rem 0; }
a.link-button {
  display: block; padding: 0.85rem 1.2rem; text-decoration: none;
  border: 1px solid var(--accent); border-radius: var(--pill-radius);
  background: var(--surface); color: var(--fg);
  /* subtle hover feedback (Andrew, 2026-08-16): a breath of lift,
     a brighter edge, a soft shadow — never garish */
  transition: background 0.15s ease, border-color 0.15s ease,
              transform 0.15s ease, box-shadow 0.15s ease;
}
a.link-button:hover, a.link-button:focus-visible {
  background: var(--surface-2);
  border-color: color-mix(in srgb, var(--accent) 55%, var(--fg));
  transform: translateY(-1px);
  box-shadow: 0 3px 14px rgba(0, 0, 0, 0.22);
}
@media (prefers-reduced-motion: reduce) {
  a.link-button:hover, a.link-button:focus-visible { transform: none; }
}
a.link-button .note { display: block; font-size: 0.8rem; color: var(--accent); margin-top: 0.2rem; }

/* ---- PRES-26 document mode: one long-form tree document ---- */
main.document {
  /* one centred reading column — the rail floats, it reserves nothing
     (his preview review, 2026-08-30) */
  /* 46rem of reading width inside the 1.25rem sides: the same column
     as the home band and the stream pages (his review 2026-09-15) */
  max-width: 48.5rem; margin: 0 auto;
  padding: var(--title-line) 1.25rem 4rem;
}
main.document header.doc { max-width: 46rem; }
main.document header.doc h1 { margin: 0 0 0.3rem; font-size: 2rem; line-height: 1.15; }
main.document header.doc p.subtitle {
  margin: 0 0 1.5rem; color: var(--muted); font-size: 1.05rem;
}
/* the switch keeps its top-left corner on a document page too */
main.document > nav.browse { margin-bottom: 1.5rem; }
/* the contents rail is a QUIET element (his preview review,
   2026-08-30: "present but it shouldn't dominate"): closed by
   default everywhere; on wide viewports it floats at the left edge
   as a translucent overlay (the card-backdrop idiom) that fades in
   on hover/focus and while open — content keeps the full column */
@media (min-width: 64rem) {
  main.document div.doc-body { margin-left: 0; }
  main.document nav.toc {
    position: fixed; left: 1rem; top: 5.5rem; z-index: 4;
    width: max-content; max-width: 17rem;
    max-height: calc(100vh - 8rem); overflow-y: auto;
    background: var(--card-backdrop);
    backdrop-filter: blur(var(--card-blur, 8px));
    -webkit-backdrop-filter: blur(var(--card-blur, 8px));
    opacity: 0.55;
  }
  main.document nav.toc:hover, main.document nav.toc:focus-within,
  main.document nav.toc:has(details[open]) { opacity: 1; }
  @media (prefers-reduced-motion: no-preference) {
    main.document nav.toc { transition: opacity 0.15s ease; }
  }
}
main.document nav.toc {
  font-size: 0.9rem; margin: 0 0 2rem;
  border: 1px solid var(--hairline); border-radius: 6px;
  background: var(--surface); padding: 0.6rem 0.9rem;
}
main.document nav.toc summary {
  cursor: pointer; font-weight: 600; letter-spacing: 0.02em;
}
main.document nav.toc ol { list-style: none; margin: 0.4rem 0 0; padding: 0 0 0 0.2rem; }
main.document nav.toc ol ol { padding-left: 0.9rem; font-size: 0.95em; }
main.document nav.toc li { margin: 0.3rem 0; }
main.document nav.toc a { color: var(--fg); text-decoration: none; }
main.document nav.toc a:hover, main.document nav.toc a:focus-visible { text-decoration: underline; }
main.document section.doc-node { max-width: 46rem; }
main.document .doc-body h2 { margin: 2.2rem 0 0.6rem; font-size: 1.45rem; }
main.document .doc-body h3 { margin: 1.8rem 0 0.5rem; font-size: 1.2rem; }
main.document .doc-body h4, main.document .doc-body h5, main.document .doc-body h6 {
  margin: 1.4rem 0 0.4rem; font-size: 1.02rem;
}
/* folded deep nodes: the heading rides the summary; the marker stays
   quiet until hover, so the fold is offered, never shouted */
main.document details > summary { cursor: pointer; }
main.document details > summary > h4,
main.document details > summary > h5,
main.document details > summary > h6 { display: inline; }
main.document .text { line-height: 1.65; }
main.document .text p { margin: 0.7rem 0; }
main.document .text pre {
  overflow-x: auto; background: var(--surface-2);
  border: 1px solid var(--hairline); border-radius: 6px;
  padding: 0.8rem 1rem; font-size: 0.85rem;
}
main.document .text blockquote {
  margin: 0.9rem 0; padding: 0.1rem 0 0.1rem 1rem;
  border-left: 3px solid var(--hairline); color: var(--muted);
}
/* PRES-26 footnotes: per-node backlinked end-notes */
main.document ol.footnotes {
  margin: 1.2rem 0 0.5rem; padding-left: 1.4rem;
  border-top: 1px solid var(--hairline);
  font-size: 0.85rem; color: var(--muted);
}
main.document ol.footnotes li { margin: 0.4rem 0; }
main.document figure.doc-figure { margin: 1.4rem 0; }
main.document figure.doc-figure img {
  max-width: 100%; height: auto; display: block;
  border: 1px solid var(--hairline); border-radius: 4px;
}
main.document figure.doc-figure figcaption {
  margin-top: 0.4rem; font-size: 0.85rem; color: var(--muted);
}
/* Q-D6 backlinks: the build-generated "referenced by" index */
main.document section.backlinks {
  max-width: 46rem; margin-top: 3rem; padding-top: 0.8rem;
  border-top: 1px solid var(--hairline); font-size: 0.9rem;
}
main.document section.backlinks h2 {
  margin: 0 0 0.4rem; font-size: 0.95rem; color: var(--muted);
  letter-spacing: 0.03em;
}
main.document section.backlinks ul { list-style: none; margin: 0; padding: 0; }
main.document section.backlinks li { margin: 0.25rem 0; }
@media print {
  /* PRES-12: the document prints as a document — full content (deep
     nodes are open by default), no chrome. The closed rail would
     print as a bare "Contents" line, so it goes; the typeset ToC is
     bb doc-pdf's outline() */
  main.document { max-width: none; padding: 0; }
  main.document div.doc-body { margin-left: 0; }
  main.document nav.toc { display: none; }
  main.document > nav.browse { display: none; }
  main.document .text pre { border: 1px solid #bbb; background: none; }
  main.document ol.footnotes { color: #333; }
  main.document section.doc-node { break-inside: auto; }
  main.document .doc-body h2 { break-after: avoid; }
}

/* ---- PRES-25 CV mode: the derived document (main.cv rides the
   main.document rules; these add the typed-entry grammar) ---- */
main.cv article.cv-entry { margin: 0.9rem 0 1.2rem; }
/* the typeset-PDF link (0.70.1): quiet, under the label line */
main.cv p.cv-pdf { margin: 0.5rem 0 0; font-size: 0.85rem; }
main.cv p.cv-pdf a { color: var(--muted); }
main.cv p.cv-pdf a:hover, main.cv p.cv-pdf a:focus-visible { color: var(--fg); }
@media print {
  /* on ink the link IS the document — the words without a target
     would dangle */
  main.cv p.cv-pdf { display: none; }
}
main.cv p.cv-lead {
  display: flex; align-items: baseline; gap: 0.8rem;
  justify-content: space-between; margin: 0 0 0.1rem;
}
main.cv .range {
  color: var(--muted); font-size: 0.85rem; white-space: nowrap;
  font-feature-settings: "tnum" 1;
}
main.cv p.cv-loc { margin: 0 0 0.3rem; color: var(--muted); font-size: 0.85rem; }
main.cv article.cv-entry .text p { margin: 0.4rem 0; }
main.cv ul.cv-highlights { margin: 0.3rem 0 0; padding-left: 1.2rem; }
main.cv ul.cv-highlights li { margin: 0.2rem 0; }
main.cv p.cv-links { margin: 0.3rem 0 0; font-size: 0.9rem; }
main.cv ul.cv-list { list-style: none; margin: 0.4rem 0; padding: 0; }
main.cv ul.cv-list li { margin: 0.35rem 0; }
main.cv ul.cv-list .range { margin-left: 0.6rem; }
main.cv p.cv-skill-group { margin: 0.45rem 0; }
main.cv .chips { margin-left: 0.5rem; }
main.cv .chip {
  display: inline-block; margin: 0.15rem 0.25rem 0.15rem 0;
  padding: 0.1rem 0.55rem; border: 1px solid var(--hairline);
  border-radius: var(--pill-radius); background: var(--surface);
  font-size: 0.82rem;
}
@media print {
  main.cv .chip { border-color: #bbb; }
  main.cv article.cv-entry { break-inside: avoid; }
}

/* ---- PRES-27 events timeline (Q-T1..4): one vertical spine,
   today-first, empty stretches compressed ---- */
/* the one column (his ruling 2026-09-15); the earlier 44rem never
   applied: the browse frame's later rule of the same specificity won */
main.browse.timeline { max-width: 48.5rem; }
main.timeline h1 { margin-bottom: 1.6rem; }
nav.tl-entry { margin: 0.4rem 0 0; font-size: 0.9rem; }
nav.tl-entry a { color: var(--muted); text-decoration: none; }
nav.tl-entry a:hover, nav.tl-entry a:focus-visible { color: var(--fg); text-decoration: underline; }
main.timeline h2.tl-upcoming {
  margin: 1.2rem 0 0.4rem; font-size: 0.95rem; color: var(--muted);
  letter-spacing: 0.04em; text-transform: uppercase;
}
/* the NOW line: the settled location (Q-T4) — future above, past
   below. Refined per his review ("clumsy although the sentiment is
   ok"): a small dot on the time axis, the date beside it, one thin
   rule trailing away — a quiet landmark, not a banner */
div.tl-now {
  display: flex; align-items: center; gap: 0.6rem;
  margin: 1.4rem 0; color: var(--accent);
  font-size: 0.8rem; letter-spacing: 0.05em;
  /* #now settles MID-viewport, not at the top — anchored at the top
     the Upcoming band above the line (the next events, the most
     relevant rows) scrolls out of sight (his review, round 4). With
     a short Upcoming band the browser simply stops at the page top,
     which shows everything. */
  scroll-margin-top: 50vh;
}
div.tl-now::before {
  content: ""; flex: 0 0 auto; width: 0.6rem; height: 0.6rem;
  border-radius: 50%; background: var(--accent);
}
div.tl-now::after {
  content: ""; flex: 1; border-top: 1px solid var(--accent); opacity: 0.35;
}
ol.tl-spine, ul.tl-spine, ul.tl-rows { list-style: none; margin: 0; padding: 0; }
ol.tl-spine { border-left: 2px solid var(--hairline); padding-left: 1.1rem; }
ul.tl-spine { border-left: 2px solid var(--hairline); padding-left: 1.1rem; }
/* sticky year headings keep scroll position legible (the ruling);
   SUBSTANTIAL by design — his review: "too small and therefore
   visually insignificant"; the year is the scroll's landmark */
li.tl-year-li h2.tl-year {
  position: sticky; top: 0; z-index: 1;
  margin: 1.8rem 0 0.3rem; padding: 0.3rem 0;
  font-size: 1.8rem; font-weight: 700; letter-spacing: 0.01em;
  background: var(--bg);
}
li.tl-band { margin: 0.4rem 0 1rem; content-visibility: auto; contain-intrinsic-size: auto 8rem; }
li.tl-band h3.tl-month { margin: 0.6rem 0 0.4rem; font-size: 0.9rem; color: var(--muted); }
li.tl-row {
  display: flex; gap: 0.9rem; align-items: flex-start;
  margin: 0.7rem 0;
}
.tl-text { min-width: 0; }
p.tl-head { margin: 0; font-weight: 600; }
p.tl-head a { color: var(--fg); text-decoration: none; }
p.tl-head a:hover, p.tl-head a:focus-visible { text-decoration: underline; }
p.tl-venue { margin: 0.1rem 0 0; color: var(--muted); font-size: 0.85rem; }
p.tl-when {
  margin: 0.1rem 0 0; color: var(--muted); font-size: 0.85rem;
  font-feature-settings: "tnum" 1;
}
/* Q-T3, text-first: the thumb joins the row only where it exists */
/* thumbs form ONE aligned column (his review, round 3: adjacent
   thumbs landed wherever each row's text ended — "cheap"): the row
   caps at a reading width and the thumb right-aligns within it, so
   every thumb shares an edge, near the text rather than the page's
   far side; thumb-less rows still reserve nothing (Q-T3) */
li.tl-row { max-width: 32rem; }
img.tl-thumb, span.tl-thumb {
  flex: 0 0 auto; width: 5.5rem; height: 5.5rem; object-fit: cover;
  border-radius: 4px; border: 1px solid var(--hairline);
  margin-left: auto;
}
span.tl-map { overflow: hidden; display: block; }
span.tl-map svg { width: 100%; height: 100%; display: block; }
/* the miniature map hides its text layers, the grid-tile rule */
span.tl-map svg text { display: none; }
/* Q-T2 gap markers: one quiet line, never elided */
li.tl-gap {
  margin: 0.8rem 0; color: var(--muted); font-size: 0.8rem;
  letter-spacing: 0.05em;
}
li.tl-band[id] , li.tl-year-li h2[id] { scroll-margin-top: 2.5rem; }
/* FEED-04: syndicated rows are MARKED external — the source badge
   beside the title, the link to the source, the snippet shielded —
   so a ticket seller's listing is never mistaken for the owner's own
   event (his ruling 2026-09-02) */
li.tl-feed p.tl-head a { font-weight: 500; }
span.tl-source {
  display: inline-block; vertical-align: middle; white-space: nowrap;
  font-size: 0.68rem; font-weight: 600; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--muted);
  border: 1px solid var(--hairline); border-radius: 3px;
  padding: 0 0.3rem; margin-left: 0.15rem;
}
p.tl-summary { margin: 0.15rem 0 0; color: var(--muted); font-size: 0.85rem; }
/* SEO-05: the feed's quiet entry on the home (the timeline entry's look) */
nav.feed-entry { margin: 0.4rem 0 0; font-size: 0.9rem; }
nav.feed-entry a { color: var(--muted); text-decoration: none; }
nav.feed-entry a:hover, nav.feed-entry a:focus-visible { color: var(--fg); text-decoration: underline; }
@media print {
  li.tl-band { content-visibility: visible; }
  img.tl-thumb, span.tl-thumb { display: none; }
}

/* ---- PRIV-10 legal links: the permanent, quiet path to privacy/
   and terms/ on Tier-3 portfolios — the banner disappears once
   answered, this never does ---- */
nav.legal {
  position: fixed; right: 0.9rem; bottom: 0.5rem; z-index: 3;
  font-size: 0.72rem; opacity: 0.55;
}
nav.legal:hover, nav.legal:focus-within { opacity: 1; }
nav.legal a { color: var(--muted); text-decoration: none; }
nav.legal a:hover, nav.legal a:focus-visible { color: var(--fg); text-decoration: underline; }

/* ---- FEED-02: the stream (0.77.0, Andrew's reputat.io review) —
   one list across every source, newest first, tighter type, the
   source's domain and the fetch date small on each entry, the
   source's colour on its domain text and a thin left edge ---- */
section.feed {
  position: relative; z-index: 1;
  padding: 1.25rem 1.25rem 1.75rem;
}
/* the feed as the FIRST band (a lander without a lead): its title's
   box starts at 2.5rem, the document frame's top line, so the home,
   the kind pages, notes and faq share one top line (his review of the
   composed home 2026-09-15) */
main.item > details.card:not(.card-top) + section.feed { padding-top: var(--title-line); }
/* the closed bottom card never covers the page's end: the last band
   keeps the card's height free under its last row, as a framed work
   centres above the closed card (his review 2026-09-15) */
main.item:has(details.card:not(.card-top)):not(:has(+ footer.site)) > section.feed:last-of-type {
  padding-bottom: calc(1.75rem + 5.5rem + env(safe-area-inset-bottom, 0px));
}
/* the site footer (his review 2026-09-15, his ruling Q-3e of 2026-09-06):
   a quiet line of the legal links at the foot of every page; on a
   lander with the closed bottom card the footer, not the band, keeps
   the card's height free */
footer.site {
  position: relative; z-index: 1; text-align: center;
  padding: 1.5rem 1.25rem; font-size: 0.75rem; letter-spacing: 0.03em; color: var(--muted);
}
footer.site a { color: var(--muted); text-decoration: none; }
footer.site a:hover, footer.site a:focus-visible { color: var(--fg); text-decoration: underline; }
main.item:has(details.card:not(.card-top)) + footer.site {
  padding-bottom: calc(1.5rem + 5.5rem + env(safe-area-inset-bottom, 0px));
}
section.feed h2 {
  margin: 0 0 0.6rem;
  font-size: 0.78rem; font-weight: 600; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--muted);
  border-bottom: 1px solid var(--hairline); padding-bottom: 0.4rem;
}
ul.feed-entries {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: 0.5rem;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  /* dense: a double-width ad that cannot start mid-row drops to the
     next row, and the entry after it back-fills the cell it left (his
     catch: an empty slot beside the seventh entry) */
  grid-auto-flow: dense;
}
ul.feed-entries li {
  background: var(--surface); border: 1px solid var(--hairline);
  /* his review: 3px read too thick, 1px too thin — 2px */
  border-left: 2px solid var(--feed-color, var(--hairline));
  border-radius: 6px; padding: 0.6rem 0.75rem;
  display: flex; flex-direction: column; gap: 0.25rem;
}
a.feed-title {
  font-weight: 600; font-size: 0.92rem; text-decoration: none; line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 2;
  -webkit-box-orient: vertical; overflow: hidden;
}
a.feed-title:hover { color: var(--accent); }
p.feed-meta {
  margin: 0; font-size: 0.68rem; letter-spacing: 0.03em;
  color: var(--muted); font-variant-numeric: tabular-nums;
}
span.feed-source { color: var(--feed-color, var(--muted)); font-weight: 600; overflow-wrap: anywhere; }
/* FEED-05 0.85.0 (Q-H2): the promotion label — a paid placement says so */
span.feed-promo {
  font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; font-size: 0.62rem;
  padding: 0 0.35rem; border-radius: 3px;
  border: 1px solid var(--feed-color, var(--muted)); color: var(--feed-color, var(--muted));
}
span.feed-promo.paid { background: var(--feed-color, var(--muted)); color: var(--bg); border-color: transparent; }
li.feed-entry.promo-lead { border-left-width: 4px; }
/* the label at the mark row's right end: the card's empty corner, level with the monogram */
p.feed-mark .feed-promo { margin-left: auto; }
/* FEED-02 0.83.0 (Q-V1, the visual-interest baseline): the mark row —
   the source's two-letter monogram tile in its colour, the kind glyph */
p.feed-mark { margin: 0; display: flex; align-items: center; gap: 0.45rem; }
p.feed-mark .monogram {
  width: 1.35rem; height: 1.35rem; border-radius: 4px; display: grid; place-items: center;
  background: var(--feed-color, var(--muted)); font-size: 0.6rem; font-weight: 700;
  letter-spacing: 0.04em; line-height: 1;
}
p.feed-mark .monogram.ink-dark { color: #111; }
p.feed-mark .monogram.ink-light { color: #fff; }
/* 0.84.0 (his instruction 2026-09-14): the tile is the way to the source's page */
p.feed-mark a.monogram { text-decoration: none; }
p.feed-mark a.monogram:hover, p.feed-mark a.monogram:focus-visible { outline: 2px solid var(--fg); outline-offset: 1px; }
p.feed-mark svg.kind { width: 1rem; height: 1rem; color: var(--feed-color, var(--muted)); fill: currentColor; }
/* FEED-02 0.83.0 (Q-H5): the way to the kind pages; the current one static */
p.feed-kinds { margin: 0 0 0.6rem; font-size: 0.72rem; letter-spacing: 0.03em; color: var(--muted); }
p.feed-kinds a { color: var(--muted); text-decoration: none; }
p.feed-kinds a:hover, p.feed-kinds a:focus-visible { color: var(--fg); text-decoration: underline; }
p.feed-kinds span { color: var(--fg); font-weight: 600; }
p.feed-empty { color: var(--muted); font-size: 0.9rem; }
/* FEED-02 0.79.0: ad placeholder slots — a labelled box, single or
   double width (double spans two columns where the grid has them) */
li.feed-ad {
  border: 1px dashed var(--hairline); border-radius: 6px; min-height: 6.5rem;
  display: grid; place-items: center; color: var(--muted);
  font-size: 0.68rem; letter-spacing: 0.12em; text-transform: uppercase;
  /* the DTP placeholder (his review 2026-09-15): a hairline cross from
     corner to corner says the box holds content; the label sits on the
     ground so the lines break around it; the rendered size in px at the
     bottom right (the viewer writes it, nothing without scripts) */
  position: relative;
}
/* the cross rides the list item's own surface (that rule outranks a
   bare li.feed-ad), the label masks it where the lines would strike
   the text */
ul.feed-entries li.feed-ad {
  background:
    linear-gradient(to top right, transparent calc(50% - 0.5px), var(--hairline) calc(50% - 0.5px), var(--hairline) calc(50% + 0.5px), transparent calc(50% + 0.5px)),
    linear-gradient(to top left, transparent calc(50% - 0.5px), var(--hairline) calc(50% - 0.5px), var(--hairline) calc(50% + 0.5px), transparent calc(50% + 0.5px)) var(--surface);
}
li.feed-ad .feed-ad-label { background: var(--surface); padding: 0.1rem 0.5rem; border-radius: 3px; }
li.feed-ad .feed-ad-size {
  position: absolute; right: 0.5rem; bottom: 0.3rem; font-size: 0.6rem; letter-spacing: 0.06em;
  text-transform: none; font-variant-numeric: tabular-nums; opacity: 0.85;
}
li.feed-ad a { color: inherit; text-decoration: none; display: grid; place-items: center; width: 100%; height: 100%; }
@media (min-width: 34rem) { li.feed-ad-double { grid-column: span 2; } }
section.feed h2 a.sources-link {
  float: right; margin-left: 1rem; font-weight: 400; letter-spacing: 0.02em;
  text-transform: none; font-size: 0.7rem; color: var(--muted);
}
/* the Sources legend page: swatch + title + domain */
span.source { display: inline-flex; align-items: center; gap: 0.45rem; }
span.source .swatch { width: 0.7rem; height: 0.7rem; border-radius: 2px; background: var(--feed-color, var(--hairline)); }
span.source .domain { color: var(--muted); font-size: 0.8em; }
/* FEED-02 0.84.0 (Q-H6): a source's own page — its colour on the
   heading's swatch, the about line (domain · feed · licence), the way back */
/* the swatch sits level with the FIRST line of the title, not the
   centre of a wrapped one (his review 2026-09-15: "The Guardian: Press
   freedom" on a phone put the colour between the lines) */
main.browse h1 span.source { gap: 0.6rem; align-items: flex-start; }
main.browse h1 span.source .swatch {
  width: 0.85rem; height: 0.85rem; border-radius: 3px; flex: none;
  margin-top: calc((1.15em - 0.85rem) / 2);
}
p.feed-about { margin: -0.4rem 0 1.2rem; color: var(--muted); font-size: 0.85rem; }
p.feed-about a { color: var(--muted); }
p.feed-about .license { font-style: italic; }
p.feed-back { margin: 1.2rem 0 0; font-size: 0.85rem; }
/* CON-21 0.79.0: the FAQ on the card */
details.card section.faq h2 { font-size: 0.85rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); margin: 1rem 0 0.4rem; }
details.card section.faq dl { margin: 0; }
details.card section.faq dt { font-weight: 600; margin-top: 0.6rem; }
details.card section.faq dd { margin: 0.15rem 0 0; color: var(--muted); }
/* CON-21 0.79.0: the FAQ as a document node (his ruling: not a card) */
main.document dl.faq { margin: 0.5rem 0 0; }
main.document dl.faq dt { font-weight: 600; margin-top: 0.9rem; }
main.document dl.faq dd { margin: 0.25rem 0 0; color: var(--muted); }
section.feed h2 .asof {
  float: right; font-weight: 400; letter-spacing: 0.02em; text-transform: none;
  font-size: 0.7rem; opacity: 0.8; font-variant-numeric: tabular-nums;
}
p.feed-summary {
  margin: 0; color: var(--muted); font-size: 0.82rem; line-height: 1.4;
  display: -webkit-box; -webkit-line-clamp: 3;
  -webkit-box-orient: vertical; overflow: hidden;
}
/* FEED-06 0.86.0 (Q-H1, Q-H3): the home composition — the masthead day,
   the hero with its side slot, the rails, the list */
section.feed .feed-masthead {
  /* the page title's size and line (his review 2026-09-15: the date
     read smaller than the other pages' headers); the fetch date and
     the way to the legend sit on its baseline, not floated. The home's
     masthead is an h2 (the lander's h1 is the site), a kind page's is
     its h1 (his review, the same day: the title IS the stream's header) */
  font-size: 2rem; line-height: 1.15; font-weight: 600; letter-spacing: 0; text-transform: none;
  color: var(--fg); font-variant-numeric: tabular-nums; margin: 0 0 0.9rem;
  border-bottom: 1px solid var(--hairline); padding-bottom: 0.4rem;
  display: flex; align-items: baseline; gap: 1rem;
}
section.feed .feed-masthead .asof {
  float: none; margin: 0 0 0 auto; order: 1; font-weight: 400; letter-spacing: 0.02em;
  text-transform: none; font-size: 0.7rem; opacity: 0.8; font-variant-numeric: tabular-nums;
}
section.feed .feed-masthead a.sources-link {
  float: none; margin: 0; order: 2; font-weight: 400; letter-spacing: 0.02em;
  text-transform: none; font-size: 0.7rem; color: var(--muted);
}
/* the title never breaks; on a phone it takes the first line and the
   fetch date and the legend link the second, at the right */
section.feed .feed-masthead > :first-child { white-space: nowrap; }
@media (max-width: 40rem) {
  section.feed .feed-masthead { flex-wrap: wrap; row-gap: 0.2rem; }
  section.feed .feed-masthead > :first-child { flex: 0 0 100%; }
}
div.feed-hero { display: grid; gap: 0.5rem; grid-template-columns: 1fr; margin: 0 0 0.6rem; }
ul.feed-entries.feed-lead, ul.feed-entries.feed-side { grid-template-columns: 1fr; }
/* the premium slot beside the hero (his second review 2026-09-14): a
   FIXED 300px column (a percentage width inside an auto track resolved
   to the label's width, a 135px box) and ONE row of equal height: the
   hero card and the medium rectangle (250px at least) stretch to the
   taller of the two, so no band opens under the hero. On a phone the
   slot stacks under the hero at the page's width, the base 6.5rem tall */
@media (min-width: 40rem) {
  div.feed-hero.with-side { grid-template-columns: minmax(0, 1fr) 300px; }
  ul.feed-entries.feed-side li.feed-ad { min-height: 250px; }
}
li.feed-entry.feed-lead { padding: 0.9rem 1rem; border-left-width: 4px; gap: 0.4rem; }
li.feed-entry.feed-lead a.feed-title { font-size: 1.25rem; line-height: 1.25; -webkit-line-clamp: 3; }
li.feed-entry.feed-lead p.feed-summary { font-size: 0.9rem; -webkit-line-clamp: 6; }
section.feed h3.feed-rail-head {
  margin: 0.9rem 0 0.4rem; font-size: 0.72rem; font-weight: 600; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--muted);
}
section.feed h3.feed-rail-head a { color: inherit; text-decoration: none; }
section.feed h3.feed-rail-head a:hover, section.feed h3.feed-rail-head a:focus-visible { color: var(--fg); text-decoration: underline; }
/* a rail fills its row on a wide screen (the grid as the stream's) and
   scrolls sideways on a phone */
@media (max-width: 39.99rem) {
  ul.feed-entries.feed-rail {
    display: flex; gap: 0.5rem; overflow-x: auto; scroll-snap-type: x mandatory;
    padding-bottom: 0.3rem; scrollbar-width: thin;
  }
  ul.feed-entries.feed-rail > li { flex: 0 0 78vw; scroll-snap-align: start; }
}
@media (min-width: 40rem) { ul.feed-entries.feed-rail { grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); } }
/* a rail's end slot never wraps alone at half width (his second review):
   on a wide screen it is a strip across the rail beneath the cards; on
   a phone it is the last card of the scroller */
@media (min-width: 40rem) { ul.feed-entries.feed-rail > li.feed-ad { grid-column: 1 / -1; min-height: 4.5rem; } }

/* ---- PRIV-09 consent banner: plain, quiet, dismissible — shown only
   when a consent-gated provider exists and no choice is stored ---- */
div.consent {
  position: fixed; inset: auto 0 0 0; z-index: 5;
  display: flex; flex-wrap: wrap; gap: 0.6rem 1rem; align-items: center;
  padding: 0.8rem 1.25rem;
  background: var(--card-backdrop);
  backdrop-filter: blur(var(--card-blur));
  -webkit-backdrop-filter: blur(var(--card-blur));
  border-top: 1px solid var(--hairline);
  font-size: 0.9rem;
}
div.consent p { margin: 0; color: var(--muted); }
div.consent a { color: var(--fg); }
div.consent button {
  background: none; border: 1px solid var(--accent); border-radius: 3px;
  color: var(--accent); padding: 0.35rem 1rem; cursor: pointer;
  font: inherit;
}
div.consent button.consent-no { border-color: var(--muted); color: var(--muted); }
ul.providers { padding-left: 1.2rem; max-width: 46rem; }
ul.providers li { margin: 0.5rem 0; }
div.consent-manage { margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid var(--hairline); }
div.consent-manage .status { color: var(--muted); }
div.consent-manage button {
  background: none; border: 1px solid var(--accent); border-radius: 3px;
  color: var(--accent); padding: 0.35rem 1rem; cursor: pointer;
  font: inherit; margin-right: 0.6rem;
}
div.consent-manage button.consent-no { border-color: var(--muted); color: var(--muted); }

/* ---- 0.48.0 brand logo in the home pill: cap to the pill's line ---- */
nav.home img.brand {
  height: 1.1em; width: auto; vertical-align: -0.18em; margin-right: 0.45em;
}

/* ---- 0.48.0 category :effect :sparkle (CON-05): tiny twinkling
   marks either side of the label. CSS-only; without motion consent
   they hold as faint static marks — the effect still reads, nothing
   moves ---- */
.fx-sparkle { position: relative; }
/* block-level hosts (the category page h1) must hug their text or
   the right-hand mark lands at the far page edge */
h1.fx-sparkle { display: inline-block; }
.fx-sparkle::before, .fx-sparkle::after {
  content: "\2726"; position: absolute; font-size: 0.45em; line-height: 1;
  opacity: 0.35; pointer-events: none; text-decoration: none;
}
.fx-sparkle::before { top: -0.5em; right: -0.7em; }
.fx-sparkle::after { bottom: -0.35em; left: -0.8em; }
@media (prefers-reduced-motion: no-preference) {
  .fx-sparkle::before { animation: plinth-twinkle 2.6s ease-in-out infinite; }
  .fx-sparkle::after { animation: plinth-twinkle 2.6s ease-in-out 1.3s infinite; }
}
@keyframes plinth-twinkle {
  0%, 100% { opacity: 0; transform: scale(0.6); }
  50% { opacity: 0.9; transform: scale(1); }
}

/* ---- in-place slideshow home (PRES-10 :slideshow, ruled 2026-08-10):
   URL constant; first slide is the no-JS content; crossfade honours
   prefers-reduced-motion via the animation kill below ---- */
div.slides section.slide { display: none; }
div.slides section.slide.active { display: block; }
/* the IMAGE fades, never the slide or its figure (0.53.2, Andrew's two
   catches): an animated slide became a stacking context that trapped
   the card's z-index beneath the loop's arrows, and an animated figure
   faded its own WALL away — the page's light background flashed
   through a grey or black wall. The painting now emerges from a wall
   that never moves; slower than before, so the loop reads relaxed. */
/* 0.71.0 (Q-L4): the fade duration is a knob — ONE custom property,
   TWO stylesheet defaults (wall emergence 1.8s here, full-bleed
   dissolve 0.9s below); an owner value drives whichever style the
   loop actually uses */
div.slides section.slide.active figure img { animation: var(--slide-fade, 1.8s) ease both slide-fade; }
@keyframes slide-fade { from { opacity: 0; } }
/* 0.65.0 (his flashing-loop finding): a FULL-BLEED loop
   (data-loop-transition="crossfade") cross-dissolves instead — the
   wall-emergence fade exposed the bare page background on every
   advance, a white flash behind dark photographs. The dissolve is a
   same-document view transition (the viewer's .loop-vt class rides
   only while one runs, so page-to-page transitions keep their own
   tempo); browsers without the API cut cleanly. Framed loops keep the
   emergence above. Reduced motion: the global ::view-transition kill
   makes the dissolve a cut, as ever. */
html[data-loop-transition="crossfade"] div.slides section.slide.active figure img { animation: none; }
html.loop-vt::view-transition-old(root),
html.loop-vt::view-transition-new(root) { animation-duration: var(--slide-fade, 0.9s); }

/* ---- PRES-22 0.71.0 (Q-L1): the relaxed arrow hint — the FIRST
   card open of a session pulses the summary arrow, three gentle
   cycles, then rests. DELAYED past the card's own opening animation
   (his preview review: fired together, the pulse drowned in the
   bigger motion and was easy to miss) and given a small bob beside
   the opacity dip — isolated, slightly fuller motion draws the eye
   while staying calm. The viewer adds .hint-pulse once per session
   (sessionStorage) and never on a card carrying a metallic sheen
   (the 0.57.0 tempo law: effects never compete); reduced-motion
   pages open the card immediately and never reach the pulse path,
   and the media query guards the animation besides. ---- */
@media (prefers-reduced-motion: no-preference) {
  details.card.hint-pulse summary::after {
    animation: plinth-card-hint 1.6s ease-in-out 0.8s 3;
  }
}
/* opacity only — the earlier 2px bob MOVED the arrow, his catch */
@keyframes plinth-card-hint {
  50% { opacity: 0.15; }
}
/* PRES-22 0.77.0 (:card-hint :peek, his reputat.io review): the
   static page's invitation — the arrow keeps pulsing for the whole
   7 s sequence (the viewer removes the class at its end), and the
   peek clamps the card's content to its first 8rem: the title and
   the first line, the eased height doing the motion */
@media (prefers-reduced-motion: no-preference) {
  details.card.hint-peek summary::after {
    animation: plinth-card-hint 1.4s ease-in-out infinite;
  }
}
details.card.card-peek[open]::details-content { height: 8rem; overflow: clip; }

/* the initial close must NOT animate (0.71.0, his Chrome catch: the
   card ships open for no-JS visitors, and the load-time close eased
   into a visible collapse right after paint — the "initial judder";
   Safari, without the interpolation path, closed instantly and read
   smoother). The viewer wears this class around the entry close
   only; the timed retract keeps its ease. */
details.card.card-instant::details-content { transition: none; }
/* the card's content eases in and out of view instead of snapping
   (0.53.2): height auto interpolates where interpolate-size exists
   (Chrome 129+); elsewhere it simply toggles, as before. (0.71.0
   NOTE: a duplicate ease was briefly added here on his "still
   clunky" review before finding THIS rule already does it — removed;
   where the clunk persists, the browser lacks the interpolation
   path, which is a support matter, not a missing rule.) */
html { interpolate-size: allow-keywords; }
details.card::details-content {
  display: block; height: 0; opacity: 0; overflow: clip;
  transition: height 0.7s ease, opacity 0.7s ease, content-visibility 0.7s allow-discrete;
}
details.card[open]::details-content { height: auto; opacity: 1; }
nav.slides-nav { display: none; } /* JS-only controls */
html.js nav.slides-nav { display: block; }
nav.slides-nav button {
  /* z-index 1 and font: inherit (0.53.2, Andrew's review): the loop's
     arrows are the SAME glyph as the item pages' (a UA button font
     drew a different one), and they sit BELOW the card — an open card
     covers the arrows rather than appearing to carry them */
  position: fixed; top: 50%; transform: translateY(-50%); z-index: 1;
  background: none; border: none; cursor: pointer; font: inherit;
  padding: 0.6rem 0.8rem; font-size: var(--arrow-size); opacity: 0.7;
  color: var(--wall-ink, var(--fg));
  text-shadow: 0 0 8px var(--wall-halo, var(--halo)), 0 0 2px var(--wall-halo, var(--halo));
}
nav.slides-nav button:hover, nav.slides-nav button:focus-visible { opacity: 1; }
nav.slides-nav .prev { left: 0.25rem; }
nav.slides-nav .next { right: 0.25rem; }
details.card summary a { text-decoration: none; }
details.card summary a:hover { text-decoration: underline; }
@media (prefers-reduced-motion: reduce) {
  div.slides section.slide.active figure img { animation: none; }
  details.card::details-content { transition: none; }
}

/* ---- home affordance: minimal chrome back to the tree root ---- */
/* chrome hangs on the SAME column grid as page content (Andrew,
   2026-08-13: the title pill's left edge must align with the
   elements below): the 80rem container's content edge, or 1.25rem
   on narrow viewports — identical on every page type. Known nuance:
   100vw counts a CLASSIC scrollbar, so on non-overlay-scrollbar OSes
   a scrolling page shifts chrome ~7px; exact under macOS/iOS overlay
   scrollbars */
nav.home {
  position: fixed; top: 0.6rem; z-index: 3;
  left: calc(max((100vw - 80rem) / 2, 0rem) + 1.25rem);
}
/* card-edge :top (0.49.0, Andrew's review): the card's title owns the
   top-left, so the pill mirrors to the BOTTOM with the same spacing —
   a stable identity anchor balancing the legal links' corner */
main.item:has(details.card.card-top) nav.home {
  top: auto; bottom: 0.6rem;
}
/* the top-right control cluster: Browse, and — when exhibitions
   exist — the way into /x/ (Andrew's placement, 2026-08-13) */
nav.browse {
  position: fixed; top: 0.6rem; z-index: 3;
  right: calc(max((100vw - 80rem) / 2, 0rem) + 1.25rem);
  display: flex; gap: 0.4rem;
}
/* 0.60.0 (his interaction-model rationalisation): in an assembly the
   SWITCH replaces the branding pill — the cluster that carries it
   (switch first, Browse at its right) takes the top LEFT corner, and
   its dropdown hangs from the pill's left edge */
nav.browse:has(details.sections) {
  right: auto;
  left: calc(max((100vw - 80rem) / 2, 0rem) + 1.25rem);
}
nav.browse:has(details.sections) details.sections ul { right: auto; left: 0; }
/* boxed chrome (per Andrew, 2026-08-13): the site name and the
   cluster links sit in pill boxes — the category-pill style — so they
   are legible over any media under any palette, no contrast tricks */
nav.home a, nav.browse a, nav.home > span, nav.browse > span,
nav.browse summary, nav.browse li > span {
  display: inline-block; padding: 0.35rem 0.8rem; text-decoration: none;
  font-size: 0.85rem; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--fg); background: var(--card-backdrop);
  border: 1px solid var(--hairline); border-radius: var(--pill-radius);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  /* quiet by default, distinct on hover/focus (Andrew, 2026-08-13):
     present without disrupting the page. A bare span = the CURRENT
     location: same slot, no link, no hover raise (2026-08-14) */
  opacity: 0.55; transition: opacity 0.15s;
}
nav.home a:hover, nav.home a:focus-visible,
nav.browse a:hover, nav.browse a:focus-visible,
nav.browse summary:hover, nav.browse summary:focus-visible,
nav.browse details[open] > summary { opacity: 1; }
/* PRES-23 (0.56.0, Andrew's round-2 ruling): the SECTION SWITCH — in
   an assembly the cluster's first pill names the current section with
   a caret and opens the section list as stacked pills beneath it. A
   native <details>: works without JavaScript; the viewer only adds
   close-on-outside-tap and Escape. Same pill language throughout —
   "it should not be a deviation from the existing style". */
/* --caret-w lives on the <details> so BOTH the summary's arrow segment
   and the list beneath read it (defined on the summary alone, the list —
   a sibling — fell back to 0 and hung from the pill's right edge:
   Andrew's screenshot, 2026-08-23) */
nav.browse details.sections { position: relative; --caret-w: 2.1rem; }
nav.browse summary { list-style: none; cursor: pointer; user-select: none; }
nav.browse summary::-webkit-details-marker { display: none; }
/* the arrow is its own SEGMENT — text on the left, a vertical hairline,
   the arrow on the right (Andrew's review, 2026-08-23: the small caret
   was barely legible; the split form he named is the reference) */
/* the pill's own padding moves onto its two segments, so the separator
   runs the FULL height of the pill; the arrow segment has a fixed width
   (--caret-w) and the list below hangs from the separator, not from the
   pill's right edge (Andrew's review, 2026-08-23: "the vertical line …
   would be better if it extended from the top to the bottom … the down
   arrow is very indistinct … right-aligned to the left of the menu
   vertical line") */
nav.browse summary { display: inline-flex; align-items: stretch; gap: 0; padding: 0; }
nav.browse summary > span[data-nosnippet] { display: inline-block; padding: 0.35rem 0.8rem; }
nav.browse summary .caret {
  display: inline-flex; align-items: center; justify-content: center;
  align-self: stretch;
  width: var(--caret-w); box-sizing: border-box; padding: 0;
  border-left: 1px solid color-mix(in srgb, currentColor 40%, transparent);
}
/* the caret is an SVG triangle, not a font glyph (his correction: the
   ▼ sat high in its segment — glyph ink rides font metrics; a drawn
   triangle centres geometrically, vertically AND horizontally) */
nav.browse summary .caret svg { display: block; width: 0.6em; height: 0.42em; }
nav.browse summary .caret path { fill: currentColor; }
/* the NEUTRAL position reads Home (0.59.1, his phone review — the
   ghost-sized empty pill it replaces read as blank and strange) */
nav.browse summary > span[data-nosnippet]:empty { display: none; }
/* an OPEN switch must be READABLE over whatever sits beneath (his
   phone review: the translucent stack over a page title was "almost
   unreadable"): while open, the whole cluster goes solid */
nav.browse:has(details.sections[open]) a,
nav.browse:has(details.sections[open]) summary,
nav.browse:has(details.sections[open]) > span,
nav.browse:has(details.sections[open]) li > span {
  background: var(--surface); opacity: 1;
}
nav.browse details.sections ul {
  list-style: none; margin: 0.4rem 0 0; padding: 0;
  position: absolute; right: var(--caret-w, 0); top: 100%;
  display: flex; flex-direction: column; gap: 0.3rem; align-items: stretch;
  /* the ABSOLUTE list shrink-fits to its containing pill, so a long
     entry wrapped inside it ("BORDERS CREATIONS", his screenshot);
     max-content frees the column, stretch + nowrap make EVERY entry
     as wide as the longest — his ruling */
  width: max-content;
}
nav.browse details.sections li > a,
nav.browse details.sections li > span {
  display: block; box-sizing: border-box; white-space: nowrap;
}
nav.browse details.sections li > span { opacity: 0.35; color: var(--muted); cursor: default; }
/* small screens: the pills collided side-by-side (Andrew's mobile
   screenshot) — stack them in one left-aligned column, smaller and
   quieter still, so photographs keep the frame */
@media (max-width: 40rem) {
  nav.browse {
    right: auto; left: 1.25rem; top: 3rem;
    flex-direction: column; align-items: flex-start; gap: 0.3rem;
  }
  /* no branding pill above the switch cluster (0.60.0) — it starts
     at the top */
  nav.browse:has(details.sections) { top: 0.6rem; }
  nav.home a, nav.browse a, nav.home > span, nav.browse > span,
  nav.browse summary, nav.browse li > span {
    font-size: 0.72rem; padding: 0.25rem 0.6rem; opacity: 0.4;
  }
  /* the column already stacks: the section list flows in place */
  nav.browse details.sections ul { position: static; }
  nav.browse summary { padding: 0; }
  nav.browse summary > span[data-nosnippet] { padding: 0.25rem 0.6rem; }
  nav.browse summary .caret { padding: 0.25rem 0; }
  nav.home > span, nav.browse > span { opacity: 0.25; }
  main.item nav.home a, main.item nav.browse a { opacity: 0.2; }
  main.item nav.home > span, main.item nav.browse > span { opacity: 0.17; }
}
/* an INACTIVE pill (the you-are-here state) reads as such: fainter
   than the links beside it, muted text, and deliberately NO hover
   response (Andrew, 2026-08-14) */
nav.home > span, nav.browse > span {
  opacity: 0.35; color: var(--muted); cursor: default;
}
/* over full-bleed media the chrome fades further — clearly present,
   very much diminished, until hover (Andrew, 2026-08-14) */
main.item nav.home a, main.item nav.browse a, main.item nav.browse summary { opacity: 0.3; }
/* inactive pills stay LEGIBLE over media — 0.18 vanished entirely
   (Andrew's water-page screenshot); the muted colour still marks
   them quieter than the links beside them */
main.item nav.home > span, main.item nav.browse > span { opacity: 0.25; }
main.item nav.home a:hover, main.item nav.home a:focus-visible,
main.item nav.browse a:hover, main.item nav.browse a:focus-visible,
main.item nav.browse summary:hover, main.item nav.browse summary:focus-visible,
main.item nav.browse details[open] > summary { opacity: 1; }


/* MAP-02 (0.59.0): the place-map background — every element generated
   by the build from committed OSM snapshots (ODbL credit inside the
   SVG); colours are the PALETTE's, so the map wears the site */
figure.map-bg {
  position: fixed; inset: 0; margin: 0; z-index: 0; overflow: hidden;
}
figure.map-bg svg { width: 100%; height: 100%; display: block; }
.place-map .map-ground { fill: var(--surface-2); }
.place-map .map-water { fill: color-mix(in srgb, var(--accent) 18%, var(--surface)); }
.place-map .map-water-line { stroke: color-mix(in srgb, var(--accent) 30%, var(--surface)); stroke-width: 3; }
.place-map .map-road { stroke: var(--muted); stroke-linecap: round; stroke-linejoin: round; }
.place-map .map-road-major { stroke-width: 5; }
.place-map .map-road-minor { stroke-width: 3; opacity: 0.75; }
.place-map .map-road-path { stroke-width: 1.5; opacity: 0.5; stroke-dasharray: 4 4; }
.place-map .map-label {
  fill: var(--fg); opacity: 0.55; font-size: 20px;
  letter-spacing: 0.04em; paint-order: stroke; stroke: var(--surface); stroke-width: 4px;
}
/* the pin's OWN colour token — distinct from roads and accent greys;
   owners may override --map-pin in the palette */
.place-map .map-pin path { fill: var(--map-pin, #b03030); stroke: var(--surface); stroke-width: 2; }
.place-map .map-pin circle { fill: var(--surface); }
.place-map .map-pin-label {
  fill: var(--fg); font-size: 22px; font-weight: 600; letter-spacing: 0.02em;
  paint-order: stroke; stroke: var(--surface); stroke-width: 5px;
}
.place-map .map-credit { fill: var(--muted); font-size: 12px; opacity: 0.8; }
/* the card and headline ride above the map; the navs are FIXED and
   keep their own stacking (an earlier catch-all here overrode
   position:fixed and threw the whole chrome into the flow — Andrew's
   "jumbled and janky" screenshot) */
main.item:has(figure.map-bg) > h1,
main.item:has(figure.map-bg) > details.card { position: relative; z-index: 1; }
/* the PIN stays visible with the card OPEN (Andrew's condition): a
   map-backed page anchors its card to the bottom, leaving the map's
   upper half — where the pin sits — clear */
main.item:has(figure.map-bg) {
  display: flex; flex-direction: column; min-height: 100vh; box-sizing: border-box;
  /* the V-134 class again, in flow this time: iOS's 100vh is the
     LARGEST viewport, so with Safari's bar showing the bottom-anchored
     card sat below the visible edge (his phone review: scrollbar +
     clipped card when closed). dvh tracks the visible height. */
  min-height: 100dvh;
}
main.item:has(figure.map-bg) details.card { margin-top: auto; }
/* over a MAP the chrome must not fade (Andrew: the pills' colouring is
   too close to the map's): solid surface, full presence */
main.item:has(figure.map-bg) nav.home a,
main.item:has(figure.map-bg) nav.browse a,
main.item:has(figure.map-bg) nav.browse summary,
main.item:has(figure.map-bg) nav.browse > span,
main.item:has(figure.map-bg) nav.browse li > span {
  background: var(--surface); opacity: 0.95;
  border-color: color-mix(in srgb, var(--muted) 35%, transparent);
}
/* a TILE map is a miniature: text would be ~3px — hide it there */
ul.grid .map-tile .map-label, ul.grid .map-tile .map-road-label,
ul.grid .map-tile .map-water-label, ul.grid .map-tile .map-area-label,
ul.grid .map-tile .map-road-ref, ul.grid .map-tile .map-pin-label,
ul.grid .map-tile .map-credit { display: none; }
ul.grid .map-tile svg { width: 100%; height: 100%; display: block; }
/* the map's ONE caption panel (round 6): the legend with the ODbL
   credit as its final line. It rides the FLOW at the lower right,
   directly above the card — the top-right overlay crowded the
   small-screen map (his review); flex keeps the placement honest at
   every viewport. Narrow by design — the old free-standing credit
   line ran under the wrapped pill cluster on phones */
main.item:has(figure.map-bg) aside.map-key {
  position: relative; z-index: 1;
  margin-top: auto; align-self: flex-end;
  margin-right: 1rem; margin-bottom: 0.6rem;
  max-width: 10rem;
  font-size: 0.68rem; color: var(--muted); line-height: 1.6;
  background: color-mix(in srgb, var(--surface) 82%, transparent);
  padding: 0.35rem 0.55rem; border-radius: 0.6rem;
}
/* the panel takes over the card's push-to-bottom; the card follows */
main.item:has(figure.map-bg) aside.map-key + details.card { margin-top: 0.6rem; }
.map-key ul.map-legend { list-style: none; margin: 0; padding: 0; }
.map-key ul.map-legend li { display: flex; align-items: center; gap: 0.4rem; }
.map-key svg.map-swatch { width: 1em; height: 1em; flex: none; }
.map-key p.map-credit-line {
  margin: 0.35rem 0 0; padding-top: 0.3rem; text-align: left;
  border-top: 1px solid color-mix(in srgb, var(--muted) 30%, transparent);
  font-size: 0.66rem; line-height: 1.35;
}
/* a phone's tall card leaves a short map strip — the panel tightens
   so it costs less of it */
@media (max-width: 30rem) {
  main.item:has(figure.map-bg) aside.map-key {
    font-size: 0.62rem; line-height: 1.5; max-width: 8.5rem;
  }
  .map-key p.map-credit-line { font-size: 0.6rem; }
}
/* under a tiled grid the credit is a quiet line attached to the
   grid's LEFT edge — right-aligned it floated alone in the viewport's
   far corner when the tiles filled half a row (his "clunky") */
p.map-credit-line { font-size: 0.72rem; color: var(--muted); text-align: left; margin: 0.5rem 0 0; }
.place-map .map-building { fill: color-mix(in srgb, var(--fg) 12%, var(--surface-2)); }
.place-map .map-green { fill: color-mix(in srgb, #4f7a4a 14%, var(--surface-2)); }
.place-map .map-road-label {
  fill: var(--muted); font-size: 15px; letter-spacing: 0.03em;
  paint-order: stroke; stroke: var(--surface); stroke-width: 3.5px;
  text-anchor: middle;
}
/* road-number badges (round 6: "which road number am I on") — the
   atlas idiom, upright, in the site's own surfaces */
.place-map .map-road-ref rect { fill: var(--surface); stroke: var(--muted); stroke-width: 1.5; opacity: 0.92; }
.place-map .map-road-ref text { fill: var(--fg); font-size: 14px; font-weight: 600; letter-spacing: 0.04em; }
/* rivers wear the water colour, italic — the cartographic convention */
.place-map .map-water-label {
  fill: color-mix(in srgb, var(--accent) 60%, var(--fg));
  font-size: 15px; font-style: italic; letter-spacing: 0.05em;
  paint-order: stroke; stroke: var(--surface); stroke-width: 3.5px;
  text-anchor: middle;
}
/* named areas — parks, water bodies, landmark buildings — at their
   centroids, quieter than roads */
.place-map .map-area-label {
  fill: var(--muted); font-size: 13px; letter-spacing: 0.06em;
  paint-order: stroke; stroke: var(--surface); stroke-width: 3px;
}
.place-map .map-area-water { fill: color-mix(in srgb, var(--accent) 60%, var(--fg)); font-style: italic; }
.place-map .map-area-green { fill: color-mix(in srgb, #4f7a4a 55%, var(--fg)); }

/* ---- prev/next (PRES-03 static half; keyboard/swipe arrive with the viewer) */
nav.seq a {
  position: fixed; top: 50%; transform: translateY(-50%); z-index: 1; /* below the card (0.53.2) */
  padding: 0.6rem 0.8rem; text-decoration: none; font-size: var(--arrow-size);
  color: var(--wall-ink, var(--fg)); opacity: 0.7; transition: opacity 0.2s;
  /* legible over any media, including pure white (PRES-04 spirit);
     on a framed page the wall decides the ink (0.53.2) */
  text-shadow: 0 0 8px var(--wall-halo, var(--halo)), 0 0 2px var(--wall-halo, var(--halo));
}
nav.seq a:hover, nav.seq a:focus-visible { opacity: 1; }
nav.seq a[rel="prev"] { left: 0.25rem; }
nav.seq a[rel="next"] { right: 0.25rem; }

/* ---- index & category: media-first grid ---- */
main.browse {
  /* the title's box starts at 2.5rem, the document frame's top line,
     on every page type (his review 2026-09-15: one top line for the
     home, the kind pages, notes and faq); the glyphs clear the fixed
     chrome row (0.6rem + the pill), whose position is the same on
     EVERY page type (Andrew, 2026-08-13) */
  padding: var(--title-line) 1.25rem 1.25rem; max-width: 80rem; margin: 0 auto;
}
/* the mobile chrome STACKS the switch over the cluster (the column
   ends at 4.4rem): the title sits the same 0.9rem under it as a single
   pill's title does (his review 2026-09-15: the phone's title line
   differed between the home and the kind pages) — must sit after the
   base rule: same specificity, cascade decides */
@media (max-width: 40rem) {
  main.browse[data-chrome="2"] { padding-top: 5.25rem; }
}
/* the feed pages (a kind's page, a source's page, the legend) keep the
   home band's reading width instead of the media grid's 80rem, so the
   site reads as one column throughout (his review 2026-09-15) */
main.browse.stream { max-width: 48.5rem; }
main.browse h1 { font-weight: 600; letter-spacing: 0.01em; margin-top: 0; line-height: 1.15; }
/* inside the browse frame the band pads nothing but its foot: the
   frame's own padding is the column, so the cards align with the title
   and the pill as the home's do, and a kind page's masthead sits on the
   title line (his review 2026-09-15: consistency in every visual item;
   the kind pages' cards sat 1.25rem inside, their title 105px above the
   first card) */
main.browse > section.feed { padding: 0 0 1.75rem; }
ul.grid {
  list-style: none; margin: 1.5rem 0; padding: 0;
  display: grid; gap: 0.75rem;
  grid-template-columns: repeat(auto-fill, minmax(min(46vw, 15rem), 1fr));
}
ul.grid a { text-decoration: none; display: block; }
/* THE grid tile is the framed card (per Andrew, 2026-08-13, adopted
   portfolio-wide): SQUARE photograph bordered with the title beneath,
   separated from the background */
ul.grid li {
  background: var(--surface); border-radius: 6px; overflow: hidden;
  border: 1px solid var(--hairline);
  /* a browse page may hold thousands of tiles: skip layout and paint
     for off-screen ones while keeping their reserved height exact, so
     scrollbars and anchors stay stable (13a) */
  content-visibility: auto; contain-intrinsic-size: auto 15rem;
}
ul.grid img, ul.grid .ph {
  width: 100%; aspect-ratio: 1; object-fit: cover; display: block;
  background: var(--surface);
}
ul.grid .ph {
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-2); color: var(--muted); font-size: 1.9rem;
}
ul.grid .label {
  display: block; color: var(--fg); font-size: 0.85rem;
  padding: 0.5rem 0.65rem 0.5rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* CON-18 (0.57.0): the map link opens OpenStreetMap; on a phone the
   viewer swaps its target for the geo: URI in data-geo (the phone's own
   maps app) — the stylesheet only marks it */
p.venue a.map { margin-left: 0.25em; white-space: nowrap; }
p.venue a.map::before { content: "\2316\00a0"; opacity: 0.7; }
ul.grid .sub {
  display: block; margin-top: -0.45rem; padding: 0 0.65rem 0.55rem;
  color: var(--muted); font-size: 0.8rem;
}
ul.grid a:hover .label { color: var(--accent); }
.sr-only {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
p.crumb { margin: 0 0 0.2rem; }
p.crumb a { color: var(--muted); font-size: 0.9rem; }
p.crumb a::before { content: "\2190\00a0"; }
ul.categories { list-style: none; padding: 0; display: flex; flex-wrap: wrap; gap: 0.5rem; }
ul.categories a {
  display: inline-block; padding: 0.25rem 0.9rem;
  border: 1px solid var(--hairline); background: var(--surface);
  border-radius: var(--pill-radius); text-decoration: none;
  color: var(--muted); font-size: 0.9rem;
}
ul.categories a:hover { color: var(--fg); border-color: var(--muted); }
/* the taxonomy marks (\u00a7 categories, # tags) read as marks, not
   words: dimmer than the text they prefix (Andrew, 2026-08-14) */
span.pfx { opacity: 0.5; }
div.definition { max-width: 46rem; color: var(--muted); }
p.hero a { color: var(--accent); }

/* ---- cross-document view transitions (PRES-02 registry, static half).
   Progressive: browsers without the API navigate normally. ---- */
@view-transition { navigation: auto; }
html.t-slide-h::view-transition-old(root) {
  animation: 0.28s ease both slide-out-l;
}
html.t-slide-h::view-transition-new(root) {
  animation: 0.28s ease both slide-in-r;
}
html.t-zoom::view-transition-old(root) { animation: 0.3s ease both fade-out; }
html.t-zoom::view-transition-new(root) { animation: 0.3s ease both zoom-in; }
/* t-crossfade: the API's default cross-fade — no extra rules needed */
@keyframes slide-out-l { to { transform: translateX(-6%); opacity: 0; } }
@keyframes slide-in-r { from { transform: translateX(6%); opacity: 0; } }
@keyframes fade-out { to { opacity: 0; } }
@keyframes zoom-in { from { transform: scale(1.03); opacity: 0; } }

/* ---- accessibility: motion (PRES-03, TEST-09) ---- */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
  nav.seq a { transition: none; }
}

/* ---- print (PRES-12): ink-legible, flattened, no chrome ---- */
@media print {
  body { background: #fff; color: #000; font-size: 11pt; }
  main.item { min-height: 0; }
  main.item figure { height: auto; }
  main.item figure img { max-height: 60vh; }
  /* PRES-24: motion contributes nothing on ink */
  main.item figure video { display: none; }
  details.card { position: static; background: none; padding: 0.5rem 0;
                 backdrop-filter: none; border-top: none; }
  details.card:not([open]) { display: block; }
  details.card .text p, details.card .meta { color: #222; }
  nav.seq, nav.home, ul.categories { display: none; }
  p.cta a { border: none; padding: 0; color: #000; text-decoration: underline; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 0.8em; }
  footer.rights { color: #444; }
}

/* ---- dictionary-entry category header (CON-05) ---- */
header.dict h1 { display: inline-block; margin-bottom: 0.2rem; }
p.when {
  color: var(--muted); font-size: 0.9rem; letter-spacing: 0.04em;
  font-feature-settings: "tnum" 1; margin: 0.1rem 0 0.4rem;
}
header.dict .phon {
  margin-left: 0.8rem; color: var(--muted); font-size: 1.05rem;
}
header.dict .def { max-width: 46rem; color: var(--fg); margin: 0.3rem 0 0.4rem; }
header.dict .related { color: var(--muted); font-size: 0.9rem; }
header.dict .related a { color: var(--fg); }

/* ---- deck mode (PRES-14): slides application ---- */
/* the backdrop spans the WHOLE slide container, not one viewport
   height: an overflowing slide scrolls (mobile), and a 100svh bg
   left a bare band below the fold (Andrew's iPhone, 2026-08-18).
   Absolute against the positioned main, it covers all content at
   every scroll position — no fixed-layer viewport games. */
main.deck { position: relative; }
main.deck figure.bg {
  position: absolute; inset: 0; z-index: 0;
  /* explicit: the generic main.item figure height (100svh) would
     otherwise win over the inset stretch */
  height: 100%;
}
main.deck figure.bg figure { height: 100%; }
/* :background {:scrim true} — dim an image backdrop for legibility */
main.deck figure.bg.scrimmed::after {
  content: ""; position: absolute; inset: 0;
  /* the palette's --scrim token: dark-to-transparent by default;
     dark-text palettes override it light */
  background: var(--scrim);
}
main.deck .slide-content {
  position: relative; z-index: 1; min-height: 100svh;
  display: flex; flex-direction: column; justify-content: center;
  max-width: 58rem; margin: 0 auto; padding: 8vh 8vw;
}
/* the legibility glow exists FOR photographs — a solid-colour slide
   without one must stay sharp (Andrew, 2026-08-16: "janky") */
main.deck figure.bg ~ .slide-content {
  text-shadow: 0 0 10px rgba(8, 8, 10, 0.7);
}
main.deck > h1 { position: static; left: auto; }
main.deck .slide-content h1 {
  position: static; font-size: clamp(1.8rem, 5vw, 3.4rem);
  line-height: 1.12; margin: 0.2em 0 0.4em;
}
main.deck .slide-content .text { max-width: none; font-size: 1.15rem; }
main.deck .slide-content .text li { margin: 0.35em 0; }
/* inline media on a slide: contained, never the full-bleed treatment */
main.deck .slide-content figure {
  height: auto; margin: 1.2rem 0; background: transparent;
  justify-items: start;
}
main.m-cover .slide-content figure img, main.m-contain .slide-content figure img {
  width: auto; height: auto; max-width: 100%; max-height: 52svh;
  object-fit: contain; border-radius: 6px;
}
p.kicker {
  color: var(--accent); text-transform: uppercase; font-size: 0.8rem;
  letter-spacing: 0.12em; margin: 0 0 0.2em;
}
aside.notes { display: none; }
html.show-notes aside.notes {
  display: block; position: fixed; inset: 0 0 auto 0; z-index: 6;
  background: rgba(10, 10, 13, 0.93); color: var(--muted);
  padding: 1rem 1.4rem; font-size: 0.95rem; border-bottom: 1px solid #33333c;
}
div.progress { position: fixed; bottom: 0.55rem; right: 0.9rem; z-index: 3; }
div.progress a {
  color: var(--muted); font-size: 0.8rem; text-decoration: none;
  text-shadow: 0 0 8px var(--halo);
}
div.progress a:hover { color: var(--fg); }
nav.vert a {
  position: fixed; left: 50%; transform: translateX(-50%); z-index: 3;
  padding: 0.4rem 0.9rem; text-decoration: none; font-size: var(--arrow-size);
  color: var(--fg); opacity: 0.7;
  text-shadow: 0 0 8px var(--halo), 0 0 2px var(--halo);
}
nav.vert a:hover, nav.vert a:focus-visible { opacity: 1; }
nav.vert a.up { top: 0.15rem; }
nav.vert a.down { bottom: 0.15rem; }

/* ---- touch traversal (0.50.0, PRES-14): coarse pointers only.
   The viewer injects advance/back buttons carrying the keyboard's
   exact reveal-or semantics; CSS alone decides visibility, so the
   gate stays a capability query — fine pointers never see them.
   Under JS the vert arrows are superseded by the injected pair in
   the same positions (one control per position, never two stacked);
   without JS the vert arrows remain and grow to tap size. */
button.touch-nav { display: none; }
@media (pointer: coarse) {
  /* every fixed nav control reaches a 48x48px hit area by padding —
     the glyph itself does not grow (GSC/HIG tap-target guidance) */
  nav.seq a, nav.vert a, nav.slides-nav button, button.touch-nav {
    min-width: 48px; min-height: 48px; padding: 0;
    display: grid; place-items: center; box-sizing: border-box;
    /* fast repeated taps must never register as double-tap zoom
       (Andrew's iPhone, 2026-08-18); pinch zoom stays available */
    touch-action: manipulation;
  }
  main.deck nav.home a { touch-action: manipulation; }
  html.js main.deck nav.vert a { display: none; } /* superseded */
  button.touch-nav {
    position: fixed; left: 50%; transform: translateX(-50%); z-index: 3;
    background: none; border: 0; cursor: pointer;
    font-size: var(--arrow-size); color: var(--fg); opacity: 0.7;
    text-shadow: 0 0 8px var(--halo), 0 0 2px var(--halo);
  }
  button.touch-nav:active { opacity: 1; }
  button.touch-nav.back { top: 0.15rem; }
  /* the back control owns top-centre: the branding pill yields,
     truncating long deck titles with an ellipsis (the full title
     lives on the overview) — chrome yields to controls; slide TEXT
     yields to nothing (the insets below) */
  /* 16px clear gap pill→control: the pill is a LINK — a mis-tap
     near the back control must not navigate home (his note).
     The ellipsis lives ON the anchor (its own box, explicit
     max-width): Safari does not truncate reliably through a
     nav-constrained shrink-to-fit chain (his iPhone, 2026-08-18) */
  main.deck nav.home a {
    max-width: calc(50vw - 40px - 1.25rem); box-sizing: border-box;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  /* advance sits clear of the bottom-edge 2D indicator bar —
     --pos-size is scoped to div.pos, so clear its spec maximum
     (:deck-indicator size caps at 10px) deterministically */
  button.touch-nav.advance { bottom: calc(10px + 0.3rem); }
  html.show-ov button.touch-nav { display: none; } /* popup overview open */
  /* occlusion rule: the slide content column reserves the control
     zones — top/bottom strips and the side arrow columns (the side
     reservation matters most in landscape) — so controls can never
     cover slide text in any orientation */
  main.deck .slide-content {
    padding: max(8vh, 3.5rem) max(8vw, 3.5rem)
             max(8vh, calc(3.5rem + 10px));
  }
}
/* fragments: stepwise reveal, JS only; full content without JS/print */
html.js main.deck[data-fragments] .slide-content li {
  opacity: 0.15; transition: opacity 0.25s;
}
html.js main.deck[data-fragments] .slide-content li.revealed { opacity: 1; }
/* overview: spatial map whose axes MATCH the keys — sections as
   columns (←/→), slides stacked within a column (↑/↓). Shared by the
   browse/ page and the in-slide popup. */
div.ov-map {
  display: flex; gap: 1.1rem; align-items: flex-start;
  /* horizontal padding ≥ outline width + offset (2px + 3px): the
     scroll box clips at its content edge, which was cutting the
     selection outline off the leftmost column (Andrew's screenshot) */
  overflow-x: auto; padding: 0.5rem 6px 1rem;
  /* keep the outline visible when the viewer scrolls a tile to the
     edge (scrollIntoView would otherwise consume the padding) */
  scroll-padding: 8px;
}
div.ov-map section.ov {
  flex: 0 0 clamp(9rem, 20vw, 13rem);
  /* flex items floor at min-content by default (min-width: auto) — a
     long nowrap label was widening its whole column past the basis,
     so the ellipsis never engaged (Andrew's catch, F-002) */
  min-width: 0; width: clamp(9rem, 20vw, 13rem);
}
div.ov-map section.ov h2 {
  font-size: 0.95rem; color: var(--muted); margin: 0 0 0.5rem;
}
div.ov-map ul.col {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 0.6rem;
}
div.ov-map ul.col li {
  background: var(--surface); border-radius: 6px; overflow: hidden;
  border: 1px solid var(--hairline);
}
div.ov-map ul.col a { display: block; text-decoration: none; }
/* every tile the same size: image thumb or placeholder glyph */
div.ov-map ul.col img, div.ov-map ul.col .ph {
  width: 100%; aspect-ratio: 16 / 9; object-fit: cover; display: block;
}
div.ov-map ul.col .ph {
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-2); color: var(--muted); font-size: 1.9rem;
}
div.ov-map ul.col .label {
  display: block; padding: 0.45rem 0.6rem;
  color: var(--fg); font-size: 0.85rem;
  /* one line, clipped with an ellipsis — tiles stay aligned; the
     full title is the anchor's :title hover pop-over */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
div.ov-map ul.col a:hover li, div.ov-map ul.col a:hover .label { color: var(--accent); }
/* CURRENT slide (arrived-from: the URL fragment on the browse page,
   or .current in the fetched popup, where no fragment exists): solid
   accent. SELECTED (the arrow-key cursor): dashed fg. A bg-coloured
   ring sits under each outline so both read on any ground — the
   popup backdrop included (Andrew's review, 2026-08-16) */
div.ov-map li:target, div.ov-map li.current {
  outline: 2px solid var(--accent); outline-offset: 3px;
  box-shadow: 0 0 0 5px var(--bg);
}
div.ov-map li.selected {
  outline: 2px dashed var(--fg); outline-offset: 3px;
  box-shadow: 0 0 0 5px var(--bg);
}
/* 2D spatial indicator: bottom edge = sections (←/→), right edge =
   slides within the current section (↑/↓). Subtle but clear. */
div.pos { pointer-events: none; --pos-size: 3px; }
div.pos-h {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 4;
  display: flex; gap: 4px; height: var(--pos-size);
}
div.pos-v {
  position: fixed; top: 0; bottom: var(--pos-size); right: 0; z-index: 4;
  display: flex; flex-direction: column; gap: 4px; width: var(--pos-size);
}
/* palette-aware bars: fg-mixed so light palettes read dark marks on
   light grounds (Andrew, 2026-08-16: white-on-white lost them) */
div.pos-h span, div.pos-v span {
  flex: 1; background: color-mix(in srgb, var(--fg) 18%, transparent);
}
div.pos-h span.on, div.pos-v span.on { background: var(--accent); }
/* :contrast :strong — heavier fg bars with a bg ring, legible over
   any slide ground, photographs and solid colours alike */
div.pos.strong span {
  background: color-mix(in srgb, var(--fg) 42%, transparent);
  box-shadow: 0 0 0 1px var(--bg);
}
div.pos.strong span.on { background: var(--fg); box-shadow: 0 0 0 1px var(--bg); }

/* PRES-17 (evaluation): body-prose justification. Scoped to direct
   paragraphs of .text blocks — lists, verse blockquotes and headings
   stay ragged by design. The CSS is the no-JS/native fallback; the
   vendored justif module upgrades exactly these paragraphs. */
html[data-justify] .text > p, html[data-justify] dl.faq dd { text-align: justify; }
html[data-justify="native"] .text > p, html[data-justify="native"] dl.faq dd { hyphens: auto; }

/* handout: the whole deck as one printable document (PRES-14) */
main.handout { padding: 1.25rem; max-width: 46rem; margin: 0 auto; }
main.handout nav.home a {
  position: static; padding: 0; text-shadow: none; color: var(--muted);
}
main.handout h2 { color: var(--muted); font-size: 1.1rem; margin: 2.2rem 0 0.4rem; }
main.handout article.handout-slide {
  border-top: 1px solid #26262e; padding: 1rem 0 0.5rem; margin-top: 1rem;
}
main.handout h3 { margin: 0 0 0.5rem; }
main.handout figure { margin: 1rem 0; }
main.handout img {
  width: auto; height: auto; max-width: 100%; max-height: 40svh;
  border-radius: 6px;
}
main.handout aside.notes {
  display: block; position: static; inset: auto; background: none;
  border: none; border-left: 3px solid var(--accent);
  padding: 0.25rem 0.9rem; margin-top: 0.8rem;
  color: var(--muted); font-size: 0.95rem;
}
main.handout aside.notes::before { content: "Notes: "; font-weight: 600; }
p.tools a { color: var(--accent); font-size: 0.9rem; }
@media print {
  /* one ⌘P → one page per slide (Andrew's ruling): the h1 is the
     cover page; each section opens a fresh page with its heading on
     its first slide's page; every further slide breaks to a new page */
  main.handout section.handout-section { break-before: page; }
  main.handout article.handout-slide + article.handout-slide { break-before: page; }
  main.handout article.handout-slide { break-inside: avoid; border-top: none; }
  main.handout h2 { break-after: avoid; }
  main.handout aside.notes { color: #333; border-left-color: #999; }
}

/* popup overview on slide pages (JS-only; browse/ stays the no-JS path) */
div.ov-popup {
  display: none; position: fixed; inset: 0; z-index: 7;
  /* legibility over any slide: near-opaque scrim + blur separation,
     congruent with the info card's backdrop treatment */
  background: rgba(10, 10, 13, 0.96);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  overflow: auto; padding: 1.75rem;
}
html.show-ov div.ov-popup { display: block; }
div.ov-popup p.tools { margin: 0 0 1rem; }
html.show-ov { overflow: hidden; }
@media print {
  /* the background is stage dressing, not handout content — printing
     it produced a page of image before the slide (Andrew's report).
     :background {:print true} opts a slide's backdrop back in, flowed
     as a contained image above the content, never its own page. */
  main.deck figure.bg { display: none; }
  main.deck figure.bg.print-keep { display: block; position: static; height: auto; }
  main.deck figure.bg.print-keep figure { height: auto; }
  main.deck figure.bg.print-keep img {
    width: auto; height: auto; max-width: 100%; max-height: 9cm;
    object-fit: contain;
  }
  main.deck figure.bg.print-keep::after { content: none; }
  main.deck .slide-content { min-height: 0; text-shadow: none; padding: 0.5rem 0; }
  aside.notes { display: block; border-top: 1px solid #999; color: #333;
                margin-top: 0.8rem; padding-top: 0.4rem; }
  aside.notes::before { content: "Notes: "; font-weight: 600; }
  div.progress, nav.vert, div.pos { display: none; }
  html.js main.deck[data-fragments] .slide-content li { opacity: 1; }
}

/* CON-20 sponsors strip (Q-S1..4, his rulings 2026-09-02): logos on
   PAGES only — event page, scheme page, and the site-level grid and
   timeline pages; listing rows stay text-first. One height-normalised
   hairline-separated row, NO background fills (his no-pill ruling);
   a sponsor without a logo appears as its linked name — the strip
   never fakes an image and never reserves an empty slot. */
nav.sponsors { margin: 2rem 0 0.5rem; }
nav.sponsors h2.sponsors-title {
  margin: 0 0 0.5rem; font-size: 0.8rem; color: var(--muted);
  letter-spacing: 0.04em; text-transform: uppercase; font-weight: 600;
}
ul.sponsors-strip {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; align-items: center; row-gap: 0.6rem;
}
ul.sponsors-strip li {
  display: flex; align-items: center; padding: 0 0.9rem;
  border-left: 1px solid var(--hairline);
}
ul.sponsors-strip li:first-child { border-left: none; padding-left: 0; }
ul.sponsors-strip img { height: 2rem; width: auto; display: block; }
ul.sponsors-strip a { text-decoration: none; }
ul.sponsors-strip .sponsor-name { font-size: 0.95rem; }

/* PRES-18 palette overrides (portfolio :palette) */
:root { --accent: #1f6f8b; --bg: #f5efd8; --card-backdrop: rgba(250, 246, 233, 0.88); --card-edge: rgba(0, 0, 0, 0.14); --fg: #2b2416; --hairline: rgba(0, 0, 0, 0.10); --halo: rgba(255, 255, 255, 0.92); --muted: #6b5f45; --sign: #f2c230; --sign-fg: #2b2416; --summary-size: 1.7rem; --surface: #faf6e9; --surface-2: #efe6c9; }
