/* =============================================================================
   CREATOR DESIGN SYSTEM
   Frame & Signal — a token-first design system for creators building their site.

   This is the whole system. Import it and you get tokens, elements,
   components, sections and utilities. To take less, import a layer directly:

       @import "creator-design-system/foundation";   tokens only
       @import "creator-design-system/components";   + the components

   Broadcast (layer 4) is NOT included here: it ships to YouTube and Instagram,
   not to a website. Import it explicitly if you export channel art:

       @import "creator-design-system/broadcast";

   Docs: https://swarnil.github.io/Creator-Design-System/
   ========================================================================== */

/* =============================================================================
   SWARNIL DESIGN SYSTEM · 1 — FOUNDATION
   Single entry point. Import order matters: tokens are defined before anything
   consumes them, and the reset runs first so nothing has to un-do a default.

   Consumed two ways:
     1. the /design/ preview pages link this file directly (raw foundation)
     2. later, assets/css/tailwind.css will @import it above @tailwind base

   Nothing in this layer is theme-specific markup. No components live here —
   these are the primitives everything else is built from.
   ========================================================================== */

/* =============================================================================
   00 · RESET
   A small, opinionated reset. Tuned for Ghost: content coming out of the
   editor must look right without a wrapper class doing the work.
   ========================================================================== */

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

* {
	margin: 0;
}

html {
	-moz-text-size-adjust: none;
	-webkit-text-size-adjust: none;
	text-size-adjust: none;
	scroll-behavior: smooth;
	/* Anchors land below the sticky nav rather than under it. */
	scroll-padding-top: calc(var(--nav-h, 4rem) + var(--space-6, 1.5rem));
	tab-size: 4;
}

body {
	min-height: 100svh;
	background: var(--bg-canvas);
	color: var(--fg-default);
	overflow-wrap: break-word;
	/* A page must never scroll sideways. Wide things scroll inside themselves. */
	overflow-x: clip;
}

/* Media defaults: block-level, never overflowing, never distorted. */
img,
picture,
video,
canvas,
svg,
iframe {
	display: block;
	max-width: 100%;
}

img,
video {
	height: auto;
}

/* Forms inherit type instead of falling back to the browser's 13px Arial. */
input,
button,
textarea,
select {
	font: inherit;
	color: inherit;
	letter-spacing: inherit;
}

button {
	background: none;
	border: 0;
	padding: 0;
	cursor: pointer;
}

textarea {
	resize: vertical;
	min-height: 6rem;
}

a {
	color: inherit;
	text-decoration-thickness: max(1px, 0.06em);
	text-underline-offset: 0.16em;
}

ul[role='list'],
ol[role='list'] {
	list-style: none;
	padding: 0;
}

table {
	border-collapse: collapse;
	width: 100%;
}

/* Long strings (URLs, code) break instead of blowing out the layout. */
p, li, dd, dt, figcaption, blockquote, h1, h2, h3, h4, h5, h6 {
	overflow-wrap: break-word;
}

pre {
	overflow-x: auto;
	max-width: 100%;
}

hr {
	border: 0;
}

/* Ghost editor card widths — required by gscan, kept in the foundation so the
   reading column and the cards share one source of truth. */
.kg-width-wide {
	max-width: 85vw;
}

.kg-width-full {
	max-width: 100vw;
}

/* `hidden` means hidden. The browser's own rule for it is a UA-sheet rule, so
   any component that sets a display — every .btn, every .card — silently wins
   against it and the attribute stops working. This is the one place `!important`
   is right: the attribute is an assertion about the document, not a style. */
[hidden] { display: none !important; }

/* Native details marker replaced by our own chevron in components. */
summary {
	cursor: pointer;
	list-style: none;
}

summary::-webkit-details-marker {
	display: none;
}

::selection {
	background: var(--selection-bg);
	color: var(--selection-fg);
}

/* Scrollbars: thin, ink-coloured, theme-aware. */
* {
	scrollbar-width: thin;
	scrollbar-color: var(--line-strong) transparent;
}

::-webkit-scrollbar {
	width: 10px;
	height: 10px;
}

::-webkit-scrollbar-track {
	background: transparent;
}

::-webkit-scrollbar-thumb {
	background: var(--line-strong);
	border: 3px solid transparent;
	background-clip: content-box;
	border-radius: var(--radius-pill);
}
/* =============================================================================
   01 · COLOUR
   Tier 1 primitives (ramps) → Tier 2 semantic aliases (the only public names).
   Components read aliases only. Never a ramp step, never a raw hex.
   ========================================================================== */

:root {
	/* ── Tier 1 · INK ──────────────────────────────────────────────────────────
	   A near-neutral grey with a whisper of blue in the shadows, so dark
	   surfaces read as "unlit" rather than "muddy". 14 steps: the extra steps
	   at each end exist for hairlines and for near-black video surfaces. */
	--ink-0:    #fff;
	--ink-25:   #fcfcfd;
	--ink-50:   #f8f8fa;
	--ink-100:  #f1f1f4;
	--ink-200:  #e5e5ea;
	--ink-300:  #d3d3db;
	--ink-400:  #a5a5b2;
	--ink-500:  #76768a;
	--ink-600:  #55556a;
	--ink-700:  #3c3c4e;
	--ink-800:  #272734;
	--ink-900:  #191922;
	--ink-950:  #101017;
	--ink-1000: #08080c;

	/* ── Tier 1 · SIGNAL ───────────────────────────────────────────────────────
	   Vermilion, not fire-engine red — it holds up against black at full
	   saturation and stays legible as small text on white at 600+. This is the
	   record light. It is the only chromatic voice with authority. */
	--signal-50:  #fff2ef;
	--signal-100: #ffe1db;
	--signal-200: #ffc4b8;
	--signal-300: #ff9d89;
	--signal-400: #fb7358;
	--signal-500: #f04e2e;
	--signal-600: #dc3514;
	--signal-700: #b52810;
	--signal-800: #8f2211;
	--signal-900: #6f1f12;
	--signal-950: #3d0d07;

	/* ── Tier 1 · AMBER ────────────────────────────────────────────────────────
	   The rationed one. Craft, warmth, "hand-made" — construction lines,
	   viewfinder brackets, a single highlighted word. One per page, maximum. */
	--amber-50:  #fdf8ed;
	--amber-100: #f9ecce;
	--amber-200: #f2d795;
	--amber-300: #e8bd5c;
	--amber-400: #d9a33a;
	--amber-500: #c1872a;
	--amber-600: #9d6a22;
	--amber-700: #78501f;
	--amber-800: #55391b;
	--amber-900: #382512;

	/* ── Tier 1 · STATUS HUES ──────────────────────────────────────────────────
	   Deliberately distinct from signal: signal means "live", danger means
	   "destructive". They must never be confusable at a glance. */
	--mint-50:   #eafaf1;   /* success */
	--mint-100:  #cbf2de;
	--mint-500:  #16a06a;
	--mint-600:  #0f8156;
	--mint-700:  #0b6444;

	--azure-50:  #eaf3ff;   /* info */
	--azure-100: #d3e6ff;
	--azure-500: #2b7bef;
	--azure-600: #1a61cc;
	--azure-700: #164ea3;

	--rose-50:   #fdeef1;   /* danger */
	--rose-100:  #fbd7de;
	--rose-500:  #d92d4e;
	--rose-600:  #b81f3d;
	--rose-700:  #931832;

	/* Fixed, theme-independent. Used for scrims and video letterboxing. */
	--pure-black: #000;
	--pure-white: #fff;
}

/* =============================================================================
   Tier 2 · SEMANTIC ALIASES — LIGHT (the default)
   ========================================================================== */

:root,
:root[data-theme='light'] {
	color-scheme: light;

	/* Surfaces — canvas is the page, surface is a card, raised is a popover.
	   sunken is for wells (code blocks, inputs, table headers). */
	--bg-canvas:      var(--ink-0);
	--bg-surface:     var(--ink-0);
	--bg-raised:      var(--ink-0);
	--bg-sunken:      var(--ink-50);
	--bg-muted:       var(--ink-100);
	--bg-inverse:     var(--ink-950);
	--bg-scrim:       rgb(8 8 12 / 0.55);
	--bg-media:       var(--ink-900);   /* letterbox behind video/images */

	/* Foreground */
	--fg-default:     var(--ink-900);
	--fg-muted:       var(--ink-600);
	--fg-subtle:      var(--ink-500);
	--fg-faint:       var(--ink-400);
	--fg-on-inverse:  var(--ink-50);
	--fg-on-accent:   var(--pure-white);
	--fg-accent:      var(--signal-700);   /* accent text on light: 4.5:1+ */
	--fg-link:        var(--fg-default);
	--fg-link-hover:  var(--signal-700);

	/* Lines — subtle for internal dividers, default for card edges,
	   strong for anything that must survive a screenshot. */
	--line-subtle:    var(--ink-100);
	--line-default:   var(--ink-200);
	--line-strong:    var(--ink-300);
	--line-inverse:   rgb(255 255 255 / 0.14);
	--line-accent:    var(--signal-500);

	/* Accent — the record light */
	--accent:         var(--signal-500);
	--accent-hover:   var(--signal-600);
	--accent-press:   var(--signal-700);
	--accent-soft:    var(--signal-50);
	--accent-soft-fg: var(--signal-700);
	--accent-ring:    rgb(240 78 46 / 0.28);

	/* Amber — rationed craft accent */
	--craft:          var(--amber-400);
	--craft-soft:     var(--amber-50);
	--craft-fg:       var(--amber-700);

	/* Status: -bg is the tint, -fg is text on that tint, -line is the edge. */
	--success-bg:   var(--mint-50);
	--success-fg:   var(--mint-700);
	--success-line: var(--mint-500);
	--warning-bg:   var(--amber-50);
	--warning-fg:   var(--amber-700);
	--warning-line: var(--amber-400);
	--danger-bg:    var(--rose-50);
	--danger-fg:    var(--rose-700);
	--danger-line:  var(--rose-500);
	--info-bg:      var(--azure-50);
	--info-fg:      var(--azure-700);
	--info-line:    var(--azure-500);

	/* Interaction */
	--focus-ring:     var(--accent);
	--focus-ring-alt: var(--pure-white);   /* second halo, for dark backdrops */
	--selection-bg:   var(--signal-100);
	--selection-fg:   var(--ink-900);
	--hover-wash:     rgb(8 8 12 / 0.035);
	--press-wash:     rgb(8 8 12 / 0.07);

	/* Pattern ink — background textures derive from this so a pattern never
	   needs to know which theme it is on. */
	--pattern-ink:    rgb(8 8 12 / 0.06);
	--pattern-ink-2:  rgb(8 8 12 / 0.10);
}

/* =============================================================================
   Tier 2 · SEMANTIC ALIASES — DARK
   Not an inversion. Surfaces lift with light rather than drop shadows, the
   accent steps down one so it doesn't bloom, hairlines become translucent
   white so they survive over media.
   ========================================================================== */

:root[data-theme='dark'] {
	color-scheme: dark;

	--bg-canvas:      var(--ink-1000);
	--bg-surface:     var(--ink-950);
	--bg-raised:      var(--ink-900);
	--bg-sunken:      #0c0c12;
	--bg-muted:       var(--ink-800);
	--bg-inverse:     var(--ink-50);
	--bg-scrim:       rgb(0 0 0 / 0.68);
	--bg-media:       var(--pure-black);

	--fg-default:     var(--ink-50);
	--fg-muted:       var(--ink-400);
	--fg-subtle:      var(--ink-500);
	--fg-faint:       var(--ink-600);
	--fg-on-inverse:  var(--ink-900);
	--fg-on-accent:   var(--pure-white);
	--fg-accent:      var(--signal-400);
	--fg-link:        var(--fg-default);
	--fg-link-hover:  var(--signal-400);

	--line-subtle:    rgb(255 255 255 / 0.07);
	--line-default:   rgb(255 255 255 / 0.12);
	--line-strong:    rgb(255 255 255 / 0.22);
	--line-inverse:   rgb(8 8 12 / 0.14);
	--line-accent:    var(--signal-500);

	--accent:         var(--signal-500);
	--accent-hover:   var(--signal-400);
	--accent-press:   var(--signal-600);
	--accent-soft:    rgb(240 78 46 / 0.15);
	--accent-soft-fg: var(--signal-300);
	--accent-ring:    rgb(240 78 46 / 0.40);

	--craft:          var(--amber-300);
	--craft-soft:     rgb(217 163 58 / 0.14);
	--craft-fg:       var(--amber-200);

	--success-bg:   rgb(22 160 106 / 0.16);
	--success-fg:   #6ee7ad;
	--success-line: var(--mint-500);
	--warning-bg:   rgb(217 163 58 / 0.16);
	--warning-fg:   var(--amber-200);
	--warning-line: var(--amber-400);
	--danger-bg:    rgb(217 45 78 / 0.18);
	--danger-fg:    #ff8fa5;
	--danger-line:  var(--rose-500);
	--info-bg:      rgb(43 123 239 / 0.18);
	--info-fg:      #93c2ff;
	--info-line:    var(--azure-500);

	--focus-ring:     var(--signal-400);
	--focus-ring-alt: var(--ink-1000);
	--selection-bg:   rgb(240 78 46 / 0.35);
	--selection-fg:   var(--ink-0);
	--hover-wash:     rgb(255 255 255 / 0.05);
	--press-wash:     rgb(255 255 255 / 0.09);

	--pattern-ink:    rgb(255 255 255 / 0.07);
	--pattern-ink-2:  rgb(255 255 255 / 0.12);
}

/* No explicit choice → follow the OS. Written as a copy of the dark block's
   selector so `data-theme` always wins over the media query. */
@media (prefers-color-scheme: dark) {
	:root:not([data-theme='light']) {
		color-scheme: dark;

		--bg-canvas:      var(--ink-1000);
		--bg-surface:     var(--ink-950);
		--bg-raised:      var(--ink-900);
		--bg-sunken:      #0c0c12;
		--bg-muted:       var(--ink-800);
		--bg-inverse:     var(--ink-50);
		--bg-scrim:       rgb(0 0 0 / 0.68);
		--bg-media:       var(--pure-black);

		--fg-default:     var(--ink-50);
		--fg-muted:       var(--ink-400);
		--fg-subtle:      var(--ink-500);
		--fg-faint:       var(--ink-600);
		--fg-on-inverse:  var(--ink-900);
		--fg-accent:      var(--signal-400);
		--fg-link-hover:  var(--signal-400);

		--line-subtle:    rgb(255 255 255 / 0.07);
		--line-default:   rgb(255 255 255 / 0.12);
		--line-strong:    rgb(255 255 255 / 0.22);
		--line-inverse:   rgb(8 8 12 / 0.14);

		--accent-hover:   var(--signal-400);
		--accent-press:   var(--signal-600);
		--accent-soft:    rgb(240 78 46 / 0.15);
		--accent-soft-fg: var(--signal-300);
		--accent-ring:    rgb(240 78 46 / 0.40);

		--craft:          var(--amber-300);
		--craft-soft:     rgb(217 163 58 / 0.14);
		--craft-fg:       var(--amber-200);

		--success-bg:   rgb(22 160 106 / 0.16);
		--success-fg:   #6ee7ad;
		--warning-bg:   rgb(217 163 58 / 0.16);
		--warning-fg:   var(--amber-200);
		--danger-bg:    rgb(217 45 78 / 0.18);
		--danger-fg:    #ff8fa5;
		--info-bg:      rgb(43 123 239 / 0.18);
		--info-fg:      #93c2ff;

		--focus-ring:     var(--signal-400);
		--focus-ring-alt: var(--ink-1000);
		--selection-bg:   rgb(240 78 46 / 0.35);
		--selection-fg:   var(--ink-0);
		--hover-wash:     rgb(255 255 255 / 0.05);
		--press-wash:     rgb(255 255 255 / 0.09);

		--pattern-ink:    rgb(255 255 255 / 0.07);
		--pattern-ink-2:  rgb(255 255 255 / 0.12);
	}
}

/* =============================================================================
   COLOUR · THE MATHS
   Why the ramps look the way they do, expressed as tokens rather than prose,
   so the next colour anyone adds is built the same way.
   ========================================================================== */

:root {
	/* ── Lightness ladder ────────────────────────────────────────────────────
	   Every ramp follows the same perceived-lightness curve. The steps are
	   NOT linear: the eye separates light values much better than dark ones,
	   so the top of the ramp is finely sliced and the bottom is coarse.
	   These are the L* targets each step was tuned to. */
	--L-50: 97%;  --L-100: 94%; --L-200: 89%; --L-300: 81%;
	--L-400: 68%; --L-500: 56%; --L-600: 46%; --L-700: 37%;
	--L-800: 28%; --L-900: 19%; --L-950: 11%;

	/* ── Contrast targets (WCAG 2.2) ─────────────────────────────────────────
	   body text        ≥ 4.5:1     AA
	   large text ≥24px ≥ 3:1       AA
	   UI & focus rings ≥ 3:1       AA non-text
	   aiming for       ≥ 7:1       AAA on long-form prose
	   Verified pairs, both themes:
	     --fg-default on --bg-canvas   light 15.8:1   dark 16.4:1
	     --fg-muted   on --bg-canvas   light  7.4:1   dark  7.1:1
	     --fg-subtle  on --bg-canvas   light  4.9:1   dark  4.6:1
	     --fg-accent  on --bg-canvas   light  5.6:1   dark  5.1:1
	     --fg-on-accent on --accent    light  4.6:1   dark  4.6:1
	   --fg-faint is decorative only: it is BELOW 4.5:1 by design and must
	   never carry information. */
	--contrast-body: 4.5;
	--contrast-large: 3;
	--contrast-ui: 3;

	/* ── Hue relationships ───────────────────────────────────────────────────
	   The palette is a split-complementary built off the signal hue, which is
	   why it never fights itself:
	     signal   14°   the accent            (vermilion)
	     craft    42°   +28°  analogous       (amber)
	     info    216°   −158° complement-ish  (azure)
	     success 158°   +144°                 (mint)
	     danger  348°   −26°  近-signal, so it is deliberately DESATURATED
	                          and darker, or the two would be confusable.
	   Ink carries 258° at 4-8% saturation — a cold shadow under a warm accent,
	   which is what makes the greys look intentional rather than muddy. */
	--hue-signal: 14;
	--hue-craft: 42;
	--hue-info: 216;
	--hue-success: 158;
	--hue-danger: 348;
	--hue-ink: 258;

	/* ── Alpha ladder ────────────────────────────────────────────────────────
	   Overlays, washes, and hairlines all come from these five values. A
	   one-off alpha in a component is a bug. */
	--alpha-hairline: 0.07;
	--alpha-wash: 0.12;
	--alpha-veil: 0.24;
	--alpha-scrim: 0.55;
	--alpha-solid: 0.88;

	/* ── Elevation-by-light ──────────────────────────────────────────────────
	   In dark mode a raised surface gets LIGHTER rather than casting a shadow.
	   These are the exact overlay percentages, matching the surface tokens. */
	--lift-1: 4%;
	--lift-2: 7%;
	--lift-3: 11%;
	--lift-4: 16%;
}

/* Derived helpers. color-mix keeps the maths in CSS rather than in a build
   step, so a token change propagates everywhere immediately. */

.tint-accent-8  { background: color-mix(in srgb, var(--accent) 8%, var(--bg-surface)); }
.tint-accent-16 { background: color-mix(in srgb, var(--accent) 16%, var(--bg-surface)); }
.tint-craft-12  { background: color-mix(in srgb, var(--craft) 12%, var(--bg-surface)); }

/* A surface that lifts with light instead of shadow — the dark-mode idiom,
   usable in both themes. */
.lift-1 { background: color-mix(in srgb, var(--fg-default) var(--lift-1), var(--bg-surface)); }
.lift-2 { background: color-mix(in srgb, var(--fg-default) var(--lift-2), var(--bg-surface)); }
.lift-3 { background: color-mix(in srgb, var(--fg-default) var(--lift-3), var(--bg-surface)); }

/* An inverse island inside a light page (and vice versa): flip the aliases on
   a subtree without touching the document theme. Used by the CTA block, the
   series nav and the lower-third. */
[data-surface='inverse'] {
	--bg-canvas:    var(--ink-950);
	--bg-surface:   var(--ink-950);
	--bg-raised:    var(--ink-900);
	--bg-sunken:    var(--ink-1000);
	--bg-muted:     var(--ink-800);
	--fg-default:   var(--ink-50);
	--fg-muted:     var(--ink-400);
	--fg-subtle:    var(--ink-500);
	--line-subtle:  rgb(255 255 255 / 0.07);
	--line-default: rgb(255 255 255 / 0.12);
	--line-strong:  rgb(255 255 255 / 0.22);
	--fg-accent:    var(--signal-400);
	--accent-soft:  rgb(240 78 46 / 0.15);
	--hover-wash:   rgb(255 255 255 / 0.05);
	--pattern-ink:  rgb(255 255 255 / 0.07);
	--pattern-ink-2: rgb(255 255 255 / 0.12);

	background: var(--bg-surface);
	color: var(--fg-default);
	color-scheme: dark;
}       /* ramps → aliases → themes → the maths   */
/* =============================================================================
   02 · TYPOGRAPHY
   Three faces, three jobs. A fluid scale so no component ever needs a
   breakpoint-specific font size.
     display → Space Grotesk   headlines, numbers, anything ≥ 20px
     body    → Inter           everything you actually read
     slate   → IBM Plex Mono   metadata voice: labels, timecodes, code
   ========================================================================== */

:root {
	--font-display: 'Space Grotesk', 'Inter', ui-sans-serif, system-ui, sans-serif;
	--font-body: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
	--font-slate: 'IBM Plex Mono', ui-monospace, 'SF Mono', 'Cascadia Mono', monospace;

	/* Ghost Admin → Brand → fonts wins if the publisher sets one. */
	--font-heading-effective: var(--gh-font-heading, var(--font-display));
	--font-body-effective: var(--gh-font-body, var(--font-body));

	/* ── Fluid scale ───────────────────────────────────────────────────────────
	   clamp(min, preferred, max) — fluid between a 360px and a 1440px viewport.
	   Ratio ≈ 1.22 at the small end, opening to ≈ 1.30 at display sizes so
	   headlines get dramatic without the body text drifting. */
	--text-2xs:     0.6875rem;                              /* 11px · slate only */
	--text-xs:      0.75rem;                                /* 12px */
	--text-sm:      0.8125rem;                              /* 13px */
	--text-base:    1rem;                                   /* 16px */
	--text-md:      1.0625rem;                              /* 17px · long-form body */
	--text-lg:      clamp(1.125rem, 1.05rem + 0.32vw, 1.3125rem);
	--text-xl:      clamp(1.3125rem, 1.19rem + 0.55vw, 1.625rem);
	--text-2xl:     clamp(1.5rem,  1.31rem + 0.83vw,  2rem);
	--text-3xl:     clamp(1.875rem, 1.55rem + 1.42vw, 2.75rem);
	--text-4xl:     clamp(2.25rem,  1.72rem + 2.36vw, 3.75rem);
	--text-5xl:     clamp(2.75rem,  1.86rem + 3.94vw, 5.25rem);
	--text-6xl:     clamp(3.25rem,  1.82rem + 6.30vw, 7rem);

	/* ── Line height ───────────────────────────────────────────────────────────
	   Display sits tight, body sits open. Nothing in between exists. */
	--leading-flat:    1;
	--leading-tight:   1.1;
	--leading-snug:    1.25;
	--leading-normal:  1.5;
	--leading-relaxed: 1.65;   /* the reading default */
	--leading-loose:   1.85;

	/* ── Tracking ──────────────────────────────────────────────────────────────
	   Display tightens as it grows; the slate voice opens wide. */
	--tracking-tighter: -0.035em;
	--tracking-tight:   -0.02em;
	--tracking-normal:  0;
	--tracking-wide:    0.04em;
	--tracking-slate:   0.14em;   /* mono uppercase labels */

	/* ── Weight ────────────────────────────────────────────────────────────────
	   Four only. 500 does not exist in this system — it muddies 400 vs 600. */
	--weight-regular:  400;
	--weight-medium:   500;
	--weight-semibold: 600;
	--weight-bold:     700;

	/* ── Measure ───────────────────────────────────────────────────────────────
	   Long-form prose caps at 68ch; UI copy at 46ch so cards break predictably. */
	--measure-prose: 68ch;
	--measure-ui:    46ch;
	--measure-lead:  56ch;
}

/* Base element defaults ---------------------------------------------------- */

html {
	font-size: 100%;
	-webkit-text-size-adjust: 100%;
}

body {
	font-family: var(--font-body-effective);
	font-size: var(--text-base);
	line-height: var(--leading-relaxed);
	font-weight: var(--weight-regular);
	font-feature-settings: 'kern' 1, 'liga' 1, 'calt' 1;
	text-rendering: optimizeLegibility;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4, h5, h6 {
	font-family: var(--font-heading-effective);
	font-weight: var(--weight-bold);
	line-height: var(--leading-tight);
	letter-spacing: var(--tracking-tight);
	text-wrap: balance;
}

/* ── Text roles ────────────────────────────────────────────────────────────
   Semantic classes, not size utilities. Use the role that describes the job;
   the size follows. This is what keeps a heading a heading when the scale
   later changes. */

.t-display-1 {
	font-family: var(--font-display);
	font-size: var(--text-6xl);
	font-weight: var(--weight-bold);
	line-height: var(--leading-flat);
	letter-spacing: var(--tracking-tighter);
	text-wrap: balance;
}

.t-display-2 {
	font-family: var(--font-display);
	font-size: var(--text-5xl);
	font-weight: var(--weight-bold);
	line-height: 1.04;
	letter-spacing: var(--tracking-tighter);
	text-wrap: balance;
}

.t-h1 {
	font-family: var(--font-heading-effective);
	font-size: var(--text-4xl);
	font-weight: var(--weight-bold);
	line-height: var(--leading-tight);
	letter-spacing: var(--tracking-tight);
	text-wrap: balance;
}

.t-h2 {
	font-family: var(--font-heading-effective);
	font-size: var(--text-3xl);
	font-weight: var(--weight-bold);
	line-height: var(--leading-tight);
	letter-spacing: var(--tracking-tight);
	text-wrap: balance;
}

.t-h3 {
	font-family: var(--font-heading-effective);
	font-size: var(--text-2xl);
	font-weight: var(--weight-semibold);
	line-height: var(--leading-snug);
	letter-spacing: var(--tracking-tight);
}

.t-h4 {
	font-family: var(--font-heading-effective);
	font-size: var(--text-xl);
	font-weight: var(--weight-semibold);
	line-height: var(--leading-snug);
}

.t-lead {
	font-size: var(--text-lg);
	line-height: var(--leading-relaxed);
	color: var(--fg-muted);
	max-width: var(--measure-lead);
	text-wrap: pretty;
}

.t-body {
	font-size: var(--text-md);
	line-height: var(--leading-relaxed);
	max-width: var(--measure-prose);
	text-wrap: pretty;
}

.t-small {
	font-size: var(--text-sm);
	line-height: var(--leading-normal);
	color: var(--fg-muted);
}

/* The slate voice — every piece of metadata on the site speaks like this. */
.t-slate {
	font-family: var(--font-slate);
	font-size: var(--text-xs);
	font-weight: var(--weight-medium);
	line-height: var(--leading-normal);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-subtle);
}

.t-slate-sm {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-subtle);
}

/* Timecodes, runtimes, counts — tabular so columns of numbers line up. */
.t-numeric {
	font-family: var(--font-slate);
	font-variant-numeric: tabular-nums;
	letter-spacing: var(--tracking-wide);
}

/* Big display numbers (stat grids). Lining + tabular, tight as possible. */
.t-stat {
	font-family: var(--font-display);
	font-size: var(--text-4xl);
	font-weight: var(--weight-bold);
	/* 1.06, not 1 — display digits have enough overshoot to clip at flat leading. */
	line-height: 1.06;
	letter-spacing: var(--tracking-tighter);
	font-variant-numeric: tabular-nums lining-nums;
}

.t-quote {
	font-family: var(--font-display);
	font-size: var(--text-xl);
	font-weight: var(--weight-medium);
	line-height: var(--leading-snug);
	letter-spacing: var(--tracking-tight);
	border-left: var(--border-2, 2px) solid var(--accent);
	padding-left: var(--space-4, 1rem);
	text-wrap: balance;
}

.t-code {
	font-family: var(--font-slate);
	font-size: 0.875em;
	background: var(--bg-sunken);
	border: 1px solid var(--line-subtle);
	border-radius: var(--radius-sm, 4px);
	padding: 0.15em 0.4em;
	white-space: nowrap;
}

/* Colour + emphasis helpers (aliases only) */
.t-muted  { color: var(--fg-muted); }
.t-subtle { color: var(--fg-subtle); }
.t-accent { color: var(--fg-accent); }

/* The house link: underline grows from the left on hover, never a colour
   flash. Colour change is the fallback under reduced-motion. */
.t-link {
	color: var(--fg-link);
	text-decoration: none;
	background-image: linear-gradient(var(--accent), var(--accent));
	background-repeat: no-repeat;
	background-position: 0 100%;
	background-size: 0% var(--border-2, 2px);
	padding-bottom: 0.08em;
	transition: background-size var(--dur-2, 200ms) var(--ease-out, ease),
	            color var(--dur-2, 200ms) var(--ease-out, ease);
}

.t-link:hover,
.t-link:focus-visible {
	color: var(--fg-link-hover);
	background-size: 100% var(--border-2, 2px);
}

/* Constrain any block to a comfortable measure */
.measure       { max-width: var(--measure-prose); }
.measure-ui    { max-width: var(--measure-ui); }
.measure-lead  { max-width: var(--measure-lead); }

/* ── The typefaces ───────────────────────────────────────────────────────
   Three faces, three jobs, and the reasons — so the next person doesn't
   "improve" the stack.

   DISPLAY · Space Grotesk 400/500/600/700
     A grotesk with drawing quirks that only show at size: the flat-sided g,
     the angled terminals, the tight apertures. Engineered rather than
     friendly, which is the channel's tone. Numerals are lining and tabular,
     so a stat grid never shifts. Never used below 20px — at small sizes the
     quirks read as noise.

   BODY · Inter 400/500/600/700
     Designed for screens at small sizes: tall x-height, open apertures,
     unambiguous Il1 / O0. It is meant to be invisible. Ships with the
     `calt`/`liga`/`kern` features on and `ss01`-style alternates OFF — the
     alternates make it look like a different typeface at scale.

   SLATE · IBM Plex Mono 400/500/600
     The metadata voice: timecodes, labels, code, coordinates. Mono is the
     signal that a value is machine-produced. Never body copy, never a
     paragraph — a monospaced paragraph is a wall.

   FALLBACKS are ordered so the layout barely shifts if a webfont fails:
   system-ui / SF for the sans faces, SF Mono / Cascadia for the mono. If
   metric-compatible local fallbacks are ever added, do it with
   @font-face size-adjust, not by changing the stacks. */

.face-display { font-family: var(--font-display); }
.face-body    { font-family: var(--font-body); }
.face-slate   { font-family: var(--font-slate); }

/* Numeric styles — pick deliberately, they are not interchangeable. */
.num-tabular    { font-variant-numeric: tabular-nums; }     /* columns, timecodes */
.num-proportional { font-variant-numeric: proportional-nums; } /* inline in prose */
.num-lining     { font-variant-numeric: lining-nums; }      /* with caps */
.num-slashed    { font-variant-numeric: slashed-zero; }     /* IDs, codes */

/* Weight helpers. 500 exists for the slate voice only — in the sans faces it
   sits too close to 400 to read as a real change. */
.w-regular  { font-weight: var(--weight-regular); }
.w-medium   { font-weight: var(--weight-medium); }
.w-semibold { font-weight: var(--weight-semibold); }
.w-bold     { font-weight: var(--weight-bold); }

/* Optical corrections. Big display type needs negative tracking; small
   letterspaced caps need positive. These are the only two allowed. */
.optical-display { letter-spacing: var(--tracking-tighter); }
.optical-caps    { letter-spacing: var(--tracking-slate); text-transform: uppercase; }

/* Truncation — one job each, never invented ad hoc in components. */
.truncate {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.clamp-2,
.clamp-3 {
	display: -webkit-box;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

.clamp-2 { -webkit-line-clamp: 2; line-clamp: 2; }
.clamp-3 { -webkit-line-clamp: 3; line-clamp: 3; }  /* 3 faces, fluid scale, text roles       */
/* =============================================================================
   03 · SPACE, RADIUS, SIZE, Z
   4px base unit, 8px rhythm. The ladders are closed sets — if a value you need
   isn't here, the ladder is wrong; fix the ladder, don't invent a one-off.
   ========================================================================== */

:root {
	--unit: 0.25rem;   /* 4px */

	/* ── Spacing ladder ───────────────────────────────────────────────────────
	   Named by multiples of the base unit. 1–3 are for optical nudges inside
	   components; layout uses 4 and up. */
	--space-0:   0;
	--space-px:  1px;
	--space-1:   0.25rem;   /*  4 */
	--space-2:   0.5rem;    /*  8 */
	--space-3:   0.75rem;   /* 12 */
	--space-4:   1rem;      /* 16 */
	--space-5:   1.25rem;   /* 20 */
	--space-6:   1.5rem;    /* 24 */
	--space-8:   2rem;      /* 32 */
	--space-10:  2.5rem;    /* 40 */
	--space-12:  3rem;      /* 48 */
	--space-16:  4rem;      /* 64 */
	--space-20:  5rem;      /* 80 */
	--space-24:  6rem;      /* 96 */
	--space-32:  8rem;      /* 128 */
	--space-40:  10rem;     /* 160 */

	/* ── Section rhythm ───────────────────────────────────────────────────────
	   Vertical space between page sections, fluid so mobile doesn't waste a
	   screen and desktop still breathes. Three densities, nothing else. */
	--section-sm: clamp(2rem, 1.4rem + 2.6vw, 3.5rem);
	--section-md: clamp(3rem, 2.0rem + 4.4vw, 6rem);
	--section-lg: clamp(4.5rem, 2.8rem + 7.4vw, 9rem);

	/* ── Gutter ───────────────────────────────────────────────────────────────
	   The page's left/right breathing room. Everything that touches the page
	   edge derives from this — never a hardcoded padding. */
	--gutter: clamp(1rem, 0.55rem + 2vw, 2rem);

	/* ── Radius ladder ────────────────────────────────────────────────────────
	   Fixed set. A pill is `--radius-pill`, a circle is `--radius-full`. */
	--radius-none: 0;
	--radius-xs:   2px;
	--radius-sm:   4px;
	--radius-md:   8px;
	--radius-lg:   12px;
	--radius-xl:   16px;
	--radius-2xl:  24px;
	--radius-3xl:  32px;
	--radius-pill: 999px;
	--radius-full: 50%;

	/* Component-level radius intents, so a card's corner can be retuned in one
	   place without hunting through templates. */
	--radius-control: var(--radius-md);   /* buttons, inputs, chips */
	--radius-card:    var(--radius-xl);
	--radius-media:   var(--radius-lg);
	--radius-sheet:   var(--radius-2xl);  /* modals, big inverse blocks */

	/* ── Border widths ────────────────────────────────────────────────────────
	   hairline is the physical-pixel line; 2px is the active/underline weight. */
	--border-hair: 1px;
	--border-1:    1px;
	--border-2:    2px;
	--border-3:    3px;
	--border-4:    4px;

	/* ── Container widths ─────────────────────────────────────────────────────
	   `--w-site` is the single source of truth for horizontal rhythm; every
	   other width is a named crop of it. */
	--w-prose: 44rem;    /* 704px · long-form reading column */
	--w-narrow: 56rem;   /* 896px · focused pages, forms */
	--w-site: 71rem;     /* 1136px · the default content column */
	--w-wide: 82rem;     /* 1312px · galleries, 4-up grids */
	--w-full: 100%;

	/* ── Control sizes ────────────────────────────────────────────────────────
	   Heights shared by buttons, inputs and selects so they line up in a row.
	   md is ≥44px tall on touch targets by way of its padding. */
	--control-sm: 2rem;      /* 32 */
	--control-md: 2.5rem;    /* 40 */
	--control-lg: 3rem;      /* 48 */
	--tap-min:    44px;      /* minimum interactive target */

	/* ── Icon sizes ───────────────────────────────────────────────────────── */
	--icon-xs: 0.875rem;
	--icon-sm: 1rem;
	--icon-md: 1.25rem;
	--icon-lg: 1.5rem;
	--icon-xl: 2rem;

	/* The signal dot — one physical size, used everywhere it appears. */
	--dot-sm: 6px;
	--dot-md: 8px;
	--dot-lg: 12px;

	/* ── Z-index ladder ───────────────────────────────────────────────────────
	   Closed set. Nothing in this codebase may use a bare z-index integer. */
	--z-below:    -1;
	--z-base:      0;
	--z-raised:    10;
	--z-sticky:    100;
	--z-nav:       200;
	--z-dropdown:  300;
	--z-overlay:   400;
	--z-modal:     500;
	--z-toast:     600;
	--z-tooltip:   700;
	--z-max:       9999;

	/* ── Aspect ratios ────────────────────────────────────────────────────────
	   Named because the system is "one system, every crop". */
	--ratio-wide:   16 / 9;
	--ratio-video:  16 / 9;
	--ratio-photo:  3 / 2;
	--ratio-square: 1 / 1;
	--ratio-short:  9 / 16;   /* vertical short-form */
	--ratio-poster: 2 / 3;
	--ratio-cinema: 21 / 9;
	--ratio-golden: 1.618 / 1;
}       /* space · radius · size · z · ratios     */
/* =============================================================================
   04 · ELEVATION
   Depth comes from lines first, shadow second. Five levels, and a rule for
   which one a thing gets: if it doesn't leave the page, it doesn't get shadow.
   ========================================================================== */

:root {
	/* Shadows are tinted with the ink hue, never neutral black — a pure-black
	   shadow on a warm-neutral surface reads as dirt. */
	--shadow-color: 232 20% 8%;

	--shadow-0: none;

	/* 1 · resting card — a hairline plus the faintest lift */
	--shadow-1:
		0 1px 2px  hsl(var(--shadow-color) / 0.05),
		0 1px 1px  hsl(var(--shadow-color) / 0.03);

	/* 2 · hovered card */
	--shadow-2:
		0 2px 4px  hsl(var(--shadow-color) / 0.06),
		0 6px 12px hsl(var(--shadow-color) / 0.05);

	/* 3 · dropdown, popover */
	--shadow-3:
		0 4px 8px   hsl(var(--shadow-color) / 0.07),
		0 12px 24px hsl(var(--shadow-color) / 0.07);

	/* 4 · modal, command palette */
	--shadow-4:
		0 8px 16px  hsl(var(--shadow-color) / 0.08),
		0 24px 48px hsl(var(--shadow-color) / 0.10);

	/* 5 · the thing that has left the page entirely (drag, lightbox) */
	--shadow-5:
		0 16px 32px hsl(var(--shadow-color) / 0.10),
		0 40px 80px hsl(var(--shadow-color) / 0.14);

	/* Inset well — code blocks, inputs, table headers */
	--shadow-inset: inset 0 1px 2px hsl(var(--shadow-color) / 0.06);

	/* The accent glow. Reserved for the live/recording state. Never decorative. */
	--shadow-signal: 0 0 0 4px var(--accent-ring);

	/* Focus ring — two-tone so it survives on both light and dark backdrops. */
	--ring-focus:
		0 0 0 var(--border-2) var(--focus-ring-alt),
		0 0 0 4px var(--focus-ring);
}

/* Dark mode: shadows barely read on a near-black canvas, so depth switches to
   a lit top edge plus a deeper, larger shadow. */
:root[data-theme='dark'] {
	--shadow-color: 0 0% 0%;
	--shadow-1: 0 1px 0 rgb(255 255 255 / 0.04) inset,
	            0 1px 3px rgb(0 0 0 / 0.5);
	--shadow-2: 0 1px 0 rgb(255 255 255 / 0.05) inset,
	            0 6px 16px rgb(0 0 0 / 0.55);
	--shadow-3: 0 1px 0 rgb(255 255 255 / 0.06) inset,
	            0 12px 28px rgb(0 0 0 / 0.6);
	--shadow-4: 0 1px 0 rgb(255 255 255 / 0.07) inset,
	            0 24px 56px rgb(0 0 0 / 0.65);
	--shadow-5: 0 1px 0 rgb(255 255 255 / 0.08) inset,
	            0 40px 96px rgb(0 0 0 / 0.7);
	--shadow-inset: inset 0 1px 2px rgb(0 0 0 / 0.5);
}

@media (prefers-color-scheme: dark) {
	:root:not([data-theme='light']) {
		--shadow-color: 0 0% 0%;
		--shadow-1: 0 1px 0 rgb(255 255 255 / 0.04) inset,
		            0 1px 3px rgb(0 0 0 / 0.5);
		--shadow-2: 0 1px 0 rgb(255 255 255 / 0.05) inset,
		            0 6px 16px rgb(0 0 0 / 0.55);
		--shadow-3: 0 1px 0 rgb(255 255 255 / 0.06) inset,
		            0 12px 28px rgb(0 0 0 / 0.6);
		--shadow-4: 0 1px 0 rgb(255 255 255 / 0.07) inset,
		            0 24px 56px rgb(0 0 0 / 0.65);
		--shadow-5: 0 1px 0 rgb(255 255 255 / 0.08) inset,
		            0 40px 96px rgb(0 0 0 / 0.7);
		--shadow-inset: inset 0 1px 2px rgb(0 0 0 / 0.5);
	}
}

/* ── Surface recipes ───────────────────────────────────────────────────────
   The three ways a rectangle can exist in this system. Everything else is a
   variation of one of them. */

.surface {
	background: var(--bg-surface);
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-card);
	box-shadow: var(--shadow-0);
}

.surface-raised {
	background: var(--bg-raised);
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-card);
	box-shadow: var(--shadow-3);
}

.surface-sunken {
	background: var(--bg-sunken);
	border: var(--border-hair) solid var(--line-subtle);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-inset);
}

/* The signal edge: a 2px accent rule welded to one side. This is how "live",
   "featured" and "current" are marked — never a fill, never a glow. */
.edge-signal { box-shadow: inset var(--border-3) 0 0 0 var(--accent); }
.edge-signal-top { box-shadow: inset 0 var(--border-3) 0 0 var(--accent); }
.edge-signal-bottom { box-shadow: inset 0 calc(var(--border-3) * -1) 0 0 var(--accent); }

/* Elevation utilities */
.elev-0 { box-shadow: var(--shadow-0); }
.elev-1 { box-shadow: var(--shadow-1); }
.elev-2 { box-shadow: var(--shadow-2); }
.elev-3 { box-shadow: var(--shadow-3); }
.elev-4 { box-shadow: var(--shadow-4); }
.elev-5 { box-shadow: var(--shadow-5); }   /* shadows, rings, surface recipes        */
/* =============================================================================
   05 · MOTION
   Four durations. Four easings. Every animation in the theme is built from
   this ladder — no component may invent a duration.

     dur-1 120ms  state flips: hover, press, focus, checkbox
     dur-2 200ms  the default: colour, opacity, small transforms
     dur-3 320ms  things that move a real distance: entrances, drawers
     dur-4 560ms  the deliberate one: page/scene transitions, hero reveals
   ========================================================================== */

:root {
	--dur-1: 120ms;
	--dur-2: 200ms;
	--dur-3: 320ms;
	--dur-4: 560ms;

	/* Easings.
	   out    — things arriving (fast, then settle) · the default
	   in     — things leaving
	   inout  — things moving between two on-screen states
	   spring — one overshoot, used sparingly for playful confirmations */
	--ease-out:    cubic-bezier(0.22, 1, 0.36, 1);
	--ease-in:     cubic-bezier(0.55, 0, 1, 0.45);
	--ease-inout:  cubic-bezier(0.65, 0, 0.35, 1);
	--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
	--ease-linear: linear;

	/* Stagger step for lists revealing on scroll. Multiply by the index. */
	--stagger: 60ms;

	/* Named transitions, so components stop re-declaring them. */
	--t-colors: color var(--dur-2) var(--ease-out),
	            background-color var(--dur-2) var(--ease-out),
	            border-color var(--dur-2) var(--ease-out);
	--t-transform: transform var(--dur-2) var(--ease-out);
	--t-shadow: box-shadow var(--dur-2) var(--ease-out);
	--t-all: var(--t-colors), var(--t-transform), var(--t-shadow),
	         opacity var(--dur-2) var(--ease-out);
}

/* ── Keyframes ─────────────────────────────────────────────────────────────
   Entrances travel a short distance — 16px, not 60. Long travel reads as
   "loading", which is a lie when the content is already there. */

@keyframes fx-rise {
	from { opacity: 0; transform: translate3d(0, 16px, 0); }
	to   { opacity: 1; transform: none; }
}

@keyframes fx-fade {
	from { opacity: 0; }
	to   { opacity: 1; }
}

@keyframes fx-slide-l {
	from { opacity: 0; transform: translate3d(-24px, 0, 0); }
	to   { opacity: 1; transform: none; }
}

@keyframes fx-slide-r {
	from { opacity: 0; transform: translate3d(24px, 0, 0); }
	to   { opacity: 1; transform: none; }
}

@keyframes fx-scale {
	from { opacity: 0; transform: scale3d(0.96, 0.96, 1); }
	to   { opacity: 1; transform: none; }
}

@keyframes fx-blur {
	from { opacity: 0; filter: blur(10px); }
	to   { opacity: 1; filter: blur(0); }
}

/* Scene wipe — the shutter opening. Used for media reveals. */
@keyframes fx-clip {
	from { clip-path: inset(0 0 100% 0); }
	to   { clip-path: inset(0 0 0 0); }
}

/* The record light. The only looping animation allowed on a resting page. */
@keyframes fx-pulse {
	0%, 100% { opacity: 1;    transform: scale(1); }
	50%      { opacity: 0.45; transform: scale(0.86); }
}

/* Skeleton shimmer */
@keyframes fx-shimmer {
	from { background-position: -160% 0; }
	to   { background-position: 260% 0; }
}

/* Indeterminate progress rail */
@keyframes fx-rail {
	from { transform: translate3d(-100%, 0, 0); }
	to   { transform: translate3d(300%, 0, 0); }
}

@keyframes fx-spin {
	to { transform: rotate(360deg); }
}

/* Scanline sweep — media loading, and the video-flavoured page transition. */
@keyframes fx-scan {
	from { transform: translate3d(0, -110%, 0); }
	to   { transform: translate3d(0, 320%, 0); }
}

/* Buffering dots */
@keyframes fx-buffer {
	0%, 80%, 100% { opacity: 0.25; transform: translateY(0); }
	40%           { opacity: 1;    transform: translateY(-3px); }
}

/* ── Animation utilities ───────────────────────────────────────────────────
   `both` fill so an element never flashes at its start state. */

.fx-rise    { animation: fx-rise    var(--dur-3) var(--ease-out) both; }
.fx-fade    { animation: fx-fade    var(--dur-3) var(--ease-out) both; }
.fx-slide-l { animation: fx-slide-l var(--dur-3) var(--ease-out) both; }
.fx-slide-r { animation: fx-slide-r var(--dur-3) var(--ease-out) both; }
.fx-scale   { animation: fx-scale   var(--dur-3) var(--ease-out) both; }
.fx-blur    { animation: fx-blur    var(--dur-4) var(--ease-out) both; }
.fx-clip    { animation: fx-clip    var(--dur-4) var(--ease-inout) both; }
.fx-pulse   { animation: fx-pulse   1.6s var(--ease-inout) infinite; }
.fx-spin    { animation: fx-spin    var(--dur-4) var(--ease-linear) infinite; }

/* Stagger children of a revealed group: set --i on each child. */
.fx-stagger > * { animation-delay: calc(var(--i, 0) * var(--stagger)); }

/* ── Hover behaviours ──────────────────────────────────────────────────────
   Only applied where a real pointer exists — a touch device gets the resting
   state, never a stuck hover. */

@media (hover: hover) and (pointer: fine) {
	.fx-lift {
		transition: var(--t-transform), var(--t-shadow);
	}
	.fx-lift:hover {
		transform: translate3d(0, -3px, 0);
		box-shadow: var(--shadow-2);
	}

	.fx-glow {
		transition: var(--t-shadow), var(--t-colors);
	}
	.fx-glow:hover {
		border-color: var(--line-accent);
		box-shadow: var(--shadow-signal);
	}

	/* Tilt follows the pointer via --mx/--my set in JS; without JS it is a
	   flat, harmless scale. */
	.fx-tilt {
		transition: transform var(--dur-3) var(--ease-out);
		transform-style: preserve-3d;
	}
	.fx-tilt:hover {
		transform: perspective(800px)
		           rotateX(calc(var(--my, 0) * -4deg))
		           rotateY(calc(var(--mx, 0) * 4deg))
		           scale3d(1.01, 1.01, 1);
	}

	/* Arrow nudge on links/buttons that go somewhere. */
	.fx-shift > .fx-shift__icon {
		transition: var(--t-transform);
	}
	.fx-shift:hover > .fx-shift__icon {
		transform: translate3d(3px, 0, 0);
	}
}

/* ── Scroll-driven reveal ──────────────────────────────────────────────────
   Native where supported, so no IntersectionObserver is needed for the common
   case. JS may still add `.is-in` as a fallback. */

@supports (animation-timeline: view()) {
	@media (prefers-reduced-motion: no-preference) {
		.fx-on-scroll {
			animation: fx-rise var(--dur-4) var(--ease-out) both;
			animation-timeline: view();
			animation-range: entry 5% cover 26%;
		}
	}
}

/* =============================================================================
   MOTION · PART 2 — the full set
   Thirteen named behaviours, each with one job. If a new animation is needed,
   first check that none of these is the same idea with a different name.
   ========================================================================== */

/* 08 · flip — a card turning over. Reveals, before/after, answer cards. */
@keyframes fx-flip {
	from { opacity: 0; transform: perspective(900px) rotateX(-18deg) translate3d(0, 10px, 0); }
	to   { opacity: 1; transform: none; }
}

/* 09 · swing — enters with one small overshoot. Confirmations only. */
@keyframes fx-swing {
	0%   { opacity: 0; transform: translate3d(0, 14px, 0) rotate(-1.5deg); }
	70%  { opacity: 1; transform: translate3d(0, -3px, 0) rotate(0.4deg); }
	100% { opacity: 1; transform: none; }
}

/* 10 · pop — scale from 92% with a spring. Badges, toasts, likes. */
@keyframes fx-pop {
	from { opacity: 0; transform: scale3d(0.92, 0.92, 1); }
	to   { opacity: 1; transform: none; }
}

/* 11 · draw — a rule or underline growing from its origin. */
@keyframes fx-draw {
	from { transform: scaleX(0); }
	to   { transform: scaleX(1); }
}

/* 12 · mask-wipe — content revealed by a moving edge, no movement of its own.
   The most "editorial" reveal; use it for headlines, not for lists. */
@keyframes fx-mask {
	from { clip-path: inset(0 100% 0 0); }
	to   { clip-path: inset(0 0 0 0); }
}

/* 13 · zoom-out — settles from slightly too big. Hero images only. */
@keyframes fx-zoom {
	from { opacity: 0; transform: scale3d(1.06, 1.06, 1); }
	to   { opacity: 1; transform: none; }
}

.fx-flip  { animation: fx-flip  var(--dur-4) var(--ease-out) both; }
.fx-swing { animation: fx-swing var(--dur-4) var(--ease-out) both; }
.fx-pop   { animation: fx-pop   var(--dur-3) var(--ease-spring) both; }
.fx-draw  { animation: fx-draw  var(--dur-3) var(--ease-out) both; transform-origin: left center; }
.fx-mask  { animation: fx-mask  var(--dur-4) var(--ease-inout) both; }
.fx-zoom  { animation: fx-zoom  var(--dur-4) var(--ease-out) both; }

/* =============================================================================
   TEXT EFFECTS
   Twelve. Most need the text split into spans — one per character or per word,
   each carrying its index as --i:

     <span class="tx-chars"><span style="--i:0">S</span><span style="--i:1">w</span>…</span>

   Splitting is five lines of JS at runtime, or done in the template. The CSS
   below assumes it has happened; without spans every effect degrades to a
   plain fade, which is a safe failure.

   House rule: ONE text effect per screen. Two is a lava lamp.
   ========================================================================== */

/* T1 · type — the typewriter. The site's signature; hero only. */
.tx-type {
	display: inline-block;
	overflow: hidden;
	white-space: nowrap;
	border-right: var(--border-2) solid var(--accent);
	width: 0;
	animation:
		txType 2.4s steps(28, end) both,
		txCaret 0.9s steps(1) infinite;
}

@keyframes txType { to { width: 100%; } }

@keyframes txCaret { 50% { border-color: transparent; } }

/* T2 · chars — each character rises. The workhorse for headlines. */
.tx-chars > span {
	display: inline-block;
	animation: fx-rise var(--dur-3) var(--ease-out) both;
	animation-delay: calc(var(--i, 0) * 28ms);
}

/* T3 · words — same idea, one span per word. Cheaper, and better for long
   lines where per-character stagger reads as jitter. */
.tx-words > span {
	display: inline-block;
	animation: fx-rise var(--dur-3) var(--ease-out) both;
	animation-delay: calc(var(--i, 0) * 70ms);
}

/* T4 · wave — a slow vertical ripple. Loops, so: playful surfaces only. */
.tx-wave > span {
	display: inline-block;
	animation: txWave 2.2s var(--ease-inout) infinite;
	animation-delay: calc(var(--i, 0) * 55ms);
}

@keyframes txWave {
	0%, 60%, 100% { transform: translateY(0); }
	30%           { transform: translateY(-0.16em); }
}

/* T5 · shine — a highlight sweeps across the letterforms. For the one
   number or word that matters on the page. */
.tx-shine {
	background: linear-gradient(
		100deg,
		currentColor 0%, currentColor 40%,
		color-mix(in srgb, var(--accent) 85%, white) 50%,
		currentColor 60%, currentColor 100%
	);
	background-size: 260% 100%;
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
	animation: txShine 2.6s var(--ease-inout) infinite;
}

@keyframes txShine {
	from { background-position: 160% 0; }
	to   { background-position: -60% 0; }
}

/* T6 · underline — the link behaviour, promoted to a reveal. */
.tx-underline {
	position: relative;
	display: inline-block;
}

.tx-underline::after {
	content: '';
	position: absolute;
	left: 0;
	right: 0;
	bottom: -0.08em;
	height: var(--border-2);
	background: var(--accent);
	transform-origin: left center;
	animation: fx-draw var(--dur-3) var(--ease-out) both;
	animation-delay: var(--dur-3);
}

/* T7 · highlight — a marker-pen sweep behind the words. */
.tx-highlight {
	background-image: linear-gradient(var(--accent-soft), var(--accent-soft));
	background-repeat: no-repeat;
	background-position: 0 88%;
	background-size: 0% 0.55em;
	animation: txHighlight var(--dur-4) var(--ease-out) both;
	animation-delay: 200ms;
	padding-inline: 0.1em;
}

@keyframes txHighlight { to { background-size: 100% 0.55em; } }

/* T8 · gradient — colour flows through the text. The only place a gradient
   is allowed on type, and only on a single word. */
.tx-gradient {
	background: linear-gradient(100deg, var(--accent), var(--craft), var(--accent));
	background-size: 220% 100%;
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
	animation: txFlow 6s var(--ease-linear) infinite;
}

@keyframes txFlow {
	from { background-position: 0% 0; }
	to   { background-position: 200% 0; }
}

/* T9 · glitch — two offset ghosts in accent and info. Signal/error moments,
   the 404, the "something broke" scene. Never on body copy. */
.tx-glitch {
	position: relative;
	display: inline-block;
}

.tx-glitch::before,
.tx-glitch::after {
	content: attr(data-text);
	position: absolute;
	inset: 0;
	pointer-events: none;
	clip-path: inset(0 0 55% 0);
}

.tx-glitch::before { color: var(--accent); animation: txGlitchA 2.6s steps(2) infinite; }
.tx-glitch::after  { color: var(--info-line); clip-path: inset(55% 0 0 0); animation: txGlitchB 2.6s steps(2) infinite; }

@keyframes txGlitchA {
	0%, 88%, 100% { transform: none; opacity: 0; }
	90%           { transform: translate(-2px, -1px); opacity: 0.9; }
	94%           { transform: translate(2px, 1px); opacity: 0.9; }
}

@keyframes txGlitchB {
	0%, 88%, 100% { transform: none; opacity: 0; }
	91%           { transform: translate(2px, 1px); opacity: 0.8; }
	96%           { transform: translate(-1px, -2px); opacity: 0.8; }
}

/* T10 · blur-in — text resolves into focus. Slow, cinematic, openers. */
.tx-blur > span {
	display: inline-block;
	animation: fx-blur var(--dur-4) var(--ease-out) both;
	animation-delay: calc(var(--i, 0) * 40ms);
}

/* T11 · mask-up — each line slides out from behind its own edge. Requires a
   wrapper per line with overflow hidden. The most expensive-looking one. */
.tx-mask { display: block; overflow: hidden; }

.tx-mask > span {
	display: block;
	animation: txMaskUp var(--dur-4) var(--ease-out) both;
	animation-delay: calc(var(--i, 0) * 90ms);
}

@keyframes txMaskUp {
	from { transform: translateY(105%); }
	to   { transform: none; }
}

/* T12 · flip-chars — characters rotate in on the X axis. Countdowns, stats,
   the number in a stat grid changing. */
.tx-flip > span {
	display: inline-block;
	transform-origin: 50% 60%;
	animation: txFlipChar var(--dur-3) var(--ease-out) both;
	animation-delay: calc(var(--i, 0) * 45ms);
}

@keyframes txFlipChar {
	from { opacity: 0; transform: perspective(600px) rotateX(-90deg); }
	to   { opacity: 1; transform: none; }
}

/* Bonus · count — a number rolling up. Pure CSS via @property, so it degrades
   to the final value where unsupported. */
@supports (background: paint(x)) {
	@property --tx-num {
		syntax: '<integer>';
		initial-value: 0;
		inherits: false;
	}
}

.tx-count {
	counter-reset: n var(--tx-num, 0);
	animation: txCount var(--dur-4) var(--ease-out) both;
	font-variant-numeric: tabular-nums;
}

.tx-count::after { content: counter(n); }

@keyframes txCount { to { --tx-num: var(--tx-to, 100); } }


/* =============================================================================
   ANNOTATIONS — the hand-drawn layer
   Marks that look like someone reached in and drew on the page. They all use
   one trick: an SVG stroke with stroke-dasharray equal to its own length,
   animating dashoffset to zero. Because the path is drawn rather than faded,
   the eye reads it as a gesture instead of an effect.

     <span class="an an-circle">
       token<svg viewBox="0 0 220 70" preserveAspectRatio="none" aria-hidden="true">
         <ellipse cx="110" cy="35" rx="103" ry="27" pathLength="100"/>
       </svg>
     </span>

   pathLength="100" normalises every path, so one CSS rule drives all of them.
   ========================================================================== */

.an {
	position: relative;
	display: inline-block;
	white-space: nowrap;
}

.an > svg {
	position: absolute;
	inset: -22% -8%;
	width: 116%;
	height: 144%;
	overflow: visible;
	pointer-events: none;
	fill: none;
	stroke: var(--an-color, var(--accent));
	stroke-width: var(--an-weight, 2.4);
	stroke-linecap: round;
	stroke-linejoin: round;
	stroke-dasharray: 100;
	stroke-dashoffset: 100;
	animation: anDraw var(--an-dur, 700ms) var(--ease-out) both;
	animation-delay: var(--an-delay, 250ms);
}

@keyframes anDraw { to { stroke-dashoffset: 0; } }

/* The circle. Slightly over-rotated and not quite closed, because a perfect
   ellipse reads as a shape and a wonky one reads as a pen. */
.an-circle > svg { rotate: -1.5deg; }

/* Two loops — the "I really mean this one" version. Draws the second pass a
   beat after the first. */
.an-circle-2 > svg { --an-dur: 1100ms; rotate: -2deg; }

/* Underline: one confident swipe under the word. */
.an-underline > svg { inset: auto -4% -18%; height: 40%; width: 108%; }

/* Strike-through, for the thing that turned out to be wrong. */
.an-strike > svg { inset: 34% -6% auto; height: 30%; width: 112%; --an-color: var(--danger-line); }

/* Box — a rectangle drawn round a whole phrase. */
.an-box > svg { inset: -30% -6%; height: 160%; width: 112%; }

/* Arrow — points at the thing from below-left. Its own element, placed by the
   markup rather than wrapping text. */
.an-arrow {
	display: inline-block;
	width: 4.5rem;
	height: 3rem;
	color: var(--craft);
}

.an-arrow > svg {
	position: static;
	width: 100%;
	height: 100%;
	inset: auto;
	stroke: currentColor;
	--an-weight: 2;
}

/* Colour + weight modifiers */
.an-craft  { --an-color: var(--craft); }
.an-ink    { --an-color: var(--fg-default); }
.an-thin   { --an-weight: 1.6; }
.an-thick  { --an-weight: 3.4; }

/* The marker highlight, rebuilt: the sweep now has soft ends and a slight
   tilt, the way a real marker leaves the page. */
.tx-highlight {
	background-image: linear-gradient(
		101deg,
		color-mix(in srgb, var(--accent) 22%, transparent) 0%,
		color-mix(in srgb, var(--accent) 30%, transparent) 55%,
		color-mix(in srgb, var(--accent) 18%, transparent) 100%
	);
	background-repeat: no-repeat;
	background-position: 0 84%;
	background-size: 0% 0.62em;
	border-radius: 0.1em;
	animation: txHighlight var(--dur-4) var(--ease-out) both;
	animation-delay: 220ms;
	padding-inline: 0.14em;
	box-decoration-break: clone;
	-webkit-box-decoration-break: clone;
}

.tx-highlight-craft { --accent: var(--craft); }

/* =============================================================================
   MICRO-INTERACTIONS — eight hover behaviours
   All pointer-gated, all one property, none of them longer than 200ms. The
   rule: a hover tells you something is interactive, then gets out of the way.
   ========================================================================== */

@media (hover: hover) and (pointer: fine) {

	/* 1 · swipe — an underline that runs in from the left and out to the right */
	.hx-swipe { position: relative; }
	.hx-swipe::after {
		content: '';
		position: absolute;
		left: 0;
		right: 0;
		bottom: -0.12em;
		height: var(--border-2);
		background: var(--accent);
		transform: scaleX(0);
		transform-origin: right center;
		transition: transform var(--dur-2) var(--ease-out);
	}
	.hx-swipe:hover::after { transform: scaleX(1); transform-origin: left center; }

	/* 2 · wipe — the background fills from the bottom */
	.hx-wipe {
		position: relative;
		isolation: isolate;
		transition: color var(--dur-2) var(--ease-out);
	}
	.hx-wipe::before {
		content: '';
		position: absolute;
		inset: 0;
		z-index: -1;
		background: var(--bg-inverse);
		transform: scaleY(0);
		transform-origin: bottom;
		transition: transform var(--dur-2) var(--ease-out);
	}
	.hx-wipe:hover { color: var(--fg-on-inverse); }
	.hx-wipe:hover::before { transform: scaleY(1); }

	/* 3 · sheen — a single specular pass. Cards with media, never text. */
	.hx-sheen { position: relative; overflow: hidden; }
	.hx-sheen::after {
		content: '';
		position: absolute;
		inset: 0;
		background: linear-gradient(105deg, transparent 35%, rgb(255 255 255 / 0.14) 50%, transparent 65%);
		transform: translateX(-120%);
		transition: transform var(--dur-4) var(--ease-out);
		pointer-events: none;
	}
	.hx-sheen:hover::after { transform: translateX(120%); }

	/* 4 · magnet — the element leans toward the pointer. --mx/--my from JS. */
	.hx-magnet {
		transition: transform var(--dur-2) var(--ease-out);
	}
	.hx-magnet:hover {
		transform: translate3d(calc(var(--mx, 0) * 4px), calc(var(--my, 0) * 4px), 0);
	}

	/* 5 · press — physical key travel. Buttons and tiles. */
	.hx-press { transition: transform var(--dur-1) var(--ease-out), box-shadow var(--dur-1) var(--ease-out); }
	.hx-press:hover { transform: translate3d(0, -1px, 0); box-shadow: var(--shadow-2); }
	.hx-press:active { transform: translate3d(0, 1px, 0); box-shadow: var(--shadow-0); }

	/* 6 · reveal — a caption slides up from the bottom edge of a card */
	.hx-reveal { position: relative; overflow: hidden; }
	.hx-reveal > .hx-reveal__panel {
		position: absolute;
		inset: auto 0 0;
		transform: translateY(100%);
		transition: transform var(--dur-3) var(--ease-out);
	}
	.hx-reveal:hover > .hx-reveal__panel { transform: none; }

	/* 7 · swap — two glyphs trade places on hover (play ↔ pause, → ↔ ✓) */
	.hx-swap { position: relative; display: inline-grid; overflow: hidden; }
	.hx-swap > * { grid-area: 1 / 1; transition: transform var(--dur-2) var(--ease-out), opacity var(--dur-2) var(--ease-out); }
	.hx-swap > :last-child { transform: translateY(110%); opacity: 0; }
	.hx-swap:hover > :first-child { transform: translateY(-110%); opacity: 0; }
	.hx-swap:hover > :last-child { transform: none; opacity: 1; }

	/* 8 · trace — the border draws itself round the element */
	.hx-trace { position: relative; }
	.hx-trace::before,
	.hx-trace::after {
		content: '';
		position: absolute;
		inset: 0;
		border-radius: inherit;
		pointer-events: none;
		border: var(--border-2) solid transparent;
		transition: border-color var(--dur-1) var(--ease-out);
	}
	.hx-trace::before { transition-delay: 0ms; }
	.hx-trace:hover::before { border-top-color: var(--accent); border-right-color: var(--accent); }
	.hx-trace:hover::after { border-bottom-color: var(--accent); border-left-color: var(--accent); transition-delay: var(--dur-1); }
}

/* =============================================================================
   SECTION PRESETS — five ways for a block to arrive
   Put one on a <section>; its children carry --i for the stagger. Native
   scroll-driven where supported, so no observer is needed.
   ========================================================================== */

.sec-fx > * { --sec-ease: var(--ease-out); }

/* 1 · cascade — children rise in sequence. The default for card decks. */
.sec-cascade > * {
	animation: fx-rise var(--dur-3) var(--sec-ease, var(--ease-out)) both;
	animation-delay: calc(var(--i, 0) * 70ms);
}

/* 2 · curtain — the whole block is wiped down from its top edge. Heroes. */
.sec-curtain {
	animation: secCurtain var(--dur-4) var(--ease-inout) both;
}

@keyframes secCurtain {
	from { clip-path: inset(0 0 100% 0); }
	to   { clip-path: inset(0 0 0 0); }
}

/* 3 · split — odd children come from the left, even from the right. Two-column
   sections, before/after, comparisons. */
.sec-split > *:nth-child(odd)  { animation: fx-slide-l var(--dur-4) var(--ease-out) both; }
.sec-split > *:nth-child(even) { animation: fx-slide-r var(--dur-4) var(--ease-out) both; }
.sec-split > * { animation-delay: calc(var(--i, 0) * 60ms); }

/* 4 · focus — the block resolves from blur, like a lens finding it. Quotes,
   single-statement sections. */
.sec-focus { animation: fx-blur var(--dur-4) var(--ease-out) both; }

/* 5 · lift — everything scales up a hair from 97% while fading in. Galleries
   and stat grids, where individual movement would look busy. */
.sec-lift > * {
	animation: secLift var(--dur-4) var(--ease-out) both;
	animation-delay: calc(var(--i, 0) * 45ms);
}

@keyframes secLift {
	from { opacity: 0; transform: scale3d(0.97, 0.97, 1) translate3d(0, 8px, 0); }
	to   { opacity: 1; transform: none; }
}

/* Fire any preset on scroll rather than on load. */
@supports (animation-timeline: view()) {
	@media (prefers-reduced-motion: no-preference) {
		.sec-on-scroll,
		.sec-on-scroll > * {
			animation-timeline: view();
			animation-range: entry 4% cover 24%;
		}
	}
}

/* =============================================================================
   STINGS — the three-second brand hits
   Full-canvas idents. Five of them, one per context. Each is a single element
   plus two children, and each ends on the wordmark so the last frame is always
   the same image.

     <div class="sting sting-drop">
       <span class="sting__dot"></span>
       <span class="sting__word"> … logo markup … </span>
     </div>
   ========================================================================== */

.sting {
	position: relative;
	display: grid;
	place-items: center;
	aspect-ratio: var(--ratio-video);
	background: var(--ink-1000);
	color: var(--pure-white);
	overflow: hidden;
	isolation: isolate;
	border-radius: var(--radius-media);
}

/* The dot is only used by the drop ident: it falls, lands, and hands over to
   the wordmark's own tittle — which is the point of the whole thing. Every
   other sting hides it, because the mark already contains it. */
.sting__dot {
	position: absolute;
	width: 5%;
	aspect-ratio: 1;
	border-radius: var(--radius-full);
	background: var(--accent);
	opacity: 0;
}

.sting__word {
	position: relative;
	z-index: 2;
	display: inline-flex;
}

/* 1 · drop — the dot falls in, lands hard, and the wordmark shocks outward
   from the impact. The house ident: it is the record light arriving. */
.sting-drop .sting__dot { animation: stingDrop 1.5s var(--ease-out) both; }
.sting-drop .sting__word { animation: stingShock 1.5s var(--ease-out) both; }
.sting-drop::after {
	content: '';
	position: absolute;
	inset: 0;
	background: radial-gradient(circle at 50% 50%, color-mix(in srgb, var(--accent) 55%, transparent), transparent 60%);
	opacity: 0;
	animation: stingFlash 1.5s var(--ease-out) both;
}

@keyframes stingDrop {
	0%   { transform: translate3d(0, -260%, 0) scale(0.6); opacity: 0; }
	10%  { opacity: 1; }
	38%  { transform: none; opacity: 1; }
	46%  { transform: scale(1.6); opacity: 1; }
	56%  { transform: scale(0.9); opacity: 0; }   /* hands over to the tittle */
	100% { transform: scale(0.9); opacity: 0; }
}

@keyframes stingShock {
	0%, 36% { opacity: 0; letter-spacing: 0.6em; filter: blur(6px); }
	52%     { opacity: 1; letter-spacing: -0.02em; filter: blur(0); }
	100%    { opacity: 1; letter-spacing: -0.045em; }
}

@keyframes stingFlash {
	0%, 36% { opacity: 0; }
	44%     { opacity: 0.9; }
	100%    { opacity: 0; }
}

/* 2 · static — the frame tunes in through TV noise. Videos and the /videos
   collection. */
.sting-static::before {
	content: '';
	position: absolute;
	inset: 0;
	z-index: 3;
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
	background-size: 180px 180px;
	animation: stingStatic 1.4s steps(6) both;
}

.sting-static .sting__word { animation: fx-fade 1.4s var(--ease-out) both; animation-delay: 500ms; }
.sting-static .sting__dot { display: none; }

@keyframes stingStatic {
	0%   { opacity: 0.9; }
	70%  { opacity: 0.35; }
	100% { opacity: 0; }
}

/* 3 · shutter — the camera opens onto the mark. Photography, travel. */
.sting-shutter .sting__word { animation: stingIris 1.3s var(--ease-inout) both; }
.sting-shutter .sting__dot { display: none; }

@keyframes stingIris {
	0%   { clip-path: circle(0% at 50% 50%); opacity: 0; }
	40%  { opacity: 1; }
	100% { clip-path: circle(75% at 50% 50%); opacity: 1; }
}

/* 4 · type — the mark types itself out. Build logs, code, the docs. */
.sting-type .sting__dot { display: none; }
.sting-type .sting__word {
	overflow: hidden;
	white-space: nowrap;
	border-right: 3px solid var(--accent);
	width: 0;
	animation: stingType 1.4s steps(9, end) both, txCaret 0.8s steps(1) 6;
}

@keyframes stingType { to { width: 100%; } }

/* 5 · sweep — a light bar passes and leaves the mark behind. The quiet one:
   essays, the newsletter, anything that shouldn't shout. */
.sting-sweep .sting__dot { display: none; }
.sting-sweep .sting__word { animation: fx-fade 1.2s var(--ease-out) both; animation-delay: 350ms; }
.sting-sweep::after {
	content: '';
	position: absolute;
	top: 0;
	bottom: 0;
	width: 22%;
	background: linear-gradient(100deg, transparent, rgb(255 255 255 / 0.16), transparent);
	animation: stingSweep 1.6s var(--ease-inout) both;
}

@keyframes stingSweep {
	from { transform: translateX(-160%); }
	to   { transform: translateX(560%); }
}

/* Replay affordance for the preview — remove the class and re-add it to fire
   any sting again. */
.sting-paused *,
.sting-paused::after,
.sting-paused::before { animation-play-state: paused; }

/* ── Page transitions ─────────────────────────────────────────────────────
   Native, cross-document, one at-rule. `@view-transition` is pure progressive
   enhancement — a browser that doesn't know it just navigates, no JS, no
   fallback code to write. Per spec, a cross-document transition is styled by
   the ARRIVING document only, so the flavour is just whichever
   `data-transition` the destination's <html> carries; the collection sets it
   once in its own shell, nothing per-link.

   Six flavours: `takeoff` (travel), `pop` (course), `unfold` (resume),
   `type` (blog — new), `slate` (webseries — new, the clapperboard), and the
   unflavoured default (a plain cross-fade) for everything else. `static`
   ships too, for the video/YouTube destinations the spec table names but
   this system doesn't build a page for yet. dur-4 throughout — "the
   deliberate one: page/scene transitions" is what it's for. */

@view-transition {
	navigation: auto;
}

::view-transition-group(root) { animation-duration: var(--dur-4); }
::view-transition-old(root),
::view-transition-new(root) { animation-duration: var(--dur-4); animation-fill-mode: both; }

/* No flavour set: a plain cross-fade rather than the browser's default
   cross-fade-and-slide, so an unstyled destination still feels intentional. */
html:not([data-transition])::view-transition-old(root) { animation: fx-fade var(--dur-3) var(--ease-in) reverse both; }
html:not([data-transition])::view-transition-new(root) { animation: fx-fade var(--dur-3) var(--ease-out) both; }

/* static — a beat of TV static before the picture resolves. */
@keyframes ptStaticIn {
	0%   { opacity: 0; filter: contrast(4) brightness(0.5) saturate(0); }
	55%  { opacity: 1; filter: contrast(2.2) brightness(1.3) saturate(0); }
	100% { opacity: 1; filter: none; }
}
html[data-transition='static']::view-transition-new(root) { animation: ptStaticIn var(--dur-4) var(--ease-out) both; }
html[data-transition='static']::view-transition-old(root) { animation: fx-fade var(--dur-2) var(--ease-in) reverse both; }

/* turn — guides: a page-turn, the incoming page rotating up from the right
   edge like a leaf of a book settling flat. */
@keyframes ptTurnIn {
	0%   { transform: perspective(1600px) rotateY(-8deg) translateX(3%); transform-origin: left center; opacity: 0; }
	100% { transform: perspective(1600px) rotateY(0deg) translateX(0); transform-origin: left center; opacity: 1; }
}
html[data-transition='turn']::view-transition-new(root) { animation: ptTurnIn var(--dur-4) var(--ease-out) both; }
html[data-transition='turn']::view-transition-old(root) { animation: fx-fade var(--dur-2) var(--ease-in) reverse both; }

/* slate — the clapperboard: the outgoing page slams shut to a line, the
   incoming one snaps open from it. Webseries' own transition, built from the
   same device as the icon (icons/creator/slate.svg). */
@keyframes ptSlateClose {
	0%   { clip-path: inset(0 0 0 0); }
	100% { clip-path: inset(49.3% 0 49.3% 0); }
}

@keyframes ptSlateOpen {
	0%   { clip-path: inset(49.3% 0 49.3% 0); }
	100% { clip-path: inset(0 0 0 0); }
}
html[data-transition='slate']::view-transition-old(root) { animation: ptSlateClose calc(var(--dur-4) * 0.6) var(--ease-in) both; }
html[data-transition='slate']::view-transition-new(root) { animation: ptSlateOpen calc(var(--dur-4) * 0.6) var(--ease-out) calc(var(--dur-4) * 0.5) both; }

/* takeoff — travel: the outgoing page banks and climbs away, the incoming one
   arrives from the same angle, plane-departure diagonal. */
@keyframes ptTakeoffOut { to { transform: translate3d(-6%, -14%, 0) rotate(-2.5deg) scale(0.96); opacity: 0; } }

@keyframes ptTakeoffIn { from { transform: translate3d(6%, 14%, 0) rotate(2deg) scale(0.96); opacity: 0; } }
html[data-transition='takeoff']::view-transition-old(root) { animation: ptTakeoffOut var(--dur-4) var(--ease-in) both; }
html[data-transition='takeoff']::view-transition-new(root) { animation: ptTakeoffIn var(--dur-4) var(--ease-out) both; }

/* pop — course: a small confident scale-up, pen-to-table snap rather than a
   slide — a lesson starts, it doesn't arrive from off-screen. */
@keyframes ptPopIn { from { transform: scale(0.96); opacity: 0; } to { transform: scale(1); opacity: 1; } }
html[data-transition='pop']::view-transition-new(root) { animation: ptPopIn var(--dur-3) var(--ease-spring) both; }
html[data-transition='pop']::view-transition-old(root) { animation: fx-fade var(--dur-2) var(--ease-in) reverse both; }

/* unfold — resume: a vertical reveal from the centre out, like a folded page
   opening flat. */
@keyframes ptUnfoldIn {
	0%   { clip-path: inset(0 0 100% 0); }
	100% { clip-path: inset(0 0 0% 0); }
}
html[data-transition='unfold']::view-transition-new(root) { animation: ptUnfoldIn var(--dur-4) var(--ease-out) both; }
html[data-transition='unfold']::view-transition-old(root) { animation: fx-fade var(--dur-2) var(--ease-in) reverse both; }

/* type — blog: the page is wiped in left-to-right in discrete steps, a
   cursor writing it into place. */
@keyframes ptTypeIn {
	0%   { clip-path: inset(0 100% 0 0); }
	100% { clip-path: inset(0 0% 0 0); }
}
html[data-transition='type']::view-transition-new(root) { animation: ptTypeIn var(--dur-4) steps(18, end) both; }
html[data-transition='type']::view-transition-old(root) { animation: fx-fade var(--dur-2) var(--ease-in) reverse both; }

/* envelope — newsletter: the page unfolds down from the top, like a letter
   coming out of an envelope, rather than sliding or fading in. */
@keyframes ptEnvelopeIn {
	0%   { transform: scaleY(0.3) translateY(-6%); transform-origin: top center; opacity: 0; }
	100% { transform: scaleY(1) translateY(0); transform-origin: top center; opacity: 1; }
}
html[data-transition='envelope']::view-transition-new(root) { animation: ptEnvelopeIn var(--dur-4) var(--ease-out) both; }
html[data-transition='envelope']::view-transition-old(root) { animation: fx-fade var(--dur-2) var(--ease-in) reverse both; }

/* terminal — projects: a hard contrast snap before the left-to-right wipe,
   prompt-command-enter rather than a soft cross-fade. */
@keyframes ptTerminalIn {
	0%   { clip-path: inset(0 100% 0 0); filter: contrast(1.5) brightness(0.7); }
	60%  { filter: contrast(1.2) brightness(0.9); }
	100% { clip-path: inset(0 0% 0 0); filter: none; }
}
html[data-transition='terminal']::view-transition-new(root) { animation: ptTerminalIn var(--dur-4) steps(24, end) both; }
html[data-transition='terminal']::view-transition-old(root) { animation: fx-fade var(--dur-2) var(--ease-in) reverse both; }

/* ── Reduced motion ────────────────────────────────────────────────────────
   Everything degrades to a plain, fast opacity change. Nothing is removed —
   the reveal still happens, it just stops moving. */

@media (prefers-reduced-motion: reduce) {
	:root {
		--dur-1: 1ms;
		--dur-2: 1ms;
		--dur-3: 1ms;
		--dur-4: 1ms;
		--stagger: 0ms;
	}

	.fx-rise, .fx-slide-l, .fx-slide-r, .fx-scale, .fx-blur, .fx-clip {
		animation: fx-fade 160ms var(--ease-out) both;
	}

	.fx-pulse, .fx-spin {
		animation: none;
	}

	.fx-lift:hover, .fx-tilt:hover {
		transform: none;
	}

	/* Text effects: keep the reveal, drop the movement and every loop. */
	.tx-chars > span, .tx-words > span, .tx-blur > span,
	.tx-mask > span, .tx-flip > span {
		animation: fx-fade 160ms var(--ease-out) both;
		transform: none;
	}

	.tx-wave > span, .tx-shine, .tx-gradient { animation: none; }
	.tx-glitch::before, .tx-glitch::after { display: none; }
	.tx-type { width: auto; border-right: 0; animation: none; }
	.tx-highlight { background-size: 100% 0.55em; animation: none; }
	.tx-underline::after { transform: none; animation: none; }
	.win-term__cursor { animation: none; }

	/* Annotations draw instantly; stings hold their last frame; hovers stop. */
	.an > svg { animation: none; stroke-dashoffset: 0; }
	.sting__dot, .sting__word,
	.sting::after, .sting::before { animation: none !important; opacity: 1; }
	.sting-type .sting__word { width: auto; border-right: 0; }
	.sting-shutter .sting__word { clip-path: none; }
	.sec-cascade > *, .sec-split > *, .sec-lift > * { animation: fx-fade 160ms var(--ease-out) both; }
	.sec-curtain, .sec-focus { animation: fx-fade 160ms var(--ease-out) both; clip-path: none; filter: none; }

	/* Page transitions: no clip, no filter spike, no movement — an instant
	   swap, which is what a browser does with @view-transition here anyway. */
	::view-transition-group(*),
	::view-transition-old(*),
	::view-transition-new(*) { animation: none !important; }

	*, *::before, *::after {
		animation-duration: 1ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 1ms !important;
		scroll-behavior: auto !important;
	}
}      /* 13 animations + 12 text effects        */
/* =============================================================================
   06 · LAYOUT
   Container scale, a 12-column fluid grid, four composition primitives, and
   the media canvases. "One system, every crop" lives here.
   ========================================================================== */

:root {
	--grid-cols: 12;
	--grid-gap: var(--space-6);
	--grid-gap-tight: var(--space-4);
}

/* ── Containers ────────────────────────────────────────────────────────────
   One class, four crops. Gutter comes from the token so the nav island and the
   content column always share an edge. */

.container,
.container-prose,
.container-narrow,
.container-wide,
.container-full {
	width: 100%;
	margin-inline: auto;
	padding-inline: var(--gutter);
}

.container        { max-width: calc(var(--w-site) + var(--gutter) * 2); }
.container-prose  { max-width: calc(var(--w-prose) + var(--gutter) * 2); }
.container-narrow { max-width: calc(var(--w-narrow) + var(--gutter) * 2); }
.container-wide   { max-width: calc(var(--w-wide) + var(--gutter) * 2); }
.container-full   { max-width: none; }

/* Break a child out of a prose column to the full container width. */
.bleed-wide {
	width: min(var(--w-site), 100vw - var(--gutter) * 2);
	margin-inline: calc(50% - min(var(--w-site), 100vw - var(--gutter) * 2) / 2);
}

.bleed-full {
	width: 100vw;
	margin-inline: calc(50% - 50vw);
}

/* ── Section rhythm ─────────────────────────────────────────────────────────
   A section pads both ends, which is right when it is alone and wrong the
   moment two of them sit together: 96px + 96px reads as a hole in the page,
   not as a gap between two things. Neighbours drop the leading padding, so the
   rhythm between sections is one section's worth, not two.

   A section that paints its own surface needs its top padding back — that is
   what .section-standalone is for.
   ------------------------------------------------------------------------- */

.section    { padding-block: var(--section-md); }
.section-sm { padding-block: var(--section-sm); }
.section-lg { padding-block: var(--section-lg); }

:is(.section, .section-sm, .section-lg) + :is(.section, .section-sm, .section-lg):not(.section-standalone) {
	padding-block-start: 0;
}

/* ── 12-column grid ────────────────────────────────────────────────────────
   Fluid, no breakpoint classes: columns collapse to full width below 48rem
   using a container-query-free min() trick on the span. */

.grid-12 {
	display: grid;
	grid-template-columns: repeat(var(--grid-cols), minmax(0, 1fr));
	gap: var(--grid-gap);
}

.col-1  { grid-column: span 1; }
.col-2  { grid-column: span 2; }
.col-3  { grid-column: span 3; }
.col-4  { grid-column: span 4; }
.col-5  { grid-column: span 5; }
.col-6  { grid-column: span 6; }
.col-7  { grid-column: span 7; }
.col-8  { grid-column: span 8; }
.col-9  { grid-column: span 9; }
.col-10 { grid-column: span 10; }
.col-12 { grid-column: span 12; }

@media (max-width: 62rem) {
	.col-3, .col-4, .col-5 { grid-column: span 6; }
	.col-7, .col-8, .col-9, .col-10 { grid-column: span 12; }
}

@media (max-width: 40rem) {
	.grid-12 > [class^='col-'] { grid-column: span 12; }
}

/* ── Auto grids ────────────────────────────────────────────────────────────
   For card decks, where "how many fit" beats "how many columns". */

.grid-auto {
	display: grid;
	gap: var(--grid-gap);
	grid-template-columns: repeat(auto-fill, minmax(min(18rem, 100%), 1fr));
}

.grid-auto-sm {
	display: grid;
	gap: var(--grid-gap-tight);
	grid-template-columns: repeat(auto-fill, minmax(min(12rem, 100%), 1fr));
}

.grid-auto-lg {
	display: grid;
	gap: var(--grid-gap);
	grid-template-columns: repeat(auto-fill, minmax(min(24rem, 100%), 1fr));
}

/* ── The φ split ───────────────────────────────────────────────────────────
   Hero and article layouts split on the golden ratio rather than 2/3, so the
   sidebar never competes with the main column. Collapses at 62rem. */

.split-phi {
	display: grid;
	gap: var(--space-10);
	grid-template-columns: 1.618fr 1fr;
	align-items: start;
}

.split-phi-reverse {
	display: grid;
	gap: var(--space-10);
	grid-template-columns: 1fr 1.618fr;
	align-items: start;
}

.split-half {
	display: grid;
	gap: var(--space-8);
	grid-template-columns: repeat(2, minmax(0, 1fr));
}

@media (max-width: 62rem) {
	.split-phi,
	.split-phi-reverse,
	.split-half {
		grid-template-columns: minmax(0, 1fr);
	}
}

/* ── Composition primitives ────────────────────────────────────────────────
   Stack (vertical rhythm), cluster (wrapping inline row), sidebar-y things.
   Nearly every layout in the theme is one of these three. */

.stack       { display: flex; flex-direction: column; gap: var(--space-4); }
.stack-xs    { display: flex; flex-direction: column; gap: var(--space-2); }
.stack-sm    { display: flex; flex-direction: column; gap: var(--space-3); }
.stack-lg    { display: flex; flex-direction: column; gap: var(--space-6); }
.stack-xl    { display: flex; flex-direction: column; gap: var(--space-10); }

/* Each cluster size stands alone — no base class required, same as .stack-*. */
.cluster,
.cluster-sm,
.cluster-lg {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-3);
}

.cluster-sm { gap: var(--space-2); }
.cluster-lg { gap: var(--space-5); }

.row-between {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-4);
}

.center-x { display: grid; justify-items: center; }

/* Sticky rail (course sidebars, TOCs). Offsets by the nav height token so it
   never hides under a sticky header. */
.sticky-rail {
	position: sticky;
	top: calc(var(--nav-h, 4rem) + var(--space-6));
	align-self: start;
}

/* ── Canvases ──────────────────────────────────────────────────────────────
   Real social/media crops with the safe area baked in, so a thumbnail
   designed here survives being cropped by a platform. */

.canvas {
	position: relative;
	overflow: hidden;
	background: var(--bg-media);
	border-radius: var(--radius-media);
	isolation: isolate;
}

.canvas-thumb  { aspect-ratio: var(--ratio-video); }
.canvas-short  { aspect-ratio: var(--ratio-short); }
.canvas-square { aspect-ratio: var(--ratio-square); }
.canvas-poster { aspect-ratio: var(--ratio-poster); }
.canvas-cinema { aspect-ratio: var(--ratio-cinema); }

/* Content inside a canvas lives here. 6% inset ≈ every platform's safe area. */
.canvas__safe {
	position: absolute;
	inset: 6%;
	display: flex;
	flex-direction: column;
	justify-content: flex-end;
	gap: var(--space-2);
}

.canvas > img,
.canvas > video {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* Legibility scrim under canvas text — a gradient, never a flat overlay. */
.canvas__scrim::after {
	content: '';
	position: absolute;
	inset: 0;
	z-index: var(--z-below);
	background: linear-gradient(
		to top,
		rgb(0 0 0 / 0.72) 0%,
		rgb(0 0 0 / 0.32) 38%,
		rgb(0 0 0 / 0) 68%
	);
}

/* ── The frame ─────────────────────────────────────────────────────────────
   Viewfinder brackets. Signature device #2: it wraps the thing being shown.
   Corner length scales with --frame-size; colour defaults to the craft amber. */

.frame {
	--frame-size: 22px;
	--frame-inset: 10px;
	--frame-color: var(--craft);
	--frame-weight: var(--border-2);
	position: relative;
}

.frame::before,
.frame::after {
	content: '';
	position: absolute;
	width: var(--frame-size);
	height: var(--frame-size);
	pointer-events: none;
}

.frame::before {
	top: var(--frame-inset);
	left: var(--frame-inset);
	border-top: var(--frame-weight) solid var(--frame-color);
	border-left: var(--frame-weight) solid var(--frame-color);
}

.frame::after {
	right: var(--frame-inset);
	bottom: var(--frame-inset);
	border-right: var(--frame-weight) solid var(--frame-color);
	border-bottom: var(--frame-weight) solid var(--frame-color);
}

/* Four-corner variant — needs two inner spans to carry the other two corners. */
.frame-4 > .frame__tr,
.frame-4 > .frame__bl {
	position: absolute;
	width: var(--frame-size);
	height: var(--frame-size);
	pointer-events: none;
}

.frame-4 > .frame__tr {
	top: var(--frame-inset);
	right: var(--frame-inset);
	border-top: var(--frame-weight) solid var(--frame-color);
	border-right: var(--frame-weight) solid var(--frame-color);
}

.frame-4 > .frame__bl {
	bottom: var(--frame-inset);
	left: var(--frame-inset);
	border-bottom: var(--frame-weight) solid var(--frame-color);
	border-left: var(--frame-weight) solid var(--frame-color);
}

.frame-signal { --frame-color: var(--accent); }
.frame-ink    { --frame-color: var(--line-strong); }

/* ── Aspect + media helpers ──────────────────────────────────────────────── */

.ratio-video  { aspect-ratio: var(--ratio-video); }
.ratio-photo  { aspect-ratio: var(--ratio-photo); }
.ratio-square { aspect-ratio: var(--ratio-square); }
.ratio-short  { aspect-ratio: var(--ratio-short); }

.object-cover    { width: 100%; height: 100%; object-fit: cover; }
.object-contain  { width: 100%; height: 100%; object-fit: contain; }

/* ── Gap & flow helpers ──────────────────────────────────────────────────
   Gap is the only spacing mechanism in layouts — no margins between siblings,
   ever. These map the spacing ladder onto grid and flex containers. */

.gap-0  { gap: var(--space-0); }
.gap-1  { gap: var(--space-1); }
.gap-2  { gap: var(--space-2); }
.gap-3  { gap: var(--space-3); }
.gap-4  { gap: var(--space-4); }
.gap-6  { gap: var(--space-6); }
.gap-8  { gap: var(--space-8); }
.gap-12 { gap: var(--space-12); }
.gap-16 { gap: var(--space-16); }

.gap-x-4 { column-gap: var(--space-4); }
.gap-x-6 { column-gap: var(--space-6); }
.gap-y-4 { row-gap: var(--space-4); }
.gap-y-8 { row-gap: var(--space-8); }

/* The owl: vertical rhythm inside flowing content (Ghost's {{content}}), where
   you can't put a class on every child. */
.flow > * + * { margin-block-start: var(--flow, var(--space-5)); }
.flow-tight { --flow: var(--space-3); }
.flow-loose { --flow: var(--space-8); }

/* ── Grid helpers ────────────────────────────────────────────────────────
   Named placements, so templates never write raw grid-column values. */

.grid { display: grid; gap: var(--grid-gap); }

.grid-2 { display: grid; gap: var(--grid-gap); grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { display: grid; gap: var(--grid-gap); grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-4 { display: grid; gap: var(--grid-gap); grid-template-columns: repeat(4, minmax(0, 1fr)); }
.grid-6 { display: grid; gap: var(--grid-gap); grid-template-columns: repeat(6, minmax(0, 1fr)); }

@media (max-width: 62rem) {
	.grid-3, .grid-4, .grid-6 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 40rem) {
	.grid-2, .grid-3, .grid-4, .grid-6 { grid-template-columns: minmax(0, 1fr); }
}

/* Content + rail, the article shape. Rail collapses under the content.
   The width is a variable because 18rem is right for an ad, a table of
   contents or a sponsor block, and too narrow the moment the rail holds rows
   with thumbnails — an episode list, a related deck. One knob, two sizes, no
   second layout. */
.grid-rail {
	--rail: 18rem;
	display: grid;
	gap: var(--space-10);
	grid-template-columns: minmax(0, 1fr) var(--rail);
	align-items: start;
}

/* The mirrored rail. Standalone on purpose — it carries the base properties
   too, so `.grid-rail-left` alone works and can't silently stack. */
.grid-rail-left {
	--rail: 18rem;
	display: grid;
	gap: var(--space-10);
	align-items: start;
	grid-template-columns: var(--rail) minmax(0, 1fr);
}

/* Wide enough for rows that carry a 16:9 thumbnail and still break their title
   onto one line. */
.grid-rail-wide { --rail: 24rem; }

@media (max-width: 68rem) {
	.grid-rail,
	.grid-rail-left { grid-template-columns: minmax(0, 1fr); }
}

/* Editorial grid — a prose column that can bleed wide or full without any
   wrapper divs. Children opt in by class. */
.grid-editorial {
	display: grid;
	grid-template-columns:
		[full-start] minmax(var(--gutter), 1fr)
		[wide-start] minmax(0, 12rem)
		[text-start] min(var(--w-prose), 100% - var(--gutter) * 2) [text-end]
		minmax(0, 12rem) [wide-end]
		minmax(var(--gutter), 1fr) [full-end];
}

.grid-editorial > *          { grid-column: text; }
.grid-editorial > .col-wide  { grid-column: wide; }
.grid-editorial > .col-full  { grid-column: full; }

/* Masonry-ish: equal columns that fill by height. For the tag pile and the
   guestbook, where order doesn't matter. */
.grid-cols-fill {
	column-count: 3;
	column-gap: var(--grid-gap);
}

.grid-cols-fill > * { break-inside: avoid; margin-bottom: var(--grid-gap); }

@media (max-width: 62rem) { .grid-cols-fill { column-count: 2; } }

@media (max-width: 40rem) { .grid-cols-fill { column-count: 1; } }

/* Placement */
.place-center { display: grid; place-items: center; }
.self-start { align-self: start; }
.self-end { align-self: end; }
.justify-between { justify-content: space-between; }
.items-start { align-items: start; }
.items-center { align-items: center; }

/* ── Divider ───────────────────────────────────────────────────────────────
   The house rule: a horizontal rule fades out at both ends, so a page never
   feels boxed in. */

.rule {
	height: var(--border-hair);
	border: 0;
	background: linear-gradient(
		to right,
		transparent,
		var(--line-default) 12%,
		var(--line-default) 88%,
		transparent
	);
}

.rule-solid {
	height: var(--border-hair);
	border: 0;
	background: var(--line-default);
}      /* containers, grids, gaps, canvases      */
/* =============================================================================
   07 · BACKGROUND PATTERNS
   16 CSS-only textures. Zero images, zero requests. Each paints on a ::before
   layer so the element keeps its own background, border and content.

   Usage:  <div class="surface pattern pattern-grid pattern-lg fade-corners">
   Compose:  size (--sm/--lg) · ink (--signal/--craft) · fade (mask) · strength
   ========================================================================== */

.pattern {
	--p-size: 28px;
	--p-ink: var(--pattern-ink);
	--p-ink-2: var(--pattern-ink-2);
	--p-opacity: 1;
	position: relative;
	isolation: isolate;
}

.pattern::before {
	content: '';
	position: absolute;
	inset: 0;
	z-index: var(--z-below);
	opacity: var(--p-opacity);
	pointer-events: none;
	border-radius: inherit;
	background-repeat: repeat;
}

/* ── Size modifiers ───────────────────────────────────────────────────────── */
.pattern-xs { --p-size: 12px; }
.pattern-sm { --p-size: 18px; }
.pattern-lg { --p-size: 44px; }
.pattern-xl { --p-size: 72px; }

/* ── Ink modifiers ────────────────────────────────────────────────────────── */
/* On a media canvas the surface is near-black in BOTH themes, so the pattern
   ink can't follow the theme — it is always light. */
.pattern-media  { --p-ink: rgb(255 255 255 / 0.08); --p-ink-2: rgb(255 255 255 / 0.14); }
.pattern-signal { --p-ink: rgb(240 78 46 / 0.16); --p-ink-2: rgb(240 78 46 / 0.28); }
.pattern-craft  { --p-ink: rgb(217 163 58 / 0.28); --p-ink-2: rgb(217 163 58 / 0.45); }
.pattern-strong { --p-opacity: 1.6; }
.pattern-faint  { --p-opacity: 0.5; }

/* ── Fades — a pattern should never end on a hard edge ────────────────────── */
.fade-corners::before {
	mask-image: radial-gradient(ellipse 78% 78% at 50% 50%, #000 30%, transparent 100%);
}
.fade-b::before {
	mask-image: linear-gradient(to bottom, #000 0%, transparent 92%);
}
.fade-t::before {
	mask-image: linear-gradient(to top, #000 0%, transparent 92%);
}
.fade-r::before {
	mask-image: linear-gradient(to right, #000 20%, transparent 100%);
}
.fade-center::before {
	mask-image: radial-gradient(ellipse at 50% 50%, transparent 8%, #000 72%);
}

/* ── 01 · grid — the default surface texture, graph paper ─────────────────── */
.pattern-grid::before {
	background-image:
		linear-gradient(to right, var(--p-ink) var(--border-hair), transparent var(--border-hair)),
		linear-gradient(to bottom, var(--p-ink) var(--border-hair), transparent var(--border-hair));
	background-size: var(--p-size) var(--p-size);
}

/* ── 02 · blueprint — fine grid with a heavier major line every 5th ───────── */
.pattern-blueprint::before {
	background-image:
		linear-gradient(to right, var(--p-ink) var(--border-hair), transparent var(--border-hair)),
		linear-gradient(to bottom, var(--p-ink) var(--border-hair), transparent var(--border-hair)),
		linear-gradient(to right, var(--p-ink-2) var(--border-hair), transparent var(--border-hair)),
		linear-gradient(to bottom, var(--p-ink-2) var(--border-hair), transparent var(--border-hair));
	background-size:
		var(--p-size) var(--p-size),
		var(--p-size) var(--p-size),
		calc(var(--p-size) * 5) calc(var(--p-size) * 5),
		calc(var(--p-size) * 5) calc(var(--p-size) * 5);
}

/* ── 03 · dots ────────────────────────────────────────────────────────────── */
.pattern-dots::before {
	background-image: radial-gradient(var(--p-ink-2) 1.2px, transparent 1.3px);
	background-size: var(--p-size) var(--p-size);
}

/* ── 04 · halftone — dots that grow toward the bottom-right ───────────────── */
.pattern-halftone::before {
	background-image: radial-gradient(var(--p-ink-2) 22%, transparent 23%);
	background-size: var(--p-size) var(--p-size);
	mask-image: linear-gradient(135deg, transparent 20%, #000 100%);
}

/* ── 05 · golden — construction circles on the φ split ────────────────────── */
.pattern-golden::before {
	background-image:
		radial-gradient(circle at 38.2% 50%, transparent calc(var(--p-size) * 1.6),
			var(--p-ink-2) calc(var(--p-size) * 1.6 + 1px),
			transparent calc(var(--p-size) * 1.6 + 2px)),
		radial-gradient(circle at 61.8% 50%, transparent calc(var(--p-size) * 1.6),
			var(--p-ink-2) calc(var(--p-size) * 1.6 + 1px),
			transparent calc(var(--p-size) * 1.6 + 2px)),
		linear-gradient(to right, transparent 38.1%, var(--p-ink) 38.1%, var(--p-ink) 38.3%, transparent 38.3%),
		linear-gradient(to bottom, transparent 49.9%, var(--p-ink) 49.9%, var(--p-ink) 50.1%, transparent 50.1%);
	background-repeat: no-repeat;
	background-size: 100% 100%;
}

/* ── 06 · scanline — CRT horizontals, for media surfaces ──────────────────── */
.pattern-scanline::before {
	background-image: repeating-linear-gradient(
		to bottom,
		var(--p-ink) 0 1px,
		transparent 1px calc(var(--p-size) / 5)
	);
}

/* ── 07 · timecode — a film edge, ticks with a taller mark every 5th ──────── */
.pattern-timecode::before {
	background-image:
		repeating-linear-gradient(to right, var(--p-ink-2) 0 1px, transparent 1px var(--p-size)),
		repeating-linear-gradient(to right, var(--p-ink-2) 0 1px, transparent 1px calc(var(--p-size) * 5));
	background-size: 100% 12px, 100% 22px;
	background-position: 0 100%, 0 100%;
	background-repeat: repeat-x;
}

/* ── 08 · filmstrip — sprocket holes down both edges ──────────────────────── */
.pattern-filmstrip::before {
	background-image:
		repeating-linear-gradient(
			to bottom,
			var(--p-ink-2) 0 calc(var(--p-size) * 0.4),
			transparent calc(var(--p-size) * 0.4) var(--p-size)
		),
		repeating-linear-gradient(
			to bottom,
			var(--p-ink-2) 0 calc(var(--p-size) * 0.4),
			transparent calc(var(--p-size) * 0.4) var(--p-size)
		);
	background-size: calc(var(--p-size) * 0.5) 100%, calc(var(--p-size) * 0.5) 100%;
	background-position: 0 0, 100% 0;
	background-repeat: repeat-y;
}

/* ── 09 · hatch — 45° single-direction rule ───────────────────────────────── */
.pattern-hatch::before {
	background-image: repeating-linear-gradient(
		45deg,
		var(--p-ink) 0 1px,
		transparent 1px calc(var(--p-size) / 3)
	);
}

/* ── 10 · crosshatch ──────────────────────────────────────────────────────── */
.pattern-crosshatch::before {
	background-image:
		repeating-linear-gradient(45deg,  var(--p-ink) 0 1px, transparent 1px calc(var(--p-size) / 3)),
		repeating-linear-gradient(-45deg, var(--p-ink) 0 1px, transparent 1px calc(var(--p-size) / 3));
}

/* ── 11 · waveform — audio bars, heights driven by a repeating gradient ───── */
.pattern-waveform::before {
	background-image: repeating-linear-gradient(
		to right,
		var(--p-ink-2) 0 2px,
		transparent 2px calc(var(--p-size) / 4)
	);
	mask-image: repeating-linear-gradient(
		to right,
		#000 0 2px,
		transparent 2px calc(var(--p-size) / 4)
	), linear-gradient(to top, #000 12%, rgb(0 0 0 / 0.35) 60%, rgb(0 0 0 / 0.6) 100%);
	mask-composite: intersect;
	-webkit-mask-composite: source-in;
}

/* ── 12 · rays — a soft diagonal light shaft ──────────────────────────────── */
.pattern-rays::before {
	background-image: linear-gradient(
		108deg,
		transparent 30%,
		var(--p-ink) 44%,
		transparent 48%,
		transparent 58%,
		var(--p-ink) 68%,
		transparent 74%
	);
	background-repeat: no-repeat;
	background-size: 100% 100%;
}

/* ── 13 · arcs — concentric rings, a lens diagram ─────────────────────────── */
.pattern-arcs::before {
	background-image: repeating-radial-gradient(
		circle at 50% 50%,
		var(--p-ink-2) 0 1px,
		transparent 1px calc(var(--p-size) * 0.9)
	);
	background-repeat: no-repeat;
	background-size: 100% 100%;
}

/* ── 14 · glow — a single warm bloom, for hero corners ────────────────────── */
.pattern-glow::before {
	background-image: radial-gradient(
		ellipse 60% 52% at 50% 6%,
		var(--glow-color, rgb(240 78 46 / 0.16)) 0%,
		transparent 70%
	);
	background-repeat: no-repeat;
	background-size: 100% 100%;
}

.pattern-glow.pattern-craft { --glow-color: rgb(217 163 58 / 0.18); }

/* ── 15 · mesh — multi-point soft gradient, the only "colourful" texture ──── */
.pattern-mesh::before {
	background-image:
		radial-gradient(ellipse 42% 46% at 12% 8%,  rgb(240 78 46 / 0.20) 0%, transparent 70%),
		radial-gradient(ellipse 40% 44% at 88% 14%, rgb(217 163 58 / 0.16) 0%, transparent 72%),
		radial-gradient(ellipse 55% 50% at 62% 92%, rgb(43 123 239 / 0.12) 0%, transparent 74%);
	background-repeat: no-repeat;
	background-size: 100% 100%;
}

/* ── 16 · noise — film grain, an inline SVG turbulence ────────────────────── */
.pattern-noise::before {
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
	background-size: 140px 140px;
	/* No blend mode: overlay vanishes on a near-black surface, which is exactly
	   where grain is most wanted. Plain alpha reads on both. */
	opacity: 0.22;
}

/* =============================================================================
   PATTERNS · PART 2
   Size is not just scale. A 12px grid wants a hairline; a 72px grid wants a
   heavier, more confident line or it looks like dust. So --p-line rides along
   with --p-size, and the size modifiers set both.
   ========================================================================== */

.pattern { --p-line: 1px; }

.pattern-xs { --p-size: 12px; --p-line: 1px; --p-opacity: 0.8; }
.pattern-sm { --p-size: 18px; --p-line: 1px; }
.pattern-lg { --p-size: 44px; --p-line: 1.5px; }
.pattern-xl { --p-size: 72px; --p-line: 2px; --p-opacity: 1.15; }

/* Line-style modifiers — they change the CHARACTER of the rule, not its size. */
.pattern-hairline { --p-line: 0.5px; --p-opacity: 0.85; }
.pattern-heavy    { --p-line: 2px; }
.pattern-dashed::before { mask-image: repeating-linear-gradient(to right, #000 0 6px, transparent 6px 12px); }

/* The grid family, redrawn against --p-line so every size stays crisp. */
.pattern-grid::before {
	background-image:
		linear-gradient(to right, var(--p-ink) var(--p-line), transparent var(--p-line)),
		linear-gradient(to bottom, var(--p-ink) var(--p-line), transparent var(--p-line));
	background-size: var(--p-size) var(--p-size);
}

/* Vertical-only and horizontal-only rules — for tables, timelines, tickers. */
.pattern-cols::before {
	background-image: linear-gradient(to right, var(--p-ink) var(--p-line), transparent var(--p-line));
	background-size: var(--p-size) 100%;
}

.pattern-rows::before {
	background-image: linear-gradient(to bottom, var(--p-ink) var(--p-line), transparent var(--p-line));
	background-size: 100% var(--p-size);
}

/* Scanline, three densities. fine = 3px pitch (a real CRT), coarse = 12px
   (a stylised one). The default sits between. */
.pattern-scanline-fine::before {
	background-image: repeating-linear-gradient(to bottom, var(--p-ink) 0 1px, transparent 1px 3px);
}

.pattern-scanline-coarse::before {
	background-image: repeating-linear-gradient(to bottom, var(--p-ink-2) 0 2px, transparent 2px 12px);
}

/* 17 · isometric — a 30° grid. Projects, architecture-ish pages. */
.pattern-iso::before {
	background-image:
		repeating-linear-gradient(30deg, var(--p-ink) 0 var(--p-line), transparent var(--p-line) var(--p-size)),
		repeating-linear-gradient(-30deg, var(--p-ink) 0 var(--p-line), transparent var(--p-line) var(--p-size)),
		repeating-linear-gradient(to bottom, var(--p-ink) 0 var(--p-line), transparent var(--p-line) var(--p-size));
}

/* 18 · plus — surveyor's crosses at each intersection. Quieter than a grid,
   and the best texture under long-form text. */
.pattern-plus::before {
	background-image:
		linear-gradient(to right, transparent calc(50% - 3px), var(--p-ink-2) calc(50% - 3px), var(--p-ink-2) calc(50% + 3px), transparent calc(50% + 3px)),
		linear-gradient(to bottom, transparent calc(50% - 3px), var(--p-ink-2) calc(50% - 3px), var(--p-ink-2) calc(50% + 3px), transparent calc(50% + 3px));
	background-size: var(--p-size) var(--p-size);
	mask-image:
		linear-gradient(to right, #000 var(--p-line), transparent var(--p-line)),
		linear-gradient(to bottom, #000 var(--p-line), transparent var(--p-line));
	mask-size: var(--p-size) var(--p-size);
	mask-composite: add;
	-webkit-mask-composite: source-over;
}

/* 19 · stripes — 45° bands. The "work in progress" texture; use it for
   drafts, skeletons and empty states. */
.pattern-stripes::before {
	background-image: repeating-linear-gradient(
		45deg,
		var(--p-ink) 0 calc(var(--p-size) / 4),
		transparent calc(var(--p-size) / 4) calc(var(--p-size) / 2)
	);
}

/* 20 · checker — for transparency, image placeholders, "no artwork yet". */
.pattern-checker::before {
	background-image:
		linear-gradient(45deg, var(--p-ink) 25%, transparent 25% 75%, var(--p-ink) 75%),
		linear-gradient(45deg, var(--p-ink) 25%, transparent 25% 75%, var(--p-ink) 75%);
	background-size: var(--p-size) var(--p-size);
	background-position: 0 0, calc(var(--p-size) / 2) calc(var(--p-size) / 2);
}

/* 21 · topo — contour lines. Travel pages, trip headers, maps. */
.pattern-topo::before {
	background-image:
		repeating-radial-gradient(ellipse 60% 45% at 22% 30%, var(--p-ink) 0 var(--p-line), transparent var(--p-line) calc(var(--p-size) * 0.8)),
		repeating-radial-gradient(ellipse 55% 50% at 78% 72%, var(--p-ink) 0 var(--p-line), transparent var(--p-line) calc(var(--p-size) * 0.9));
	background-repeat: no-repeat;
	background-size: 100% 100%;
}

/* 22 · circuit — right-angled traces with pads. Build logs, code pages. */
.pattern-circuit::before {
	background-image:
		linear-gradient(to right, var(--p-ink) var(--p-line), transparent var(--p-line)),
		linear-gradient(to bottom, var(--p-ink) var(--p-line), transparent var(--p-line)),
		radial-gradient(var(--p-ink-2) 2px, transparent 2.5px);
	background-size:
		calc(var(--p-size) * 2) calc(var(--p-size) * 2),
		calc(var(--p-size) * 2) calc(var(--p-size) * 2),
		calc(var(--p-size) * 2) calc(var(--p-size) * 2);
	mask-image: linear-gradient(115deg, #000 20%, transparent 78%);
}

/* 23 · equaliser — bars of varying height along the bottom. Audio, podcast,
   the newsletter block. */
.pattern-eq::before {
	background-image:
		repeating-linear-gradient(to right, var(--p-ink-2) 0 calc(var(--p-size) / 5), transparent calc(var(--p-size) / 5) calc(var(--p-size) / 2.2));
	background-size: 100% 46%;
	background-position: 0 100%;
	background-repeat: repeat-x;
	mask-image: linear-gradient(to top, #000 30%, transparent 100%);
}

/* 24 · sprocket — film perforations along the top and bottom edges. */
.pattern-sprocket::before {
	background-image:
		repeating-linear-gradient(to right, var(--p-ink-2) 0 calc(var(--p-size) / 3), transparent calc(var(--p-size) / 3) calc(var(--p-size) / 1.4)),
		repeating-linear-gradient(to right, var(--p-ink-2) 0 calc(var(--p-size) / 3), transparent calc(var(--p-size) / 3) calc(var(--p-size) / 1.4));
	background-size: 100% calc(var(--p-size) / 2.6), 100% calc(var(--p-size) / 2.6);
	background-position: 0 0, 0 100%;
	background-repeat: repeat-x;
}

/* 25 · matrix — a dot grid with a heavier dot every fifth. Reads as a screen
   rather than as paper. */
.pattern-matrix::before {
	background-image:
		radial-gradient(var(--p-ink) 1px, transparent 1.2px),
		radial-gradient(var(--p-ink-2) 1.6px, transparent 1.8px);
	background-size: var(--p-size) var(--p-size), calc(var(--p-size) * 5) calc(var(--p-size) * 5);
}

/* 26 · spotlight — a single soft pool of light, following --p-x/--p-y. Set
   those from JS for a cursor-tracking hero, or leave them for a static wash. */
.pattern-spotlight::before {
	background-image: radial-gradient(
		circle 42% at var(--p-x, 50%) var(--p-y, 22%),
		color-mix(in srgb, var(--accent) 14%, transparent),
		transparent 70%
	);
	background-repeat: no-repeat;
	background-size: 100% 100%;
}

/* 27 · ruler — measurement ticks with a heavier mark every fifth. Timelines,
   changelogs, the resume. */
.pattern-ruler::before {
	background-image:
		repeating-linear-gradient(to right, var(--p-ink) 0 var(--p-line), transparent var(--p-line) var(--p-size)),
		repeating-linear-gradient(to right, var(--p-ink-2) 0 var(--p-line), transparent var(--p-line) calc(var(--p-size) * 5));
	background-size: 100% 8px, 100% 16px;
	background-position: 0 0, 0 0;
	background-repeat: repeat-x;
}

/* 28 · corners — tick marks at the four corners only. The lightest possible
   "this is a frame" signal, for cards that shouldn't shout. */
.pattern-corners::before {
	background-image:
		linear-gradient(to right, var(--p-ink-2) 0 var(--p-size), transparent var(--p-size)),
		linear-gradient(to bottom, var(--p-ink-2) 0 var(--p-size), transparent var(--p-size)),
		linear-gradient(to left, var(--p-ink-2) 0 var(--p-size), transparent var(--p-size)),
		linear-gradient(to top, var(--p-ink-2) 0 var(--p-size), transparent var(--p-size));
	background-size: 100% var(--p-line), var(--p-line) 100%, 100% var(--p-line), var(--p-line) 100%;
	background-position: 0 0, 0 0, 100% 100%, 100% 100%;
	background-repeat: no-repeat;
}

/* ── Platform accents ────────────────────────────────────────────────────
   The signature devices, re-cut for the places the channel actually lives.
   Each is a small overlay, not a full texture. */

/* Instagram: the gradient ring, as a border only. */
.ring-ig {
	position: relative;
	border-radius: var(--radius-full);
	padding: 3px;
	background: conic-gradient(from 200deg, #f9a03f, #f04e2e, #d92d4e, #a12ea6, #f9a03f);
}

.ring-ig > * { border-radius: inherit; display: block; background: var(--bg-canvas); padding: 2px; }

/* A live ring — the same idea in the house accent, for "streaming now". */
.ring-live {
	padding: 3px;
	border-radius: var(--radius-full);
	background: conic-gradient(from 0deg, var(--accent), color-mix(in srgb, var(--accent) 25%, transparent), var(--accent));
	animation: fx-spin 3.2s var(--ease-linear) infinite;
}

.ring-live > * { animation: fx-spin 3.2s var(--ease-linear) infinite reverse; }

/* ── The signal dot ────────────────────────────────────────────────────────
   Signature device #1. Pattern file because it is pure ornament-with-meaning,
   and because every component that shows "live" reuses exactly this. */

.dot {
	display: inline-block;
	width: var(--dot-md);
	height: var(--dot-md);
	border-radius: var(--radius-full);
	background: var(--accent);
	flex: none;
}

.dot-sm { width: var(--dot-sm); height: var(--dot-sm); }
.dot-lg { width: var(--dot-lg); height: var(--dot-lg); }
.dot-live { animation: fx-pulse 1.6s var(--ease-inout) infinite; }
.dot-muted { background: var(--fg-faint); }     /* 28 CSS-only background patterns        */
/* =============================================================================
   08 · ACCESSIBILITY & UTILITY
   Focus, skip links, screen-reader text, forced-colours, print. The parts of
   the system that are non-negotiable regardless of what a component looks like.
   ========================================================================== */

/* ── Focus ─────────────────────────────────────────────────────────────────
   Visible on every interactive element, in both themes, on any background.
   Two-tone: an inner halo in the opposite value, an outer ring in the accent.
   Mouse users don't see it; keyboard users always do. */

:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
	outline: var(--border-2) solid var(--focus-ring);
	outline-offset: var(--border-2);
	border-radius: var(--radius-sm);
}

/* For elements sitting on media or an inverse surface, the halo carries it. */
.focus-halo:focus-visible {
	outline: none;
	box-shadow: var(--ring-focus);
}

/* Never remove the outline without replacing it. */
:focus:not(:focus-visible) {
	outline: none;
}

/* ── Skip link ─────────────────────────────────────────────────────────────
   Off-screen until focused, then a real, styled control at the top-left. */

.skip-link {
	position: absolute;
	top: var(--space-2);
	left: var(--space-2);
	z-index: var(--z-max);
	transform: translateY(-200%);
	background: var(--bg-inverse);
	color: var(--fg-on-inverse);
	padding: var(--space-3) var(--space-4);
	border-radius: var(--radius-control);
	font-family: var(--font-slate);
	font-size: var(--text-xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	text-decoration: none;
	transition: transform var(--dur-2) var(--ease-out);
}

.skip-link:focus-visible {
	transform: translateY(0);
}

/* ── Screen-reader only ────────────────────────────────────────────────── */

.sr-only {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

.sr-only-focusable:not(:focus, :focus-within) {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

/* ── Touch targets ─────────────────────────────────────────────────────────
   Anything smaller than 44px gets an invisible expanded hit area rather than
   being made visually bigger. */

.tap-target {
	position: relative;
}

.tap-target::after {
	content: '';
	position: absolute;
	inset: 50%;
	width: var(--tap-min);
	height: var(--tap-min);
	transform: translate(-50%, -50%);
}

/* ── Forced colours (Windows high contrast) ────────────────────────────────
   Patterns and decorative layers disappear; structure survives because it is
   built from real borders. */

@media (forced-colors: active) {
	.pattern::before,
	.canvas__scrim::after {
		display: none;
	}

	.surface,
	.surface-raised,
	.surface-sunken {
		border: 1px solid CanvasText;
	}

	:where(a, button, input, select, textarea, summary):focus-visible {
		outline: 2px solid Highlight;
	}
}

/* ── Utility ───────────────────────────────────────────────────────────────
   Deliberately tiny. This is a foundation, not a utility framework — Tailwind
   covers the rest. Only things the system itself depends on live here. */

.hidden      { display: none !important; }
.relative    { position: relative; }
.overflow-hidden { overflow: hidden; }
.no-scrollbar { scrollbar-width: none; }
.no-scrollbar::-webkit-scrollbar { display: none; }

/* Horizontal scrollers (carousels) — snap, momentum, no visible bar. */
.scroller {
	display: flex;
	gap: var(--space-4);
	overflow-x: auto;
	overscroll-behavior-x: contain;   /* never steal the page's vertical scroll */
	scroll-snap-type: x mandatory;
	-webkit-overflow-scrolling: touch;
	scrollbar-width: none;
	padding-block: var(--space-1);
}

.scroller::-webkit-scrollbar { display: none; }
.scroller > * { scroll-snap-align: start; flex: none; }

/* ── Print ─────────────────────────────────────────────────────────────────
   The resume and any article should print cleanly: no chrome, no textures,
   ink on paper, URLs spelled out. */

@media print {
	:root {
		--bg-canvas: #fff;
		--bg-surface: #fff;
		--fg-default: #000;
		--fg-muted: #333;
		--line-default: #999;
	}

	body {
		background: #fff;
		color: #000;
	}

	.pattern::before,
	nav,
	.no-print {
		display: none !important;
	}

	a[href^='http']::after {
		content: ' (' attr(href) ')';
		font-family: var(--font-slate);
		font-size: 0.8em;
		color: #555;
	}

	.surface,
	.surface-raised {
		box-shadow: none;
		border: 1px solid #999;
		break-inside: avoid;
	}

	h1, h2, h3 { break-after: avoid; }
	img, figure, table { break-inside: avoid; }
}        /* focus, sr-only, forced-colours, print  */
/* =============================================================================
   09 · LOGO & BRAND MARKS
   One idea: the record light, sitting where the tittle of the "i" belongs.

   THE TITTLE. The dot over a lowercase i or j is called a tittle. The whole
   identity is: that tittle is the REC light. Which means the mark is not
   "wordmark plus a dot" — the dot is already part of the word, and it just
   happens to be red and alive.

   HOW IT'S BUILT. You can't recolour a font's own tittle, so the "i" is set
   with the dotless Turkish ı (U+0131) and the tittle is drawn as an element
   above it. Every size, weight and colour of the mark derives from that one
   arrangement, so the dot never drifts.

     <span class="logo">
       Swarn<span class="logo__i">ı<i class="logo__tittle"></i></span>l
     </span>
   ========================================================================== */

.logo {
	--logo-size: 1.5rem;
	--logo-dot: 0.15em;           /* tittle diameter, relative to the type size */
	--logo-lift: 0.72em;         /* baseline → underside of the tittle. Space
	                                Grotesk's x-height is 0.52em, so this leaves
	                                a real tittle gap rather than floating. */
	--logo-nudge: 0em;           /* the tittle is dead centre over the stem, always */
	--logo-color: var(--fg-default);
	--logo-dot-color: var(--accent);

	display: inline-flex;
	align-items: baseline;
	font-family: var(--font-display);
	font-weight: var(--weight-bold);
	font-size: var(--logo-size);
	line-height: 1;
	letter-spacing: -0.045em;
	color: var(--logo-color);
	white-space: nowrap;
	user-select: none;
}

/* The dotless i and its replacement tittle. */
.logo__i {
	position: relative;
	display: inline-block;
	font-style: normal;
}

.logo__tittle {
	position: absolute;
	left: calc(50% + var(--logo-nudge));
	bottom: var(--logo-lift);
	width: var(--logo-dot);
	height: var(--logo-dot);
	transform: translateX(-50%);
	border-radius: var(--radius-full);
	background: var(--logo-dot-color);
	font-style: normal;
}

/* Live state — the tittle breathes. Used only while something is actually
   recording or streaming, never as decoration. */
.logo-live .logo__tittle { animation: fx-pulse 1.6s var(--ease-inout) infinite; }

/* ── Sizes ───────────────────────────────────────────────────────────────
   A closed ladder, like every other ladder in this system. */

.logo-xs { --logo-size: 1rem; }
.logo-sm { --logo-size: 1.25rem; }
.logo-md { --logo-size: 1.5rem; }
.logo-lg { --logo-size: 2.25rem; }
.logo-xl { --logo-size: 3.5rem; }
.logo-2xl { --logo-size: clamp(3rem, 1.6rem + 6vw, 6rem); }

/* ── Colour variants ─────────────────────────────────────────────────────── */

.logo-inverse { --logo-color: var(--fg-on-inverse); }
.logo-mono    { --logo-dot-color: currentColor; }   /* single-colour print, embroidery */
.logo-ghost   { --logo-color: var(--fg-muted); --logo-dot-color: var(--fg-faint); }

/* ── Lockups ─────────────────────────────────────────────────────────────
   The wordmark rarely appears alone. These are the only three arrangements. */

/* 1 · stacked — wordmark over the tagline. Hero, end cards, print. */
.logo-stack {
	display: inline-flex;
	flex-direction: column;
	align-items: center;
	gap: var(--space-2);
	text-align: center;
}

.logo-stack .logo__tag {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: 0.32em;
	text-transform: uppercase;
	color: var(--fg-subtle);
}

/* 2 · inline — wordmark, hairline, tagline. Nav, footer, email header. */
.logo-inline {
	display: inline-flex;
	align-items: center;
	gap: var(--space-3);
}

.logo-inline .logo__rule {
	width: var(--space-6);
	height: var(--border-hair);
	background: var(--line-default);
	flex: none;
}

/* 3 · badge — the mark in a container, for avatars and app icons. */
.logo-badge {
	display: grid;
	place-items: center;
	width: 3rem;
	height: 3rem;
	border-radius: var(--radius-lg);
	background: var(--bg-inverse);
	color: var(--fg-on-inverse);
}

.logo-badge-round { border-radius: var(--radius-full); }

/* ── Short forms ─────────────────────────────────────────────────────────
   As the space shrinks the mark degrades in a fixed order:

       Swarnıl   →   Sıl   →   S•   →   •

   There is no "Sı": a lone dotless ı is just a stick, and it reads as an l.
   The compact mark is a capital S with the tittle moved to its top right —
   the same dot, relocated, which is why it still reads as the same logo. */

.logo-short { letter-spacing: -0.05em; }

/* S + dot — the compact mark, and the favicon.

   THE DOT SITS AT 1 O'CLOCK. Not 12 — that reads as a tittle on the wrong
   letter. Not 2 — it drifts off the shoulder and looks detached. One o'clock
   is 30 degrees clockwise from vertical, orbiting at 0.62em from the optical
   centre: far enough to be its own mark, close enough to belong to the S.

   Both numbers are tokens, so the relationship is tunable in one place and
   identical at every size.
   ------------------------------------------------------------------------ */

.logo-s {
	--s-clock: 30deg;      /* 1 o'clock = 360 / 12 */
	--s-orbit: 0.5em;      /* not far, not close: the dot clears the S's cap
	                          by about a third of its own width */
	position: relative;
	display: inline-block;
	font-family: var(--font-display);
	font-weight: var(--weight-bold);
	font-size: var(--logo-size, 1.5rem);
	line-height: 1;
	letter-spacing: -0.05em;
	color: var(--logo-color, var(--fg-default));
	padding: 0.1em 0.44em 0.1em 0.04em;
}

.logo-s::after {
	content: '';
	position: absolute;
	top: 50%;
	left: 47%;
	width: var(--logo-dot, 0.2em);
	height: var(--logo-dot, 0.2em);
	border-radius: var(--radius-full);
	background: var(--logo-dot-color, var(--accent));
	/* Polar placement: x = sin(t) * r, y = -cos(t) * r, then centre the dot on
	   that point. Static fallback first, for anything without trig support. */
	translate: 0.15em -0.53em;
	translate: calc(sin(var(--s-clock)) * var(--s-orbit) - var(--logo-dot, 0.2em) / 2)
	           calc(cos(var(--s-clock)) * var(--s-orbit) * -1 - var(--logo-dot, 0.2em) / 2);
}

.logo-s-live::after { animation: fx-pulse 1.6s var(--ease-inout) infinite; }

/* ── The recorder mark ───────────────────────────────────────────────────
   No brackets, no box: a square canvas with the dot in the top-right corner,
   exactly where a camera puts its REC light. This is the app icon, the
   watermark and the merch mark. */

/* The dot alone — favicon, app icon, watermark, loading indicator. It is
   legible at 16px, which nothing else in the system is. */
.logo-dot {
	display: inline-block;
	width: 1em;
	height: 1em;
	border-radius: var(--radius-full);
	background: var(--accent);
	position: relative;
}

/* Haloed dot, for when the mark sits on a busy photo. */
.logo-dot-halo {
	box-shadow: 0 0 0 0.28em color-mix(in srgb, var(--accent) 22%, transparent),
	            0 0 0 0.56em color-mix(in srgb, var(--accent) 10%, transparent);
}

/* ── Favicon / app-icon sizes ────────────────────────────────────────────
   What ships as a file, and what the mark degrades to at that size:

     16 · 32     browser tab, bookmark      → dot alone, no frame
     48 · 64     Windows tile, pinned site  → dot alone
     180         apple-touch-icon           → dot in a rounded square
     192 · 512   PWA manifest               → dot in a rounded square
     1024        store listing              → dot + Sı, generous padding

   Preview them with .favicon, which pins the real pixel size. */

.favicon {
	display: inline-grid;
	place-items: center;
	background: var(--bg-inverse);
	color: var(--fg-on-inverse);
	border-radius: 22.5%;         /* the iOS "squircle" ratio, close enough */
	overflow: hidden;
	flex: none;
}

.favicon-16  { width: 16px;  height: 16px;  border-radius: 3px; }
.favicon-32  { width: 32px;  height: 32px;  border-radius: 6px; }
.favicon-48  { width: 48px;  height: 48px;  border-radius: 9px; }
.favicon-64  { width: 64px;  height: 64px;  border-radius: 12px; }
.favicon-180 { width: 180px; height: 180px; }
.favicon-512 { width: 180px; height: 180px; }   /* previewed at 180 */

/* Inside a favicon the mark is a fixed fraction of the tile, so every size is
   the same drawing. At 16px the S reads as a shape rather than a letter, and
   the dot at 1 o'clock is what makes it identifiable. */
.favicon > .logo-s {
	/* No padding inside a favicon: the box collapses to the glyph, the grid
	   centres THAT, and the dot orbits out of it. Centring the lockup instead
	   would push the S off-centre, and the S is what people recognise. */
	padding: 0;
	font-size: 72%;
	--logo-dot: 0.24em;
	--s-orbit: 0.5em;
	--logo-color: currentColor;   /* inherits the tile's fg, so it inverts free */
}

/* Small tiles need a proportionally bigger, blunter mark or it turns to mush. */
.favicon-16 > .logo-s { font-size: 84%; --logo-dot: 0.32em; --s-orbit: 0.46em; }
.favicon-32 > .logo-s { font-size: 78%; --logo-dot: 0.28em; --s-orbit: 0.48em; }
.favicon-48 > .logo-s { font-size: 76%; }
.favicon > .logo-dot { width: 38%; height: 38%; }

/* ── Responsive behaviour ────────────────────────────────────────────────
   In the nav the mark drops to its short form on small screens. This is the
   only place the system changes the logo automatically. */

/* Markup carries all three forms; CSS chooses. No JS, no layout shift, and the
   accessible name stays the same because only the visual form changes.

     <a class="logo-responsive" aria-label="Swarnil — home">
       <span class="logo__full">…Swarnıl…</span>
       <span class="logo__abbr">…Sıl…</span>
       <span class="logo__mark">…S•…</span>
     </a>  */

.logo-responsive { display: inline-flex; align-items: center; }
.logo-responsive .logo__full { display: inline-flex; }
.logo-responsive .logo__abbr,
.logo-responsive .logo__mark { display: none; }

@media (max-width: 48rem) {
	.logo-responsive .logo__full { display: none; }
	.logo-responsive .logo__abbr { display: inline-flex; }
}

@media (max-width: 26rem) {
	.logo-responsive .logo__abbr { display: none; }
	.logo-responsive .logo__mark { display: inline-flex; }
}

/* Fluid size, for hero and end-card lockups that shouldn't step. */
.logo-fluid { --logo-size: clamp(1.5rem, 0.9rem + 2.6vw, 4rem); }

/* ── Clear space ─────────────────────────────────────────────────────────
   Minimum breathing room around the mark = the tittle's diameter, on all four
   sides. .logo-clear draws it for the spec sheet. */

.logo-clear {
	position: relative;
	padding: calc(var(--logo-size, 1.5rem) * 0.22 * 2);
}

.logo-clear::before {
	content: '';
	position: absolute;
	inset: 0;
	border: var(--border-hair) dashed var(--line-default);
	border-radius: var(--radius-sm);
	pointer-events: none;
}

/* ── Misuse (documented so it can be pointed at) ─────────────────────────
   These classes exist to be shown crossed out on the brand page. Never ship
   them. */

.logo-dont-stretch { transform: scaleX(1.35); }
.logo-dont-rotate  { transform: rotate(-8deg); }
.logo-dont-recolor { --logo-dot-color: var(--info-line); }
.logo-dont-outline { -webkit-text-stroke: 1px currentColor; color: transparent; }

/* The bar keeps the full wordmark at every width — an abbreviated mark in a
   bar that has room for the word is a puzzle for no reason. The short forms
   stay available for places that genuinely have no room (a favicon, an
   avatar, a watermark). */
.nav-bar .logo-responsive .logo__full { display: inline-flex; }
.nav-bar .logo-responsive .logo__abbr,
.nav-bar .logo-responsive .logo__mark { display: none; }        /* the tittle mark, lockups, favicons     */
/* =============================================================================
   10 · ICONS
   The icon system: one grid, one stroke, four variants. Icons are inline SVG
   from a sprite — no icon font, no external requests, and they inherit colour
   from the text they sit in.

   THE GRID
     24 × 24 viewBox, 2px padding → 20px live area
     1.5px stroke, round caps, round joins
     corner radius 2px, never sharper
     optical centring beats geometric centring — nudge, don't calculate

   A 16px icon is the 24px icon scaled, NOT redrawn. If a glyph dies at 16px
   it is too detailed; simplify the drawing rather than adding a second size.
   ========================================================================== */

:root {
	--icon-stroke: 1.5;
	--icon-stroke-thin: 1.25;
	--icon-stroke-bold: 2;
}

.icon {
	display: inline-block;
	width: var(--icon-md);
	height: var(--icon-md);
	flex: none;
	vertical-align: -0.125em;   /* sits on the text baseline, not below it */
	fill: none;
	stroke: currentColor;
	stroke-width: var(--icon-stroke);
	stroke-linecap: round;
	stroke-linejoin: round;
	color: inherit;
}

/* ── Sizes — the same ladder as everything else ─────────────────────────── */

.icon-xs { width: var(--icon-xs); height: var(--icon-xs); stroke-width: var(--icon-stroke-bold); }
.icon-sm { width: var(--icon-sm); height: var(--icon-sm); }
.icon-md { width: var(--icon-md); height: var(--icon-md); }
.icon-lg { width: var(--icon-lg); height: var(--icon-lg); }
.icon-xl { width: var(--icon-xl); height: var(--icon-xl); stroke-width: var(--icon-stroke-thin); }

/* Small icons need a heavier stroke to hold; large icons need a lighter one.
   That's optics, not maths — hence the overrides above. */

/* ── Variants ────────────────────────────────────────────────────────────
   line     the default — outline only
   solid    filled, for active/selected states
   duotone  outline plus a 12% fill of the same colour, for feature blocks
   ghost    the outline at 45%, for disabled and decorative use */

.icon-line   { fill: none; stroke: currentColor; }
.icon-solid  { fill: currentColor; stroke: none; }
.icon-duo    { fill: color-mix(in srgb, currentColor 14%, transparent); stroke: currentColor; }
.icon-ghost  { opacity: 0.45; }

/* Accent an icon without touching its markup. */
.icon-accent { color: var(--accent); }
.icon-muted  { color: var(--fg-muted); }

/* ── Containers ──────────────────────────────────────────────────────────
   An icon that needs to be tappable, or that carries a status. */

.icon-box {
	display: inline-grid;
	place-items: center;
	width: var(--control-md);
	height: var(--control-md);
	border-radius: var(--radius-control);
	background: var(--bg-sunken);
	border: var(--border-hair) solid var(--line-default);
	color: var(--fg-muted);
	transition: var(--t-colors);
}

.icon-box:hover { color: var(--fg-default); border-color: var(--line-strong); }
.icon-box-round { border-radius: var(--radius-full); }
.icon-box-sm { width: var(--control-sm); height: var(--control-sm); }
.icon-box-lg { width: var(--control-lg); height: var(--control-lg); }

.icon-box-accent {
	background: var(--accent-soft);
	border-color: transparent;
	color: var(--accent-soft-fg);
}

.icon-box-inverse {
	background: var(--bg-inverse);
	border-color: transparent;
	color: var(--fg-on-inverse);
}

/* Feature icon — the big one at the top of a card or section. */
.icon-feature {
	display: grid;
	place-items: center;
	width: 3rem;
	height: 3rem;
	border-radius: var(--radius-lg);
	background: var(--accent-soft);
	color: var(--accent-soft-fg);
}

/* ── Badged icon — a count or a status dot on the corner ─────────────────── */

.icon-badged { position: relative; display: inline-flex; }

.icon-badged__dot {
	position: absolute;
	top: -2px;
	right: -2px;
	width: var(--dot-md);
	height: var(--dot-md);
	border-radius: var(--radius-full);
	background: var(--accent);
	box-shadow: 0 0 0 var(--border-2) var(--bg-canvas);
}

.icon-badged__n {
	position: absolute;
	top: -6px;
	right: -8px;
	min-width: 1rem;
	height: 1rem;
	padding-inline: 3px;
	display: grid;
	place-items: center;
	border-radius: var(--radius-pill);
	background: var(--accent);
	color: var(--fg-on-accent);
	font-family: var(--font-slate);
	font-size: 10px;
	line-height: 1;
	box-shadow: 0 0 0 var(--border-2) var(--bg-canvas);
}

/* ── Motion ──────────────────────────────────────────────────────────────
   Icons animate on the parent's state, never on their own. Three behaviours,
   and that's the whole vocabulary. */

.icon-spin { animation: fx-spin 700ms var(--ease-linear) infinite; }

@media (hover: hover) and (pointer: fine) {
	.icon-nudge { transition: var(--t-transform); }
	*:hover > .icon-nudge { transform: translate3d(2px, 0, 0); }

	.icon-lift { transition: var(--t-transform); }
	*:hover > .icon-lift { transform: translate3d(0, -2px, 0); }
}

/* ── Sprite ──────────────────────────────────────────────────────────────
   Icons live in one inline <svg> sprite at the top of <body>, referenced with
   <svg class="icon"><use href="#i-play"></use></svg>. One request, cacheable,
   and every glyph inherits currentColor.

   The sprite element itself must never be visible or focusable. */

.icon-sprite {
	position: absolute;
	width: 0;
	height: 0;
	overflow: hidden;
	visibility: hidden;
}

/* ── Icon + label ────────────────────────────────────────────────────────
   The pairing rule: icon first, 8px gap, label optically aligned to the
   icon's centre — never its box. */

.icon-label {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
}

/* An icon with no visible label MUST carry an aria-label on its control, and
   the svg itself must be aria-hidden. This class is a reminder in the markup. */
.icon-only > .icon { pointer-events: none; }        /* icon grid, sizes, variants, sprite     */
/* =============================================================================
   11 · SHAPE
   The geometry vocabulary: the shapes the system is allowed to draw, and the
   maths behind them.

   THE RULE OF NESTED RADII. When one rounded box sits inside another, the
   inner radius must equal the outer radius minus the gap, or the corners look
   wrong — concentric, not parallel:

       inner = outer − padding

   That's what --radius-nested does. Set --nest-pad on the parent and the child
   picks up the right corner automatically.
   ========================================================================== */

:root {
	/* Optical constants used below. */
	--phi: 1.6180339887;
	--sqrt2: 1.4142135624;
	--squircle: 22.5%;   /* the iOS app-icon superellipse, approximated */
}

/* ── Nested radius ─────────────────────────────────────────────────────── */

.nest {
	--nest-pad: var(--space-3);
	padding: var(--nest-pad);
	border-radius: var(--radius-card);
}

.nest > .nest__inner {
	border-radius: calc(var(--radius-card) - var(--nest-pad));
}

/* ── Primitives ───────────────────────────────────────────────────────────
   Every one of these is a single element. No SVG, no images. */

.shape {
	--shape-size: 3rem;
	--shape-fill: var(--fg-default);
	width: var(--shape-size);
	height: var(--shape-size);
	background: var(--shape-fill);
	flex: none;
}

.shape-dot     { border-radius: var(--radius-full); }
.shape-square  { border-radius: var(--radius-none); }
.shape-rounded { border-radius: var(--radius-md); }
.shape-squircle { border-radius: var(--squircle); }
.shape-pill    { border-radius: var(--radius-pill); width: calc(var(--shape-size) * 2); }

/* Ring — a dot that is only an edge. The "not yet watched" state. */
.shape-ring {
	background: none;
	border: var(--border-3) solid var(--shape-fill);
	border-radius: var(--radius-full);
}

/* Arc — a ring with a gap; the progress primitive before any JS. */
.shape-arc {
	background: none;
	border: var(--border-3) solid var(--shape-fill);
	border-right-color: transparent;
	border-bottom-color: transparent;
	border-radius: var(--radius-full);
	transform: rotate(-45deg);
}

/* Triangle — the play glyph, built from borders so it scales with font size. */
.shape-play {
	width: 0;
	height: 0;
	background: none;
	border-left: calc(var(--shape-size) * 0.62) solid var(--shape-fill);
	border-top: calc(var(--shape-size) * 0.38) solid transparent;
	border-bottom: calc(var(--shape-size) * 0.38) solid transparent;
}

/* Chevron — navigation, accordions, breadcrumbs. */
.shape-chevron {
	width: calc(var(--shape-size) * 0.4);
	height: calc(var(--shape-size) * 0.4);
	background: none;
	border-right: var(--border-2) solid var(--shape-fill);
	border-bottom: var(--border-2) solid var(--shape-fill);
	transform: rotate(-45deg);
}

/* Diamond — the timeline node. */
.shape-diamond { border-radius: var(--radius-xs); transform: rotate(45deg); }

/* Ticket — a card with two bites taken out of its sides. Shop, courses. */
.shape-ticket {
	border-radius: var(--radius-md);
	mask-image:
		radial-gradient(circle at 0 50%, transparent 0 8px, #000 8px),
		radial-gradient(circle at 100% 50%, transparent 0 8px, #000 8px);
	mask-composite: intersect;
	-webkit-mask-composite: source-in;
}

/* Blob — the only organic shape allowed, used behind feature icons. Two of
   them at different rotations reads as hand-made; three reads as clip-art. */
.shape-blob {
	border-radius: 62% 38% 46% 54% / 54% 46%;
}

.shape-blob-2 { border-radius: 38% 62% 58% 42% / 44% 58% 42% 56%; }

/* Golden rectangle — proportion demos, hero splits, image crops. */
.shape-golden { width: calc(var(--shape-size) * var(--phi)); }

/* ── Arrows & pointers ───────────────────────────────────────────────────── */

.shape-caret {
	width: 0;
	height: 0;
	background: none;
	border-left: calc(var(--shape-size) * 0.22) solid transparent;
	border-right: calc(var(--shape-size) * 0.22) solid transparent;
	border-top: calc(var(--shape-size) * 0.22) solid var(--shape-fill);
}

/* Tooltip / popover tail. Rotate the parent's ::after into place. */
.shape-tail {
	width: calc(var(--shape-size) * 0.3);
	height: calc(var(--shape-size) * 0.3);
	transform: rotate(45deg);
	border-radius: 2px 0 0;
}

/* ── Dividers ────────────────────────────────────────────────────────────
   A rule is a shape too. Four ways to end a section. */

.div-fade {
	height: var(--border-hair);
	border: 0;
	background: linear-gradient(to right, transparent, var(--line-default) 12%, var(--line-default) 88%, transparent);
}

.div-dot {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: var(--space-3);
	color: var(--fg-faint);
}

.div-dot::before,
.div-dot::after {
	content: '';
	flex: 1;
	height: var(--border-hair);
	background: var(--line-default);
}

.div-notch {
	height: var(--border-hair);
	border: 0;
	background:
		linear-gradient(to right, var(--line-default), var(--line-default)) left / calc(50% - var(--space-4)) 100% no-repeat,
		linear-gradient(to right, var(--line-default), var(--line-default)) right / calc(50% - var(--space-4)) 100% no-repeat;
}

.div-ticks {
	height: 6px;
	border: 0;
	background: repeating-linear-gradient(to right, var(--line-default) 0 1px, transparent 1px var(--space-3));
}

/* ── Masks & clips ───────────────────────────────────────────────────────
   Used by media and by the reveal animations in 05-motion. */

.clip-wipe-up   { clip-path: inset(0 0 0 0); }
.clip-wipe-hide { clip-path: inset(0 0 100% 0); }
.clip-angle     { clip-path: polygon(0 0, 100% 0, 100% 92%, 0 100%); }
.clip-notch     { clip-path: polygon(0 0, calc(100% - 1.5rem) 0, 100% 1.5rem, 100% 100%, 0 100%); }

.mask-fade-b { mask-image: linear-gradient(to bottom, #000 60%, transparent); }
.mask-fade-r { mask-image: linear-gradient(to right, #000 60%, transparent); }
.mask-circle { mask-image: radial-gradient(circle at 50% 50%, #000 62%, transparent 72%); }

/* =============================================================================
   SHAPE · THE MATHS
   The numbers behind the geometry, as tokens — so a new shape is derived
   rather than eyeballed.
   ========================================================================== */

:root {
	/* Ratios. Everything rectangular in the system is one of these. */
	--r-square: 1;
	--r-golden: 1.6180339887;    /* φ — hero splits, article rails          */
	--r-silver: 1.4142135624;    /* √2 — print, A-series, the resume        */
	--r-bronze: 3.3027756377;    /* δ — ultra-wide banners                  */
	--r-third: 1.5;              /* 3:2 — photography                       */
	--r-quarter: 1.3333333333;   /* 4:3 — archive footage                   */
	--r-wide: 1.7777777778;      /* 16:9 — video, everything broadcast      */
	--r-cinema: 2.3333333333;    /* 21:9 — the webseries letterbox          */

	/* Angles. The system uses exactly five, and they are all derived. */
	--a-hatch: 45deg;            /* diagonal fills                          */
	--a-iso: 30deg;              /* isometric grids                         */
	--a-shear: 8deg;             /* the "in motion" skew                    */
	--a-blade: 60deg;            /* shutter blades — 360 / 6                */
	--a-ray: 108deg;             /* light shafts — the pentagon angle       */

	/* Circle constants, for arcs and rings built from conic gradients. */
	--turn-quarter: 0.25turn;
	--turn-third: 0.3333turn;
	--turn-golden: 0.618turn;

	/* Optical correction: a circle needs ~2% more area than a square to read
	   as the same size, and a triangle ~10% more. Applied by the primitives. */
	--optic-circle: 1.02;
	--optic-triangle: 1.1;
}

/* ── 2D polygons ─────────────────────────────────────────────────────────
   All clip-path, all one element, all scaling from --shape-size. Regular
   polygons are computed on the unit circle: point n = (50% + 50%·cos θ,
   50% + 50%·sin θ) with θ stepping by 360/n and starting at −90°. */

.shape-triangle {
	clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
	transform: scale(var(--optic-triangle));
}

.shape-triangle-right { clip-path: polygon(0 0, 100% 100%, 0 100%); }

.shape-diamond-4 { clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%); }

.shape-pentagon {
	clip-path: polygon(50% 0%, 100% 38.2%, 81% 100%, 19% 100%, 0% 38.2%);
}

.shape-hexagon {
	clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
}

/* Pointy-top hexagon — the one that tiles vertically. */
.shape-hexagon-v {
	clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
}

.shape-octagon {
	clip-path: polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%);
}

/* Five-pointed star, outer radius 50%, inner 19.1% (= 50%·sin18°/sin126°). */
.shape-star {
	clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%,
	                   50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
}

/* Chevron and arrow — navigation and "next" affordances at any size. */
.shape-chevron-solid { clip-path: polygon(0 0, 55% 0, 100% 50%, 55% 100%, 0 100%, 45% 50%); }
.shape-arrow { clip-path: polygon(0 35%, 62% 35%, 62% 12%, 100% 50%, 62% 88%, 62% 65%, 0 65%); }

/* Cross / plus — the surveyor mark, at shape scale. */
.shape-plus { clip-path: polygon(38% 0, 62% 0, 62% 38%, 100% 38%, 100% 62%, 62% 62%, 62% 100%, 38% 100%, 38% 62%, 0 62%, 0 38%, 38% 38%); }

/* Play triangle as a clip, for when the border trick can't be used. */
.shape-play-clip { clip-path: polygon(12% 0, 100% 50%, 12% 100%); }

/* ── Rings & arcs from conic gradients ───────────────────────────────────
   A progress ring with no SVG and no JS: set --value as a turn fraction. */

.shape-ring-progress {
	background: conic-gradient(var(--shape-fill) calc(var(--value, 0.62) * 1turn), var(--bg-muted) 0);
	border-radius: var(--radius-full);
	mask: radial-gradient(circle, transparent 58%, #000 59%);
}

.shape-donut {
	background: conic-gradient(from -90deg, var(--shape-fill) var(--turn-golden), var(--bg-muted) 0);
	border-radius: var(--radius-full);
	mask: radial-gradient(circle, transparent 46%, #000 47%);
}

/* ── Aspect + proportion helpers ─────────────────────────────────────────── */

.prop-golden  { aspect-ratio: var(--ratio-golden); }
.prop-root2   { aspect-ratio: 1.4142 / 1; }   /* A4 / print */
.prop-third   { aspect-ratio: 3 / 2; }
.prop-cinema  { aspect-ratio: var(--ratio-cinema); }       /* geometry: nested radii, primitives     */
/* =============================================================================
   12 · FRAMES & WINDOW CHROME
   Containers that say what a thing IS before you read it: a Mac window says
   "this is an app", a terminal says "this is a command", a viewfinder says
   "this is footage".

   All of them are CSS only, all of them wrap arbitrary content, and all of
   them use the same tokens as everything else — so they retheme for free.
   ========================================================================== */

/* ── Shared shell ────────────────────────────────────────────────────────── */

.win {
	--win-bar: 2.25rem;
	position: relative;
	display: flex;
	flex-direction: column;
	border-radius: var(--radius-lg);
	border: var(--border-hair) solid var(--line-default);
	background: var(--bg-surface);
	overflow: hidden;
	box-shadow: var(--shadow-3);
	isolation: isolate;
}

.win__bar {
	display: flex;
	align-items: center;
	gap: var(--space-3);
	height: var(--win-bar);
	padding-inline: var(--space-3);
	background: var(--bg-sunken);
	border-bottom: var(--border-hair) solid var(--line-subtle);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: 0.08em;
	color: var(--fg-subtle);
	flex: none;
}

.win__body {
	flex: 1;
	min-height: 0;
	overflow: hidden;
}

/* Traffic lights. Grey by default — they're chrome, not decoration. Add
   .win__dots-color when the window IS the subject of the shot. */
.win__dots { display: inline-flex; gap: 6px; flex: none; }

.win__dots > span {
	width: 10px;
	height: 10px;
	border-radius: var(--radius-full);
	background: var(--line-strong);
}

.win__dots-color > span:nth-child(1) { background: #ff5f57; }
.win__dots-color > span:nth-child(2) { background: #febc2e; }
.win__dots-color > span:nth-child(3) { background: #28c840; }

.win__title { flex: 1; text-align: center; }

/* ── 1 · macOS window ────────────────────────────────────────────────────── */

.win-mac { --win-bar: 2.5rem; border-radius: var(--radius-xl); }

.win-mac .win__bar {
	background: linear-gradient(to bottom,
		color-mix(in srgb, var(--bg-sunken) 70%, var(--bg-surface)),
		var(--bg-sunken));
}

/* ── 2 · VS Code / editor ────────────────────────────────────────────────
   Activity rail, tab strip, gutter line numbers. The gutter is a background
   gradient, not markup, so any <pre> drops straight in. */

.win-code {
	background: var(--ink-950);
	color: var(--ink-100);
	border-color: rgb(255 255 255 / 0.10);
	color-scheme: dark;
}

.win-code .win__bar {
	background: #16161f;
	border-bottom-color: rgb(255 255 255 / 0.08);
	color: rgb(255 255 255 / 0.55);
}

.win-code__tabs {
	display: flex;
	align-items: stretch;
	background: #101017;
	border-bottom: var(--border-hair) solid rgb(255 255 255 / 0.07);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
}

.win-code__tab {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	padding: var(--space-2) var(--space-4);
	color: rgb(255 255 255 / 0.45);
	border-right: var(--border-hair) solid rgb(255 255 255 / 0.07);
}

.win-code__tab[aria-selected='true'] {
	background: var(--ink-950);
	color: var(--ink-50);
	box-shadow: inset 0 2px 0 0 var(--accent);
}

.win-code__body {
	display: grid;
	grid-template-columns: 2.75rem 1fr;
	font-family: var(--font-slate);
	font-size: var(--text-xs);
	line-height: 1.7;
}

.win-code__gutter {
	padding: var(--space-3) var(--space-2);
	text-align: right;
	color: rgb(255 255 255 / 0.22);
	background: #0d0d14;
	border-right: var(--border-hair) solid rgb(255 255 255 / 0.06);
	user-select: none;
}

.win-code__lines {
	padding: var(--space-3) var(--space-4);
	overflow-x: auto;
	white-space: pre;
}

/* Minimal syntax colours — five roles, borrowed from the status hues so the
   palette stays closed. The four that follow are the same four hues re-used:
   markup and CSS need to name a tag, an attribute, a property and a selector,
   and inventing new colours for them would open a palette this system keeps
   deliberately shut. Punctuation recedes rather than colours. */
.tok-key { color: #ff8fa5; }
.tok-str { color: #6ee7ad; }
.tok-num { color: #f2d795; }
.tok-fn  { color: #93c2ff; }
.tok-com { color: rgb(255 255 255 / 0.32); font-style: italic; }
.tok-tag { color: #ff8fa5; }
.tok-attr { color: #f2d795; }
.tok-prop { color: #93c2ff; }
.tok-sel { color: #ffc59c; }
.tok-var { color: #c7b6ff; }
.tok-punc { color: rgb(255 255 255 / 0.45); }

/* ── 3 · terminal ────────────────────────────────────────────────────────── */

.win-term {
	background: var(--ink-1000);
	color: var(--ink-100);
	border-color: rgb(255 255 255 / 0.10);
	font-family: var(--font-slate);
	color-scheme: dark;
}

.win-term .win__bar { background: #0d0d14; color: rgb(255 255 255 / 0.5); border-bottom-color: rgb(255 255 255 / 0.07); }

.win-term__body {
	padding: var(--space-4);
	font-size: var(--text-xs);
	line-height: 1.75;
	white-space: pre-wrap;
}

.win-term__prompt::before {
	content: '❯ ';
	color: var(--accent);
}

/* Blinking block cursor. Stops under reduced-motion like everything else. */
.win-term__cursor {
	display: inline-block;
	width: 0.55em;
	height: 1em;
	background: var(--accent);
	vertical-align: -0.15em;
	animation: fxBlink 1.05s steps(1) infinite;
}

@keyframes fxBlink { 50% { opacity: 0; } }

/* ── 4 · browser ─────────────────────────────────────────────────────────── */

.win-browser .win__bar { gap: var(--space-2); }

.win-browser__url {
	flex: 1;
	display: flex;
	align-items: center;
	gap: var(--space-2);
	height: 1.5rem;
	padding-inline: var(--space-3);
	border-radius: var(--radius-pill);
	background: var(--bg-canvas);
	border: var(--border-hair) solid var(--line-subtle);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	color: var(--fg-subtle);
	overflow: hidden;
	white-space: nowrap;
}

/* ── 5 · phone ───────────────────────────────────────────────────────────
   For screenshots of the site on mobile, and for IG/Shorts mockups. */

.win-phone {
	--win-bar: 0;
	aspect-ratio: 9 / 19.5;
	border-radius: 2.25rem;
	border: 8px solid var(--ink-900);
	background: var(--bg-canvas);
	box-shadow: var(--shadow-4);
	max-width: 20rem;
}

.win-phone::before {
	content: '';
	position: absolute;
	top: 0.6rem;
	left: 50%;
	transform: translateX(-50%);
	width: 32%;
	height: 1.1rem;
	border-radius: var(--radius-pill);
	background: var(--ink-900);
	z-index: var(--z-raised);
}

/* ── 6 · camera shutter ──────────────────────────────────────────────────
   Six blades that close over a frame. The "photo taken" transition, and the
   loading state for a gallery. Purely decorative: it never blocks input. */

.shutter {
	position: relative;
	overflow: hidden;
	isolation: isolate;
}

.shutter__blades {
	position: absolute;
	inset: 0;
	z-index: var(--z-raised);
	pointer-events: none;
	display: grid;
	place-items: center;
}

.shutter__blades > span {
	position: absolute;
	width: 150%;
	height: 150%;
	background: var(--ink-1000);
	transform-origin: 50% 50%;
	clip-path: polygon(50% 50%, 100% 0, 100% 100%);
	transition: transform var(--dur-4) var(--ease-inout);
}

.shutter__blades > span:nth-child(1) { transform: rotate(0deg)   translateX(88%); }
.shutter__blades > span:nth-child(2) { transform: rotate(60deg)  translateX(88%); }
.shutter__blades > span:nth-child(3) { transform: rotate(120deg) translateX(88%); }
.shutter__blades > span:nth-child(4) { transform: rotate(180deg) translateX(88%); }
.shutter__blades > span:nth-child(5) { transform: rotate(240deg) translateX(88%); }
.shutter__blades > span:nth-child(6) { transform: rotate(300deg) translateX(88%); }

/* Closed state — add .is-closed to fire it. */
.shutter.is-closed .shutter__blades > span:nth-child(1) { transform: rotate(0deg)   translateX(0); }
.shutter.is-closed .shutter__blades > span:nth-child(2) { transform: rotate(60deg)  translateX(0); }
.shutter.is-closed .shutter__blades > span:nth-child(3) { transform: rotate(120deg) translateX(0); }
.shutter.is-closed .shutter__blades > span:nth-child(4) { transform: rotate(180deg) translateX(0); }
.shutter.is-closed .shutter__blades > span:nth-child(5) { transform: rotate(240deg) translateX(0); }
.shutter.is-closed .shutter__blades > span:nth-child(6) { transform: rotate(300deg) translateX(0); }

/* ── 7 · viewfinder ──────────────────────────────────────────────────────
   Moved here from the layout layer: it's a frame, not a layout primitive.
   Four corners, a rec bug, and optional grid lines / focus box.

   .frame in 06-layout.css remains as the plain two-corner bracket for cards;
   this is the full camera version. */

/* NOTE — .vf draws its top-left corner with ::before, and so does .pattern.
   Never put both on the same element: the two rules fight over one pseudo and
   you get a rounded box instead of a corner. Put the pattern on a child. */
.vf {
	--vf-color: var(--craft);
	--vf-size: 12%;
	--vf-weight: var(--border-2);
	--vf-inset: 4%;
	position: relative;
}

.vf::before,
.vf::after,
.vf > .vf__tr,
.vf > .vf__bl {
	content: '';
	position: absolute;
	width: var(--vf-size);
	aspect-ratio: 1;
	border-style: solid;
	border-color: var(--vf-color);
	border-width: 0;
	pointer-events: none;
	z-index: var(--z-raised);
}

.vf::before { top: var(--vf-inset); left: var(--vf-inset); border-top-width: var(--vf-weight); border-left-width: var(--vf-weight); }
.vf::after { bottom: var(--vf-inset); right: var(--vf-inset); border-bottom-width: var(--vf-weight); border-right-width: var(--vf-weight); }
.vf > .vf__tr { top: var(--vf-inset); right: var(--vf-inset); border-top-width: var(--vf-weight); border-right-width: var(--vf-weight); }
.vf > .vf__bl { bottom: var(--vf-inset); left: var(--vf-inset); border-bottom-width: var(--vf-weight); border-left-width: var(--vf-weight); }

.vf-signal { --vf-color: var(--accent); }
.vf-ink    { --vf-color: var(--line-strong); }
.vf-light  { --vf-color: rgb(255 255 255 / 0.8); }

/* Rule-of-thirds overlay — the composition grid inside a viewfinder. */
.vf-thirds > .vf__grid {
	position: absolute;
	inset: var(--vf-inset);
	pointer-events: none;
	background:
		linear-gradient(to right, transparent 33.2%, var(--vf-color) 33.2%, var(--vf-color) 33.4%, transparent 33.4%,
		                transparent 66.5%, var(--vf-color) 66.5%, var(--vf-color) 66.7%, transparent 66.7%),
		linear-gradient(to bottom, transparent 33.2%, var(--vf-color) 33.2%, var(--vf-color) 33.4%, transparent 33.4%,
		                transparent 66.5%, var(--vf-color) 66.5%, var(--vf-color) 66.7%, transparent 66.7%);
	opacity: 0.35;
}

/* Rec bug. There is deliberately NO centre focus box: a square in the middle
   of the frame fights whatever the frame is actually showing, which is the
   opposite of what a viewfinder is for. Corners and the bug, nothing else. */
.vf__rec {
	position: absolute;
	top: var(--vf-inset);
	left: 50%;
	transform: translateX(-50%);
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	color: var(--vf-color);
}

.vf__rec::before {
	content: '';
	width: var(--dot-md);
	height: var(--dot-md);
	border-radius: var(--radius-full);
	background: var(--accent);
	animation: fx-pulse 1.6s var(--ease-inout) infinite;
}

/* ── 8 · polaroid / contact sheet ────────────────────────────────────────── */

.polaroid {
	background: var(--bg-surface);
	padding: var(--space-3) var(--space-3) var(--space-8);
	border-radius: var(--radius-sm);
	box-shadow: var(--shadow-3);
	rotate: -1.2deg;
}

.polaroid > img,
.polaroid > .polaroid__img {
	display: block;
	width: 100%;
	aspect-ratio: 1;
	object-fit: cover;
	background: var(--bg-muted);
}

.polaroid__caption {
	margin-top: var(--space-3);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: 0.08em;
	color: var(--fg-subtle);
	text-align: center;
}

/* ── 9 · book ────────────────────────────────────────────────────────────
   A cover with a spine and a page edge. For the courses, the guides, the
   resume, and anything that wants to read as "a thing you can finish".

     <article class="book">
       <div class="book__cover"> … title … </div>
     </article>  */

.book {
	--book-spine: 1.1rem;
	position: relative;
	display: block;
	aspect-ratio: 3 / 4.2;
	border-radius: var(--radius-xs) var(--radius-md) var(--radius-md) var(--radius-xs);
	background: var(--bg-inverse);
	color: var(--fg-on-inverse);
	box-shadow: var(--shadow-3);
	overflow: hidden;
	isolation: isolate;
	transition: var(--t-transform), var(--t-shadow);
}

/* The spine: a darker band down the left edge with a highlight, which is the
   whole reason a flat rectangle reads as a book. */
.book::before {
	content: '';
	position: absolute;
	inset: 0 auto 0 0;
	width: var(--book-spine);
	z-index: 2;
	background:
		linear-gradient(to right,
			rgb(0 0 0 / 0.45) 0%,
			rgb(0 0 0 / 0.18) 42%,
			rgb(255 255 255 / 0.14) 60%,
			rgb(0 0 0 / 0.10) 100%);
}

/* The page edge on the right — thin, warm, slightly striped. */
.book::after {
	content: '';
	position: absolute;
	inset: 0 0 0 auto;
	width: 5px;
	z-index: 2;
	background:
		repeating-linear-gradient(to bottom, rgb(255 255 255 / 0.55) 0 1px, rgb(255 255 255 / 0.2) 1px 3px);
}

.book__cover {
	position: relative;
	z-index: 1;
	height: 100%;
	display: flex;
	flex-direction: column;
	gap: var(--space-2);
	padding: var(--space-6) var(--space-5) var(--space-5) calc(var(--book-spine) + var(--space-4));
}

.book__kicker {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: color-mix(in srgb, currentColor 62%, transparent);
}

.book__title {
	font-family: var(--font-display);
	font-size: var(--text-xl);
	font-weight: var(--weight-bold);
	line-height: 1.05;
	letter-spacing: var(--tracking-tight);
	text-wrap: balance;
}

.book__foot { margin-top: auto; }

@media (hover: hover) and (pointer: fine) {
	.book:hover { transform: translate3d(0, -4px, 0) rotate(-0.6deg); box-shadow: var(--shadow-4); }
}

/* Stack of books — a shelf, for a course index. */
.book-shelf { display: flex; gap: var(--space-4); align-items: flex-end; }
.book-shelf > .book:nth-child(even) { rotate: 1.2deg; }

/* ── 10 · film strip ─────────────────────────────────────────────────────── */

.filmstrip {
	position: relative;
	padding-block: 1.1rem;
	background: var(--ink-1000);
	border-radius: var(--radius-sm);
}

.filmstrip::before,
.filmstrip::after {
	content: '';
	position: absolute;
	left: 0;
	right: 0;
	height: 1.1rem;
	background: repeating-linear-gradient(to right,
		rgb(255 255 255 / 0.16) 0 10px, transparent 10px 22px);
}

.filmstrip::before { top: 0; }
.filmstrip::after { bottom: 0; }       /* window chrome, viewfinder, shutter     */
/* =============================================================================
   13 · CUTOUT
   Paper-craft and neo-brutal shapes: things that look cut, stuck, stamped or
   printed. Where 11-shape is geometry, cutout is *material*. All sizes ride
   --cut-off (the offset) and --cut-bw (the border) so one modifier scales the
   whole style.
   ========================================================================== */

.cut {
	--cut-off: 5px;
	--cut-bw: var(--border-2);
	position: relative;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: var(--space-3) var(--space-4);
	background: var(--bg-surface);
	color: var(--fg-default);
}

.cut-sm { --cut-off: 3px; --cut-bw: 1.5px; padding: var(--space-2) var(--space-3); font-size: var(--text-sm); }
.cut-lg { --cut-off: 8px; --cut-bw: 3px; padding: var(--space-4) var(--space-6); font-size: var(--text-lg); }

/* ── Sticker — the neo-brutal core: hard border, hard offset shadow ──────── */

.cut-sticker {
	border: var(--cut-bw) solid var(--fg-default);
	border-radius: var(--radius-md);
	box-shadow: var(--cut-off) var(--cut-off) 0 0 var(--fg-default);
	transition: var(--t-transform), var(--t-shadow);
}

.cut-sticker:active,
a.cut-sticker:hover,
button.cut-sticker:hover {
	transform: translate(2px, 2px);
	box-shadow: calc(var(--cut-off) - 2px) calc(var(--cut-off) - 2px) 0 0 var(--fg-default);
}

.cut-sticker-accent { box-shadow: var(--cut-off) var(--cut-off) 0 0 var(--accent); }

/* ── Stamp — perforated edge, like a postage stamp ───────────────────────── */

.cut-stamp {
	border: 0;
	background:
		radial-gradient(circle at 50% 50%, transparent 0.28em, var(--bg-surface) 0.3em);
	padding: calc(var(--space-3) + 0.3em) calc(var(--space-4) + 0.3em);
	-webkit-mask: radial-gradient(circle 0.28em at 0.3em 0.3em, transparent 98%, #000) -0.3em -0.3em / 0.6em 0.6em round;
	mask: radial-gradient(circle 0.28em, transparent 96%, #000 100%) 50% / 0.75em 0.75em round;
	background-color: var(--bg-surface);
	outline: var(--border-hair) solid var(--line-default);
	outline-offset: -0.05em;
}

/* ── Ticket — side notches ───────────────────────────────────────────────── */

.cut-ticket {
	border: var(--cut-bw) solid var(--fg-default);
	border-radius: var(--radius-md);
	-webkit-mask: radial-gradient(circle 0.5em at 0 50%, transparent 98%, #000) ,
		radial-gradient(circle 0.5em at 100% 50%, transparent 98%, #000);
	-webkit-mask-composite: source-in;
	mask:
		radial-gradient(circle 0.5em at 0 50%, transparent 98%, #000 100%) intersect,
		radial-gradient(circle 0.5em at 100% 50%, transparent 98%, #000 100%);
	padding-inline: calc(var(--space-6) + 0.5em);
}

/* ── Corner-cut — one clipped corner, like a folded slide ────────────────── */

.cut-corner {
	border: var(--cut-bw) solid var(--fg-default);
	clip-path: polygon(0 0, calc(100% - 1em) 0, 100% 1em, 100% 100%, 0 100%);
}

.cut-corner::after {
	content: '';
	position: absolute;
	top: 0;
	right: 0;
	width: 1em;
	height: 1em;
	background: var(--fg-default);
	clip-path: polygon(0 0, 100% 100%, 0 100%);
	opacity: 0.18;
}

/* ── Tab — a file-folder tab welded to the top edge ──────────────────────── */

.cut-tab {
	border: var(--cut-bw) solid var(--fg-default);
	border-radius: 0 var(--radius-md) var(--radius-md) var(--radius-md);
	margin-top: 1.4em;
}

.cut-tab::before {
	content: attr(data-tab);
	position: absolute;
	bottom: calc(100% - var(--cut-bw));
	left: calc(-1 * var(--cut-bw));
	padding: 0.15em 0.7em;
	background: var(--bg-surface);
	border: var(--cut-bw) solid var(--fg-default);
	border-bottom: 0;
	border-radius: var(--radius-md) var(--radius-md) 0 0;
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
}

/* ── Tape — two strips of translucent tape over the corners ──────────────── */

.cut-tape { border: var(--border-hair) solid var(--line-default); box-shadow: var(--shadow-1); }

.cut-tape::before,
.cut-tape::after {
	content: '';
	position: absolute;
	width: 3.2em;
	height: 1em;
	background: color-mix(in srgb, var(--amber-300, #e6c37c) 55%, transparent);
	border-inline: 1px dashed rgb(0 0 0 / 0.12);
	transform: rotate(-40deg);
}

.cut-tape::before { top: -0.45em; left: -1.1em; }
.cut-tape::after { bottom: -0.45em; right: -1.1em; transform: rotate(-40deg); }

/* ── Punched — ring-binder holes down the left edge ──────────────────────── */

.cut-punch {
	border: var(--cut-bw) solid var(--fg-default);
	border-radius: var(--radius-md);
	padding-left: calc(var(--space-6) + 0.6em);
	-webkit-mask: radial-gradient(circle 0.22em at 0.55em 50%, transparent 96%, #000 100%) 0 0 / 100% 1.1em repeat-y;
	mask: radial-gradient(circle 0.22em at 0.55em 50%, transparent 96%, #000 100%) 0 0 / 100% 1.1em repeat-y;
}

/* ── Speech — a brutal speech bubble ─────────────────────────────────────── */

.cut-speech {
	border: var(--cut-bw) solid var(--fg-default);
	border-radius: var(--radius-lg);
	box-shadow: var(--cut-off) var(--cut-off) 0 0 var(--fg-default);
	margin-bottom: 1em;
}

.cut-speech::after {
	content: '';
	position: absolute;
	bottom: calc(-0.8em - var(--cut-bw));
	left: 1.4em;
	width: 1em;
	height: 1em;
	background: var(--bg-surface);
	border-right: var(--cut-bw) solid var(--fg-default);
	border-bottom: var(--cut-bw) solid var(--fg-default);
	transform: skewX(24deg) rotate(45deg);
}

/* On ink, the cutouts flip: paper shapes on a dark desk. */
[data-surface='inverse'] .cut,
.cut-inverse {
	background: var(--bg-inverse);
	color: var(--fg-on-inverse);
	border-color: var(--fg-on-inverse);
}

[data-surface='inverse'] .cut-sticker,
.cut-inverse.cut-sticker { box-shadow: var(--cut-off) var(--cut-off) 0 0 var(--accent); }     /* paper-craft + neo-brutal shapes       */
/* =============================================================================
   SWARNIL DESIGN SYSTEM · 2 — ELEMENTS
   The atoms: type, labels, tables, indicators. An element is a single idea
   with no internal parts and no behaviour — a blockquote, a badge, a spinner.

   The moment a thing needs variants, states, or two pieces that know about
   each other, it stops being an element and moves to layer 3.

   Requires 1-foundation/index.css.
   ========================================================================== */

/* =============================================================================
   10 · TEXT ELEMENTS
   The typographic atoms: things made of type and a rule, nothing more. They
   compose nothing, they have no states, and they drop straight into Ghost's
   {{content}} without a wrapper.

   If it needs a variant, a state, or a second part that knows about the first,
   it is a component and it lives in layer 3.
   ========================================================================== */

/* ── Figure & pull-quote ─────────────────────────────────────────────────── */

.figure { display: flex; flex-direction: column; gap: var(--space-3); }

.figure > img { border-radius: var(--radius-media); width: 100%; }

.figure__caption {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-wide);
	color: var(--fg-subtle);
	display: flex;
	gap: var(--space-2);
	align-items: baseline;
}

.figure__caption::before {
	content: '';
	width: var(--dot-sm);
	height: var(--dot-sm);
	border-radius: var(--radius-full);
	background: var(--craft);
	flex: none;
	transform: translateY(-1px);
}

.pullquote {
	display: flex;
	flex-direction: column;
	gap: var(--space-3);
	padding-left: var(--space-5);
	border-left: var(--border-3) solid var(--accent);
}

.pullquote__text {
	font-family: var(--font-display);
	font-size: var(--text-2xl);
	font-weight: var(--weight-medium);
	line-height: var(--leading-snug);
	letter-spacing: var(--tracking-tight);
	text-wrap: balance;
}

.pullquote__cite {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-subtle);
	font-style: normal;
}

/* ── Code block ──────────────────────────────────────────────────────────── */

.code-block {
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-lg);
	background: var(--bg-sunken);
	overflow: hidden;
}

.code-block__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-3);
	padding: var(--space-2) var(--space-4);
	border-bottom: var(--border-hair) solid var(--line-subtle);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-subtle);
}

.code-block > pre {
	margin: 0;
	padding: var(--space-4);
	font-family: var(--font-slate);
	font-size: var(--text-xs);
	line-height: var(--leading-normal);
	overflow-x: auto;
}

/* ── Inline atoms ────────────────────────────────────────────────────────── */

/* Marked text — the static version of the .tx-highlight animation. */
.mark {
	background: var(--accent-soft);
	color: var(--accent-soft-fg);
	padding-inline: 0.14em;
	border-radius: 0.12em;
	box-decoration-break: clone;
	-webkit-box-decoration-break: clone;
}

/* An aside that isn't an alert: a margin note, a caveat, a correction. */
.note {
	border-left: var(--border-2) solid var(--line-default);
	padding-left: var(--space-4);
	color: var(--fg-muted);
	font-size: var(--text-sm);
	line-height: var(--leading-relaxed);
}

.note-craft { border-left-color: var(--craft); }
.note-signal { border-left-color: var(--accent); }

/* Footnotes */
.fn-ref {
	font-family: var(--font-slate);
	font-size: 0.7em;
	vertical-align: super;
	color: var(--fg-accent);
	text-decoration: none;
}

.fn {
	font-size: var(--text-sm);
	color: var(--fg-muted);
	border-top: var(--border-hair) solid var(--line-subtle);
	padding-top: var(--space-3);
}

/* Drop cap — one per article, at most. */
.drop-cap::first-letter {
	float: left;
	font-family: var(--font-display);
	font-size: 3.4em;
	line-height: 0.82;
	font-weight: var(--weight-bold);
	padding-right: 0.08em;
	color: var(--fg-default);
}

/* Keyboard-driven list of definitions, for docs and the changelog. */
.term {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-subtle);
}        /* blockquote, figure, code, note, caps  */
/* =============================================================================
   11 · BADGE, CHIP, EYEBROW, AVATAR
   The small stuff that labels things. All of it speaks in the slate voice, so
   metadata never competes with content.
   ========================================================================== */

/* ── Badge — a status, not a link ────────────────────────────────────────── */

.badge {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	height: 1.5rem;
	padding-inline: var(--space-2);
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-pill);
	background: var(--bg-surface);
	color: var(--fg-muted);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	font-weight: var(--weight-medium);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	white-space: nowrap;
}

.badge-signal  { background: var(--accent-soft);  color: var(--accent-soft-fg); border-color: transparent; }
.badge-craft   { background: var(--craft-soft);   color: var(--craft-fg);       border-color: transparent; }
.badge-success { background: var(--success-bg);   color: var(--success-fg);     border-color: transparent; }
.badge-warning { background: var(--warning-bg);   color: var(--warning-fg);     border-color: transparent; }
.badge-danger  { background: var(--danger-bg);    color: var(--danger-fg);      border-color: transparent; }
.badge-info    { background: var(--info-bg);      color: var(--info-fg);        border-color: transparent; }
.badge-inverse { background: var(--bg-inverse);   color: var(--fg-on-inverse);  border-color: transparent; }

/* The live badge. Filled accent + a pulsing dot — the loudest label allowed,
   and only ever one per surface. */
.badge-live {
	background: var(--accent);
	color: var(--fg-on-accent);
	border-color: var(--accent);
}

.badge-live::before {
	content: '';
	width: var(--dot-sm);
	height: var(--dot-sm);
	border-radius: var(--radius-full);
	background: currentColor;
	animation: fx-pulse 1.6s var(--ease-inout) infinite;
}

/* ── Eyebrow — the line above a heading ──────────────────────────────────── */

.eyebrow {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	font-family: var(--font-slate);
	font-size: var(--text-xs);
	font-weight: var(--weight-medium);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-subtle);
}

.eyebrow::before {
	content: '';
	width: var(--dot-sm);
	height: var(--dot-sm);
	border-radius: var(--radius-full);
	background: var(--accent);
	flex: none;
}

.eyebrow-plain::before { display: none; }

/* ── Chip — a badge you can act on (filters, tags) ───────────────────────── */

.chip {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	height: var(--control-sm);
	padding-inline: var(--space-3);
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-pill);
	background: var(--bg-surface);
	color: var(--fg-muted);
	font-size: var(--text-xs);
	font-weight: var(--weight-medium);
	text-decoration: none;
	cursor: pointer;
	transition: var(--t-colors);
}

.chip:hover {
	border-color: var(--line-strong);
	color: var(--fg-default);
	background: var(--bg-sunken);
}

.chip[aria-pressed='true'],
.chip[aria-current='page'] {
	background: var(--bg-inverse);
	border-color: var(--bg-inverse);
	color: var(--fg-on-inverse);
}

/* Count suffix inside a chip — tag clouds, filter facets. */
.chip__count {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	color: var(--fg-faint);
	font-variant-numeric: tabular-nums;
}

.chip[aria-pressed='true'] .chip__count { color: currentColor; opacity: 0.65; }

/* Removable chip */
.chip__x {
	display: inline-grid;
	place-items: center;
	width: 1rem;
	height: 1rem;
	border-radius: var(--radius-full);
	background: var(--hover-wash);
	font-size: 10px;
	line-height: 1;
}

/* ── Avatar ──────────────────────────────────────────────────────────────── */

.avatar {
	display: inline-grid;
	place-items: center;
	width: var(--control-md);
	height: var(--control-md);
	border-radius: var(--radius-full);
	background: var(--bg-inverse);
	color: var(--fg-on-inverse);
	font-family: var(--font-display);
	font-size: var(--text-xs);
	font-weight: var(--weight-semibold);
	letter-spacing: var(--tracking-normal);
	overflow: hidden;
	flex: none;
	user-select: none;
}

.avatar > img { width: 100%; height: 100%; object-fit: cover; }

.avatar-sm { width: var(--control-sm); height: var(--control-sm); font-size: var(--text-2xs); }
.avatar-lg { width: var(--control-lg); height: var(--control-lg); font-size: var(--text-sm); }
.avatar-xl { width: 4rem; height: 4rem; font-size: var(--text-lg); }

/* Overlapping stack — "3 people are in this". Ring uses the canvas colour so
   it works on any surface it sits on. */
.avatar-stack {
	display: inline-flex;
	align-items: center;
}

.avatar-stack > .avatar {
	box-shadow: 0 0 0 var(--border-2) var(--bg-canvas);
}

.avatar-stack > .avatar + .avatar { margin-left: calc(var(--space-2) * -1); }

/* ── Kbd — for ⌘K hints in nav and docs ──────────────────────────────────── */

.kbd {
	display: inline-grid;
	place-items: center;
	min-width: 1.5rem;
	height: 1.5rem;
	padding-inline: var(--space-2);
	border: var(--border-hair) solid var(--line-default);
	border-bottom-width: var(--border-2);
	border-radius: var(--radius-sm);
	background: var(--bg-sunken);
	color: var(--fg-muted);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	line-height: 1;
}

/* ── Timecode pill — runtime over media, or inline in a list ─────────────── */

.timecode {
	display: inline-flex;
	align-items: center;
	gap: var(--space-1);
	height: 1.375rem;
	padding-inline: var(--space-2);
	border-radius: var(--radius-sm);
	background: rgb(0 0 0 / 0.72);
	color: var(--pure-white);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	font-variant-numeric: tabular-nums;
	letter-spacing: var(--tracking-wide);
}

/* ── Certification seal · .cert ────────────────────────────────────────────────
   A credential, drawn the way credentials are drawn: a scalloped seal carrying
   a SHORT name — the acronym a certificate is actually known by — with the full
   title and issuer set beside it.

   The scallop is a clip-path polygon, generated once from a cosine so the lobes
   are rounded rather than spiky (a star reads as a rating; a rosette reads as an
   award). CSS-only, so it inherits currentColor and retheme for free — no image
   to re-export when the accent changes.

     <div class="cert">
       <span class="cert__seal">WAS</span>
       <div class="cert__body">
         <span class="cert__name">Web Accessibility Specialist</span>
         <span class="cert__issuer">IAAP · 2023</span>
       </div>
     </div>

   The short name is content, not decoration: it is the thing a reader scans for.
   Keep it to 2–4 characters — the seal is 3rem and does not grow.
   -------------------------------------------------------------------------- */

.cert {
	display: flex;
	align-items: center;
	gap: var(--space-4);
}

.cert__seal {
	--cert-color: var(--accent);
	flex: none;
	width: 3rem;
	height: 3rem;
	display: grid;
	place-items: center;
	clip-path: polygon(50.0% 0.0%, 54.1% 3.7%, 57.5% 7.7%, 62.0% 5.1%, 67.1% 3.0%, 69.7% 7.9%, 71.5% 12.8%, 76.7% 11.9%, 82.1% 11.7%, 82.9% 17.1%, 82.9% 22.4%, 88.1% 23.3%, 93.3% 25.0%, 92.1% 30.3%, 90.4% 35.3%, 94.9% 38.0%, 99.2% 41.3%, 96.3% 45.9%, 93.0% 50.0%, 96.3% 54.1%, 99.2% 58.7%, 94.9% 62.0%, 90.4% 64.7%, 92.1% 69.7%, 93.3% 75.0%, 88.1% 76.7%, 82.9% 77.6%, 82.9% 82.9%, 82.1% 88.3%, 76.7% 88.1%, 71.5% 87.2%, 69.7% 92.1%, 67.1% 97.0%, 62.0% 94.9%, 57.5% 92.3%, 54.1% 96.3%, 50.0% 100.0%, 45.9% 96.3%, 42.5% 92.3%, 38.0% 94.9%, 32.9% 97.0%, 30.3% 92.1%, 28.5% 87.2%, 23.3% 88.1%, 17.9% 88.3%, 17.1% 82.9%, 17.1% 77.6%, 11.9% 76.7%, 6.7% 75.0%, 7.9% 69.7%, 9.6% 64.7%, 5.1% 62.0%, 0.8% 58.7%, 3.7% 54.1%, 7.0% 50.0%, 3.7% 45.9%, 0.8% 41.3%, 5.1% 38.0%, 9.6% 35.3%, 7.9% 30.3%, 6.7% 25.0%, 11.9% 23.3%, 17.1% 22.4%, 17.1% 17.1%, 17.9% 11.7%, 23.3% 11.9%, 28.5% 12.8%, 30.3% 7.9%, 32.9% 3.0%, 38.0% 5.1%, 42.5% 7.7%, 45.9% 3.7%);
	background: var(--cert-color);
	color: var(--fg-on-accent);
	font-family: var(--font-display);
	font-size: var(--text-2xs);
	font-weight: var(--weight-bold);
	letter-spacing: var(--tracking-tight);
	text-transform: uppercase;
	line-height: 1;
	text-align: center;
}

/* Quiet variant — for a rail of six, where six accent seals would be six
   competing signals rather than one. */
.cert-quiet .cert__seal {
	--cert-color: var(--bg-muted);
	color: var(--fg-subtle);
}

.cert__body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

.cert__name {
	font-size: var(--text-sm);
	font-weight: var(--weight-semibold);
	line-height: var(--leading-snug);
}

.cert__issuer {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
}

/* A shelf of them. */
.cert-list { display: grid; gap: var(--space-4); }       /* badge, chip, eyebrow, avatar, kbd     */
/* =============================================================================
   15 · TABLE & LIST
   Mono caps headers, tabular numerals, hover rows. Tables scroll inside their
   own wrapper — the page never scrolls sideways.
   ========================================================================== */

.table-wrap {
	width: 100%;
	overflow-x: auto;
	overscroll-behavior-x: contain;
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-card);
	background: var(--bg-surface);
}

.table {
	width: 100%;
	border-collapse: collapse;
	font-size: var(--text-sm);
}

.table th {
	position: sticky;
	top: 0;
	z-index: var(--z-raised);
	background: var(--bg-sunken);
	text-align: left;
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	font-weight: var(--weight-medium);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-subtle);
	padding: var(--space-3) var(--space-4);
	border-bottom: var(--border-hair) solid var(--line-default);
	white-space: nowrap;
}

.table td {
	padding: var(--space-4);
	border-bottom: var(--border-hair) solid var(--line-subtle);
	vertical-align: middle;
}

.table tbody tr:last-child td { border-bottom: 0; }

.table tbody tr { transition: background-color var(--dur-1) var(--ease-out); }
.table tbody tr:hover { background: var(--hover-wash); }

/* Numbers line up, always. */
.table [data-numeric],
.table .num {
	font-family: var(--font-slate);
	font-variant-numeric: tabular-nums;
	text-align: right;
	color: var(--fg-muted);
	white-space: nowrap;
}

.table-compact td { padding: var(--space-2) var(--space-4); }

.table-fixed { table-layout: fixed; }

/* Row that is the current thing (playing lesson, live episode). */
.table tr[aria-current='true'] {
	background: var(--accent-soft);
	box-shadow: inset var(--border-3) 0 0 0 var(--accent);
}

/* ── Definition list — specs, resume facts, product details ─────────────── */

.dl {
	display: grid;
	grid-template-columns: minmax(8rem, 1fr) 2fr;
	gap: var(--space-3) var(--space-5);
	font-size: var(--text-sm);
}

.dl dt {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-subtle);
	padding-top: 2px;
}

.dl dd { margin: 0; color: var(--fg-default); }

@media (max-width: 34rem) {
	.dl { grid-template-columns: minmax(0, 1fr); gap: var(--space-1) 0; }
	.dl dd { margin-bottom: var(--space-3); }
}

/* ── Rule list — rows separated by hairlines, no box ─────────────────────
   The lightest way to list things: episodes, changelog entries, search hits. */

.rule-list {
	display: flex;
	flex-direction: column;
	list-style: none;
	padding: 0;
	margin: 0;
}

.rule-list > li {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-4);
	padding: var(--space-4) 0;
	border-bottom: var(--border-hair) solid var(--line-subtle);
}

.rule-list > li:last-child { border-bottom: 0; }

.rule-list__main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

.rule-list__title {
	font-weight: var(--weight-semibold);
	font-size: var(--text-sm);
	color: var(--fg-default);
	text-decoration: none;
}

.rule-list__meta {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-subtle);
}

/* ── Numbered steps — guides, curricula ─────────────────────────────────── */

.steps {
	counter-reset: step;
	list-style: none;
	padding: 0;
	margin: 0;
	display: flex;
	flex-direction: column;
}

.steps > li {
	counter-increment: step;
	position: relative;
	padding: 0 0 var(--space-6) var(--space-10);
	border-left: var(--border-hair) solid var(--line-default);
	margin-left: 0.9rem;
}

.steps > li:last-child { border-left-color: transparent; padding-bottom: 0; }

.steps > li::before {
	content: counter(step, decimal-leading-zero);
	position: absolute;
	left: -0.9rem;
	top: 0;
	display: grid;
	place-items: center;
	width: 1.8rem;
	height: 1.8rem;
	border-radius: var(--radius-full);
	background: var(--bg-surface);
	border: var(--border-hair) solid var(--line-default);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	color: var(--fg-subtle);
}

.steps > li[data-done]::before {
	background: var(--accent);
	border-color: var(--accent);
	color: var(--fg-on-accent);
}       /* table, dl, rule list, steps           */
/* =============================================================================
   13 · INDICATORS
   Atoms that show a quantity or a wait. No copy and no actions — a component
   wraps them when either is needed (.alert, .empty, .btn[data-loading]).
   ========================================================================== */

/* ── Progress ────────────────────────────────────────────────────────────── */

.progress {
	position: relative;
	height: 6px;
	width: 100%;
	border-radius: var(--radius-pill);
	background: var(--bg-muted);
	overflow: hidden;
}

.progress__bar {
	height: 100%;
	width: var(--value, 0%);
	border-radius: inherit;
	background: var(--accent);
	transition: width var(--dur-4) var(--ease-out);
}

.progress-thin { height: 3px; }

/* Indeterminate: we know it's working, not how long. */
.progress-indeterminate .progress__bar {
	width: 34%;
	animation: fx-rail 1.4s var(--ease-inout) infinite;
}

/* ── Split — a breakdown, not a completion ────────────────────────────────────
   One bar divided into parts that sum to the whole: a repository's languages, a
   budget, a vote. Distinct from .progress because the question is different —
   .progress asks "how far", this asks "of what". Each segment carries its own
   --value and --seg colour, so the palette stays in the markup where the data
   is rather than being invented here.

     <div class="progress progress-split">
       <span class="progress__seg" style="--value:62%;--seg:#563d7c"></span>
       <span class="progress__seg" style="--value:31%;--seg:#f1e05a"></span>
     </div>

   Always label the segments somewhere adjacent — a bar of unlabelled colour is
   decoration, and colour is never alone in this system. */

.progress-split { display: flex; gap: 2px; background: var(--bg-muted); }

.progress__seg {
	height: 100%;
	width: var(--value, 0%);
	background: var(--seg, var(--accent));
	flex: none;
}

.progress__seg:first-child { border-radius: var(--radius-pill) 0 0 var(--radius-pill); }
.progress__seg:last-child { border-radius: 0 var(--radius-pill) var(--radius-pill) 0; }

/* The legend that makes the colours mean something. */
.progress__key {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-2) var(--space-4);
	margin-top: var(--space-3);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	color: var(--fg-subtle);
}

.progress__key > span {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
}

/* Progress with a slate label above it (course completion, upload). */
.progress-labelled {
	display: flex;
	flex-direction: column;
	gap: var(--space-2);
}

.progress__label {
	display: flex;
	justify-content: space-between;
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-subtle);
	font-variant-numeric: tabular-nums;
}

/* ── Skeletons ───────────────────────────────────────────────────────────── */

.skeleton {
	border-radius: var(--radius-sm);
	background: linear-gradient(
		100deg,
		var(--bg-muted) 20%,
		color-mix(in srgb, var(--bg-muted) 55%, var(--bg-canvas)) 45%,
		var(--bg-muted) 70%
	);
	background-size: 220% 100%;
	animation: fx-shimmer 1.6s var(--ease-linear) infinite;
}

.skeleton-text { height: 0.75rem; }
.skeleton-title { height: 1.25rem; border-radius: var(--radius-sm); }
.skeleton-media { aspect-ratio: var(--ratio-video); border-radius: var(--radius-media); }
.skeleton-avatar { width: var(--control-md); height: var(--control-md); border-radius: var(--radius-full); }

/* Breathe: for one big unknown block. Slower and quieter than shimmer. */
.skeleton-breathe {
	background: var(--bg-muted);
	animation: fx-fade 1.8s var(--ease-inout) infinite alternate;
}

/* Scanline wipe over decoding media. */
.loading-scan {
	position: relative;
	overflow: hidden;
	background: var(--bg-media);
}

.loading-scan::after {
	content: '';
	position: absolute;
	left: 0;
	right: 0;
	height: 28%;
	background: linear-gradient(to bottom, transparent, rgb(255 255 255 / 0.07), transparent);
	animation: fx-scan 1.5s var(--ease-inout) infinite;
}

/* Buffering dots — live/player only. */
.buffer {
	display: inline-flex;
	gap: 4px;
	align-items: center;
}

.buffer > span {
	width: 5px;
	height: 5px;
	border-radius: var(--radius-full);
	background: var(--accent);
	animation: fx-buffer 1.2s var(--ease-inout) infinite;
}

.buffer > span:nth-child(2) { animation-delay: 0.15s; }
.buffer > span:nth-child(3) { animation-delay: 0.3s; }
.buffer > span:nth-child(4) { animation-delay: 0.45s; }

/* Spinner — button and inline use only. */
.spinner {
	display: inline-block;
	width: var(--icon-md);
	height: var(--icon-md);
	border: var(--border-2) solid var(--line-strong);
	border-top-color: var(--accent);
	border-radius: var(--radius-full);
	animation: fx-spin 700ms var(--ease-linear) infinite;
}

/* ── Tooltip — CSS only, content from data-tip ──────────────────────────── */

.tip { position: relative; }

.tip::after {
	content: attr(data-tip);
	position: absolute;
	bottom: calc(100% + var(--space-2));
	left: 50%;
	transform: translate(-50%, 4px);
	z-index: var(--z-tooltip);
	padding: var(--space-2) var(--space-3);
	border-radius: var(--radius-sm);
	background: var(--bg-inverse);
	color: var(--fg-on-inverse);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-wide);
	white-space: nowrap;
	opacity: 0;
	pointer-events: none;
	transition: opacity var(--dur-1) var(--ease-out), transform var(--dur-1) var(--ease-out);
}

.tip:hover::after,
.tip:focus-visible::after {
	opacity: 1;
	transform: translate(-50%, 0);
}   /* progress, skeleton, spinner, tooltip  */
/* =============================================================================
   15 · SYNTAX
   Code that reads in both themes, plus the copy affordance. The five token
   roles reuse 12-frame's dark hues; light theme gets its own measured set.
   Markup: <figure class="codebox"><figcaption class="codebox__head">…
   <button class="codebox__copy" data-copy>…</button></figcaption>
   <pre class="codebox__pre"><code>…</code></pre></figure>
   ========================================================================== */

.codebox {
	background: var(--ink-950);
	color: var(--ink-100, #e5e5ea);
	border: var(--border-hair) solid rgb(255 255 255 / 0.08);
	border-radius: var(--radius-lg);
	overflow: hidden;
	color-scheme: dark;
	margin: 0;
}

.codebox__head {
	display: flex;
	align-items: center;
	gap: var(--space-3);
	padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4);
	border-bottom: var(--border-hair) solid rgb(255 255 255 / 0.08);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: rgb(255 255 255 / 0.5);
}

.codebox__lang { margin-right: auto; }

.codebox__copy {
	display: inline-flex;
	align-items: center;
	gap: var(--space-1);
	border: var(--border-hair) solid rgb(255 255 255 / 0.14);
	background: transparent;
	color: rgb(255 255 255 / 0.65);
	border-radius: var(--radius-sm);
	font: inherit;
	padding: 3px var(--space-2);
	cursor: pointer;
	transition: var(--t-colors);
}

.codebox__copy:hover { background: rgb(255 255 255 / 0.08); color: #fff; }
.codebox__copy[data-copied] { color: var(--signal-300, #ff9c86); border-color: currentColor; }

.codebox__pre {
	margin: 0;
	padding: var(--space-4);
	overflow-x: auto;
	font-family: var(--font-slate);
	font-size: var(--text-xs);
	line-height: 1.7;
	tab-size: 2;
}

/* Line numbers: <code class="codebox__ln"> with one .ln per line. */
.codebox__pre .ln { counter-increment: ln; display: block; padding-left: 2.4em; position: relative; }
.codebox__pre .ln::before {
	content: counter(ln);
	position: absolute;
	left: 0;
	width: 1.8em;
	text-align: right;
	color: rgb(255 255 255 / 0.25);
}
.codebox__pre code { counter-reset: ln; display: block; }
.codebox__pre .ln-hl { background: rgb(255 255 255 / 0.06); box-shadow: inset 2px 0 0 var(--accent); }

/* Light twin — for docs prose where a dark slab is too loud. */
.codebox-light {
	background: var(--bg-sunken);
	color: var(--fg-default);
	border-color: var(--line-default);
	color-scheme: light dark;
}

.codebox-light .codebox__head { border-color: var(--line-subtle); color: var(--fg-faint); }
.codebox-light .codebox__copy { border-color: var(--line-default); color: var(--fg-subtle); }
.codebox-light .codebox__copy:hover { background: var(--bg-surface); color: var(--fg-default); }
.codebox-light .tok-key { color: #c22557; }
.codebox-light .tok-str { color: #1a7f4e; }
.codebox-light .tok-num { color: #9c6a10; }
.codebox-light .tok-fn  { color: #1d5fbf; }
.codebox-light .tok-com { color: var(--fg-faint); font-style: italic; }
.codebox-light .tok-tag { color: #c22557; }
.codebox-light .tok-attr { color: #9c6a10; }
.codebox-light .tok-prop { color: #1d5fbf; }
.codebox-light .tok-sel { color: #a2451b; }
.codebox-light .tok-var { color: #6b3fc4; }
.codebox-light .tok-punc { color: var(--fg-faint); }
.codebox-light .codebox__pre .ln::before { color: var(--fg-faint); opacity: 0.6; }
.codebox-light .codebox__pre .ln-hl { background: var(--accent-soft); box-shadow: inset 2px 0 0 var(--accent); }

/* ── Arrival ──────────────────────────────────────────────────────────────────
   A code block settles in when it first scrolls into view. One short fade and
   rise for the whole slab — enough to say "this just arrived", not so much
   that the reader waits on it. The finished state is the resting state, so
   nothing is hidden if the animation never runs.
   Markup: <figure class="codebox" data-play> — highlight.js drives the rest.
   -------------------------------------------------------------------------- */

.codebox[data-play].is-playing {
	animation: cb-in var(--dur-3, 260ms) var(--ease-out, ease-out) both;
}

@keyframes cb-in {
	0% { opacity: 0; transform: translateY(6px); }
	100% { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
	.codebox[data-play].is-playing { animation: none; }
}

/* Inline copy chip for one-liners (`npm run build`). */
.copy-line {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	font-family: var(--font-slate);
	font-size: var(--text-xs);
	background: var(--bg-sunken);
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-md);
	padding: var(--space-1) var(--space-1) var(--space-1) var(--space-3);
	max-width: 100%;
}

.copy-line > code { overflow-x: auto; white-space: nowrap; scrollbar-width: none; }

.copy-line > button {
	border: 0;
	background: var(--bg-surface);
	border-radius: var(--radius-sm);
	padding: 2px var(--space-2);
	font: inherit;
	color: var(--fg-subtle);
	cursor: pointer;
	flex: none;
	transition: var(--t-colors);
}

.copy-line > button:hover { color: var(--fg-default); }
.copy-line > button[data-copied] { color: var(--accent); }      /* codebox, token roles, copy affordance */
/* =============================================================================
   SWARNIL DESIGN SYSTEM · 3 — COMPONENTS
   Composed UI: things with variants, states, and parts that know about each
   other. A button is a component because it has six intents and five states;
   a badge is an element because it is a word in a shape.

   23-collection.css is the one that matters most — it is the theme's
   routes.yaml turned into cards, so every collection the site publishes has a
   defined shape instead of an improvised one.

   Requires 1-foundation and 2-elements.
   ========================================================================== */

/* =============================================================================
   10 · BUTTON
   One skeleton, six intents, three sizes. Every state is defined here so no
   template ever writes a hover rule.

   Intent picks itself: primary = the one thing this screen is for ·
   secondary = the other real action · ghost/quiet = navigation-ish ·
   soft = accent without weight · danger = destructive, always confirmed.
   ========================================================================== */

.btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--space-2);
	height: var(--control-md);
	padding-inline: var(--space-5);
	border: var(--border-hair) solid transparent;
	border-radius: var(--radius-control);
	background: transparent;
	color: var(--fg-default);
	font-family: var(--font-body-effective);
	font-size: var(--text-sm);
	font-weight: var(--weight-semibold);
	line-height: 1;
	letter-spacing: var(--tracking-normal);
	text-decoration: none;
	white-space: nowrap;
	cursor: pointer;
	user-select: none;
	transition: var(--t-colors), var(--t-transform), var(--t-shadow);
}

/* Press is a 1px settle, not a scale — a button is a physical key. */
.btn:active:not(:disabled, [aria-disabled='true']) {
	transform: translate3d(0, 1px, 0);
}

.btn:disabled,
.btn[aria-disabled='true'] {
	cursor: not-allowed;
	opacity: 0.45;
	pointer-events: none;
}

.btn > svg,
.btn__icon {
	width: var(--icon-sm);
	height: var(--icon-sm);
	flex: none;
}

/* ── Intents ─────────────────────────────────────────────────────────────── */

.btn-primary {
	background: var(--accent);
	color: var(--fg-on-accent);
	border-color: var(--accent);
}

.btn-primary:hover {
	background: var(--accent-hover);
	border-color: var(--accent-hover);
}

.btn-primary:active { background: var(--accent-press); border-color: var(--accent-press); }

.btn-secondary {
	background: var(--bg-inverse);
	color: var(--fg-on-inverse);
	border-color: var(--bg-inverse);
}

.btn-secondary:hover { opacity: 0.88; }

.btn-ghost {
	background: var(--bg-surface);
	border-color: var(--line-default);
	color: var(--fg-default);
}

.btn-ghost:hover {
	border-color: var(--line-strong);
	background: var(--bg-sunken);
}

.btn-soft {
	background: var(--accent-soft);
	color: var(--accent-soft-fg);
	border-color: transparent;
}

.btn-soft:hover { background: var(--accent-soft); border-color: var(--line-accent); }

/* Quiet: reads as text until you touch it. Toolbars, card footers, "more". */
.btn-quiet {
	background: transparent;
	color: var(--fg-muted);
	padding-inline: var(--space-3);
}

.btn-quiet:hover { background: var(--hover-wash); color: var(--fg-default); }

.btn-danger {
	background: var(--danger-line);
	color: var(--pure-white);
	border-color: var(--danger-line);
}

.btn-danger:hover { filter: brightness(0.92); }

/* ── Sizes ───────────────────────────────────────────────────────────────── */

.btn-sm {
	height: var(--control-sm);
	padding-inline: var(--space-3);
	font-size: var(--text-xs);
	gap: var(--space-1);
}

.btn-lg {
	height: var(--control-lg);
	padding-inline: var(--space-6);
	font-size: var(--text-base);
}

.btn-block { display: flex; width: 100%; }

/* Square icon-only button. Needs an aria-label — there is no visible text. */
.btn-icon {
	width: var(--control-md);
	padding-inline: 0;
}

.btn-icon.btn-sm { width: var(--control-sm); }
.btn-icon.btn-lg { width: var(--control-lg); }

.btn-pill { border-radius: var(--radius-pill); }

/* ── States ──────────────────────────────────────────────────────────────── */

/* Loading: the label stays (so width doesn't jump) and a spinner replaces the
   icon slot. Set aria-busy="true" as well as the attribute. */
.btn[data-loading] {
	pointer-events: none;
	position: relative;
	color: transparent;
}

.btn[data-loading]::after {
	content: '';
	position: absolute;
	width: var(--icon-sm);
	height: var(--icon-sm);
	border: var(--border-2) solid currentColor;
	border-top-color: transparent;
	border-radius: var(--radius-full);
	color: var(--fg-on-accent);
	animation: fx-spin 700ms var(--ease-linear) infinite;
}

.btn-ghost[data-loading]::after,
.btn-quiet[data-loading]::after,
.btn-soft[data-loading]::after { color: var(--fg-default); }

/* The live button — "watch now", "go live". The only button that pulses. */
.btn-live::before {
	content: '';
	width: var(--dot-md);
	height: var(--dot-md);
	border-radius: var(--radius-full);
	background: currentColor;
	flex: none;
	animation: fx-pulse 1.6s var(--ease-inout) infinite;
}

/* ── Button group ─────────────────────────────────────────────────────────
   Segmented control: shared edges, one active. */

.btn-group {
	display: inline-flex;
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-control);
	background: var(--bg-sunken);
	padding: 3px;
	gap: 3px;
}

.btn-group > .btn {
	height: calc(var(--control-md) - 8px);
	border-radius: calc(var(--radius-control) - 3px);
	font-size: var(--text-xs);
	color: var(--fg-muted);
	padding-inline: var(--space-3);
}

.btn-group > .btn:hover { color: var(--fg-default); }

.btn-group > .btn[aria-pressed='true'],
.btn-group > .btn[aria-current='true'] {
	background: var(--bg-canvas);
	color: var(--fg-default);
	box-shadow: var(--shadow-1);
}      /* 6 intents, 3 sizes, states, groups    */
/* =============================================================================
   12 · FORMS
   Every control is the same height as a button, so a field and a submit sit on
   one line without shimming. Errors say what happened AND how to fix it.
   ========================================================================== */

/* ── Field wrapper ───────────────────────────────────────────────────────── */

.field {
	display: flex;
	flex-direction: column;
	gap: var(--space-2);
}

.label {
	font-size: var(--text-sm);
	font-weight: var(--weight-semibold);
	color: var(--fg-default);
}

.label-optional {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
	font-weight: var(--weight-regular);
	margin-left: var(--space-2);
}

.hint {
	font-size: var(--text-xs);
	color: var(--fg-subtle);
}

/* Error copy. Never just "invalid" — say what to do. */
.error-text {
	display: flex;
	align-items: flex-start;
	gap: var(--space-2);
	font-size: var(--text-xs);
	color: var(--danger-fg);
}

.error-text::before {
	content: '!';
	display: inline-grid;
	place-items: center;
	width: 1rem;
	height: 1rem;
	flex: none;
	border-radius: var(--radius-full);
	background: var(--danger-line);
	color: var(--pure-white);
	font-family: var(--font-slate);
	font-size: 10px;
	line-height: 1;
}

/* ── Text controls ───────────────────────────────────────────────────────── */

.input,
.textarea,
.select {
	width: 100%;
	height: var(--control-md);
	padding-inline: var(--space-3);
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-control);
	background: var(--bg-sunken);
	color: var(--fg-default);
	font-size: var(--text-sm);
	transition: var(--t-colors), var(--t-shadow);
	appearance: none;
}

.textarea {
	height: auto;
	min-height: 7rem;
	padding-block: var(--space-3);
	line-height: var(--leading-normal);
	resize: vertical;
}

.input::placeholder,
.textarea::placeholder { color: var(--fg-faint); }

.input:hover,
.textarea:hover,
.select:hover { border-color: var(--line-strong); }

.input:focus,
.textarea:focus,
.select:focus {
	outline: none;
	border-color: var(--accent);
	box-shadow: 0 0 0 3px var(--accent-ring);
	background: var(--bg-surface);
}

.input:disabled,
.textarea:disabled,
.select:disabled {
	opacity: 0.5;
	cursor: not-allowed;
}

/* Invalid state is driven by aria-invalid, so the styling and the screen
   reader can never disagree. */
.input[aria-invalid='true'],
.textarea[aria-invalid='true'],
.select[aria-invalid='true'] {
	border-color: var(--danger-line);
}

.input[aria-invalid='true']:focus,
.textarea[aria-invalid='true']:focus {
	box-shadow: 0 0 0 3px var(--danger-bg);
}

/* Select needs its own chevron since appearance is reset. */
.select {
	padding-right: var(--space-8);
	background-image:
		linear-gradient(45deg, transparent 50%, currentColor 50%),
		linear-gradient(135deg, currentColor 50%, transparent 50%);
	background-position:
		calc(100% - 18px) calc(50% + 2px),
		calc(100% - 13px) calc(50% + 2px);
	background-size: 5px 5px, 5px 5px;
	background-repeat: no-repeat;
	cursor: pointer;
}

/* Search field with a leading icon slot */
.input-search { padding-left: var(--space-8); }

.field-icon { position: relative; }

.field-icon > svg,
.field-icon > .field-icon__glyph {
	position: absolute;
	left: var(--space-3);
	top: 50%;
	transform: translateY(-50%);
	width: var(--icon-sm);
	height: var(--icon-sm);
	color: var(--fg-faint);
	pointer-events: none;
}

/* ── Inline group — input + button on one line (the subscribe form) ─────── */

.input-group {
	display: flex;
	gap: var(--space-2);
	align-items: center;
}

.input-group > .input { flex: 1 1 12rem; min-width: 0; }

@media (max-width: 30rem) {
	.input-group { flex-wrap: wrap; }
	.input-group > .btn { width: 100%; }
}

/* ── Checkbox & radio ────────────────────────────────────────────────────
   Real inputs, restyled — not divs pretending. Keyboard and forms just work. */

.check,
.radio {
	display: inline-flex;
	align-items: center;
	gap: var(--space-3);
	font-size: var(--text-sm);
	cursor: pointer;
	user-select: none;
}

.check > input,
.radio > input {
	appearance: none;
	width: 1.125rem;
	height: 1.125rem;
	flex: none;
	margin: 0;
	border: var(--border-2) solid var(--line-strong);
	background: var(--bg-surface);
	cursor: pointer;
	transition: var(--t-colors);
	display: grid;
	place-items: center;
}

.check > input { border-radius: var(--radius-xs); }
.radio > input { border-radius: var(--radius-full); }

.check > input:hover,
.radio > input:hover { border-color: var(--accent); }

.check > input:checked,
.radio > input:checked {
	background: var(--accent);
	border-color: var(--accent);
}

.check > input:checked::after {
	content: '';
	width: 0.55rem;
	height: 0.3rem;
	border-left: var(--border-2) solid var(--fg-on-accent);
	border-bottom: var(--border-2) solid var(--fg-on-accent);
	transform: rotate(-45deg) translate(1px, -1px);
}

.radio > input:checked::after {
	content: '';
	width: 0.4rem;
	height: 0.4rem;
	border-radius: var(--radius-full);
	background: var(--fg-on-accent);
}

.check > input:disabled,
.radio > input:disabled { opacity: 0.45; cursor: not-allowed; }

/* ── Switch — for settings that take effect immediately ─────────────────── */

.switch {
	display: inline-flex;
	align-items: center;
	gap: var(--space-3);
	font-size: var(--text-sm);
	cursor: pointer;
	user-select: none;
}

.switch > input {
	appearance: none;
	position: relative;
	width: 2.5rem;
	height: 1.5rem;
	flex: none;
	margin: 0;
	border-radius: var(--radius-pill);
	background: var(--line-strong);
	cursor: pointer;
	transition: background-color var(--dur-2) var(--ease-out);
}

.switch > input::after {
	content: '';
	position: absolute;
	top: 3px;
	left: 3px;
	width: 1.125rem;
	height: 1.125rem;
	border-radius: var(--radius-full);
	background: var(--pure-white);
	box-shadow: var(--shadow-1);
	transition: transform var(--dur-2) var(--ease-out);
}

.switch > input:checked { background: var(--accent); }
.switch > input:checked::after { transform: translateX(1rem); }
.switch > input:disabled { opacity: 0.45; cursor: not-allowed; }

/* ── Fieldset ────────────────────────────────────────────────────────────── */

.fieldset {
	border: 0;
	padding: 0;
	margin: 0;
	display: flex;
	flex-direction: column;
	gap: var(--space-4);
}

.fieldset > legend {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-subtle);
	padding: 0;
	margin-bottom: var(--space-2);
}

/* Two-up form row that stacks on small screens. */
.form-row {
	display: grid;
	gap: var(--space-5);
	grid-template-columns: repeat(2, minmax(0, 1fr));
}

@media (max-width: 40rem) {
	.form-row { grid-template-columns: minmax(0, 1fr); }
}        /* field = label + control + hint + error */
/* =============================================================================
   13 · CARD
   The workhorse. Every collection on the site is a deck of these — designed at
   thumbnail scale first (P1), so the title survives a 120px crop.
   ========================================================================== */

.card {
	position: relative;
	display: flex;
	flex-direction: column;
	background: var(--bg-surface);
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-card);
	overflow: hidden;
	transition: var(--t-colors), var(--t-transform), var(--t-shadow);
}

@media (hover: hover) and (pointer: fine) {
	.card:hover {
		transform: translate3d(0, -3px, 0);
		box-shadow: var(--shadow-2);
		border-color: var(--line-strong);
	}
}

/* Whole-card link. The <a> stays on the title (so the accessible name and the
   tab stop are right) and its ::after covers the card. */
.card__link { text-decoration: none; color: inherit; }

/* When the card ITSELF is the anchor, nothing inside it should underline —
   the card is the affordance, not the words. */
a.card,
a.card:hover { text-decoration: none; color: inherit; }

.card__link::after {
	content: '';
	position: absolute;
	inset: 0;
	z-index: var(--z-raised);
}

/* Anything that must stay clickable above the cover (a tag, a play button). */
.card__above { position: relative; z-index: calc(var(--z-raised) + 1); }

/* ── Media ───────────────────────────────────────────────────────────────── */

.card__media {
	position: relative;
	aspect-ratio: var(--ratio-video);
	background: var(--bg-media);
	overflow: hidden;
	flex: none;
}

.card__media > img,
.card__media > video {
	width: 100%;
	height: 100%;
	object-fit: cover;
	transition: transform var(--dur-4) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
	.card:hover .card__media > img { transform: scale(1.03); }
}

.card__media-square { aspect-ratio: var(--ratio-square); }
.card__media-photo  { aspect-ratio: var(--ratio-photo); }
.card__media-poster { aspect-ratio: var(--ratio-poster); }

/* Corner slots on the media — runtime bottom-right, status top-left. */
.card__media-tr,
.card__media-br,
.card__media-tl {
	position: absolute;
	z-index: var(--z-raised);
}

.card__media-tl { top: var(--space-3); left: var(--space-3); }
.card__media-tr { top: var(--space-3); right: var(--space-3); }
.card__media-br { bottom: var(--space-3); right: var(--space-3); }

/* ── Body ────────────────────────────────────────────────────────────────── */

.card__body {
	display: flex;
	flex-direction: column;
	gap: var(--space-2);
	padding: var(--space-5);
	flex: 1;
}

.card__meta {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-subtle);
	display: flex;
	align-items: center;
	gap: var(--space-2);
	flex-wrap: wrap;
}

.card__title {
	font-family: var(--font-heading-effective);
	font-size: var(--text-lg);
	font-weight: var(--weight-bold);
	line-height: var(--leading-snug);
	letter-spacing: var(--tracking-tight);
	color: var(--fg-default);
	text-wrap: balance;
}

.card__excerpt {
	font-size: var(--text-sm);
	line-height: var(--leading-normal);
	color: var(--fg-muted);
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
	line-clamp: 2;
	overflow: hidden;
}

.card__footer {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-3);
	padding: var(--space-3) var(--space-5);
	border-top: var(--border-hair) solid var(--line-subtle);
	margin-top: auto;
	font-size: var(--text-xs);
	color: var(--fg-subtle);
}

/* ── Variants ────────────────────────────────────────────────────────────── */

/* Featured: an accent hairline and a soft tint. Used for exactly one card in
   a deck — the editor's pick, the newest episode. */
.card-featured {
	border-color: var(--line-accent);
	background: var(--bg-surface);
}

.card-featured .card__title { color: var(--fg-default); }

/* Inverse: for the cinematic decks (webseries, videos). Always dark. */
.card-inverse {
	background: var(--ink-950);
	border-color: rgb(255 255 255 / 0.10);
	color: var(--ink-50);
}

.card-inverse .card__title { color: var(--ink-50); }
.card-inverse .card__excerpt { color: var(--ink-400); }
.card-inverse .card__footer { border-top-color: rgb(255 255 255 / 0.08); }

/* Row: media left, body right. Search results, "up next", episode lists. */
.card-row {
	flex-direction: row;
	align-items: stretch;
}

.card-row > .card__media {
	width: 40%;
	max-width: 14rem;
	aspect-ratio: var(--ratio-video);
}

.card-row > .card__body { padding: var(--space-4); }

@media (max-width: 34rem) {
	.card-row { flex-direction: column; }
	.card-row > .card__media { width: 100%; max-width: none; }
}

/* Compact: no media, tight padding. Changelog, snippets, guestbook. */
.card-compact > .card__body { padding: var(--space-4); gap: var(--space-1); }
.card-compact .card__title { font-size: var(--text-base); }

/* Bare: no border or background — a card in a list that already has rules. */
.card-bare {
	background: transparent;
	border-color: transparent;
	border-radius: 0;
}

.card-bare:hover { transform: none; box-shadow: none; }

/* Poster: the Netflix-style tile. Title lives on the image. */
.card-poster {
	border: 0;
	background: var(--bg-media);
	border-radius: var(--radius-media);
}

.card-poster > .card__media {
	aspect-ratio: var(--ratio-poster);
	border-radius: inherit;
}

.card-poster .card__body {
	position: absolute;
	inset: auto 0 0;
	padding: var(--space-4);
	gap: var(--space-1);
	background: linear-gradient(to top, rgb(0 0 0 / 0.85), rgb(0 0 0 / 0.35) 55%, transparent);
	color: var(--pure-white);
}

.card-poster .card__title { color: var(--pure-white); font-size: var(--text-base); }
.card-poster .card__meta { color: rgb(255 255 255 / 0.72); }

/* ── Deck ────────────────────────────────────────────────────────────────
   The default grid a collection renders into. */

.deck {
	display: grid;
	gap: var(--grid-gap);
	grid-template-columns: repeat(auto-fill, minmax(min(19rem, 100%), 1fr));
}

.deck-sm { grid-template-columns: repeat(auto-fill, minmax(min(13rem, 100%), 1fr)); }
.deck-lg { grid-template-columns: repeat(auto-fill, minmax(min(24rem, 100%), 1fr)); }

/* ── Plan · membership pricing ─────────────────────────────────────────────────
   A card that has to answer four questions in a fixed order — what tier, what
   price, what you get, how to start — because a pricing grid is read across
   rather than down: people compare the same line on three cards at once. So
   every part is a named slot and the order is not negotiable.

   Exactly one card in a grid may be .plan-featured. Two featured plans is the
   same as none, and the accent is rationed in this system precisely so that
   "this one" can mean something.

     <div class="plan-grid">
       <article class="plan plan-featured">
         <span class="plan__flag">Most popular</span>
         <span class="plan__name">Member</span>
         <p class="plan__price">$9<span class="plan__period">/month</span></p>
         <p class="plan__note">Everything, and the archive.</p>
         <ul class="plan__features"> <li> … </ul>
         <a class="btn btn-primary plan__cta"> … </a>
       </article>
     </div>
   -------------------------------------------------------------------------- */

.plan-grid {
	display: grid;
	gap: var(--space-5);
	grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
	align-items: start;
}

.plan {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: var(--space-3);
	padding: var(--space-6);
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-card);
	background: var(--bg-surface);
	height: 100%;
}

/* The one that is recommended. A 2px accent rule, never a colour fill — the
   same house rule the navbar's active state follows. */
.plan-featured {
	border-color: var(--accent);
	box-shadow: var(--shadow-2);
}

.plan__flag {
	position: absolute;
	top: 0;
	right: var(--space-5);
	transform: translateY(-50%);
	padding: 2px var(--space-3);
	border-radius: var(--radius-pill);
	background: var(--accent);
	color: var(--fg-on-accent);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
}

.plan__name {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-subtle);
}

.plan__price {
	font-family: var(--font-display);
	font-size: var(--text-4xl);
	font-weight: var(--weight-bold);
	letter-spacing: var(--tracking-tighter);
	line-height: var(--leading-flat);
	font-variant-numeric: tabular-nums;
}

.plan__period {
	font-family: var(--font-body);
	font-size: var(--text-sm);
	font-weight: var(--weight-medium);
	letter-spacing: var(--tracking-normal);
	color: var(--fg-faint);
}

.plan__note {
	font-size: var(--text-sm);
	color: var(--fg-subtle);
	line-height: var(--leading-normal);
}

.plan__features {
	list-style: none;
	margin: var(--space-2) 0 0;
	padding: 0;
	display: grid;
	gap: var(--space-2);
	font-size: var(--text-sm);
	border-top: var(--border-hair) solid var(--line-subtle);
	padding-top: var(--space-4);
}

.plan__features > li {
	display: flex;
	align-items: flex-start;
	gap: var(--space-2);
	color: var(--fg-subtle);
}

/* The tick is drawn, so a feature list needs no icon markup per row. */
.plan__features > li::before {
	content: '✓';
	color: var(--fg-accent);
	font-size: var(--text-xs);
	line-height: 1.5;
	flex: none;
}

/* A feature the tier does NOT include still earns a row — an absence you can
   see beats a list you have to diff against the next card. */
.plan__features > li[data-off] { color: var(--fg-faint); }
.plan__features > li[data-off]::before { content: '−'; color: var(--fg-faint); }

/* Pushed to the bottom so every CTA in the grid lines up regardless of how
   many features each tier lists. */
.plan__cta { margin-top: auto; width: 100%; justify-content: center; }        /* the base card and its layouts         */
/* =============================================================================
   23 · COLLECTION CARDS
   routes.yaml, turned into cards. Every collection the site publishes gets a
   defined shape here, so a template never has to improvise one.

   THE RULE: each collection extends .card and changes exactly ONE thing — the
   device that says what kind of thing this is. Same padding, same title scale,
   same meta position, same hover. That is what makes a mixed archive page or a
   search result list look deliberate instead of like six different websites.

     collection      tag              the one device
     ─────────────────────────────────────────────────────────────────────────
     videos          #video           duration pill + play disc
     blog            #blog            reading time, no media
     projects        #project         logo tile + stack row
     project-detail  #project-detail  step number
     courses         #course          book spine + lesson count
     lessons         #lesson          progress tick + position
     webseries       #webseries       letterbox bars
     episodes        #episode         big ghost numeral
     products        #product         price + group tag
     shop            #shop            price + ticket notch
     timeline        #timeline        year gutter + node
     changelog       #changelog       version chip + type dot
     newsletters     #newsletter      issue number + envelope rule
     snippets        #snippet         language tag + code preview
     prompts         #prompt          chat bubble
     experiences     #experience      photo-first, caption below
     travel (trip)   #trip            coordinates + country
     travel (post)   #travel          day number
     docs            #docs            section label + arrow
     guides          #guide           step count
     tags / authors  —                count + avatar
   ========================================================================== */

/* Shared spine for every collection card. */
.c {
	position: relative;
	display: flex;
	flex-direction: column;
	background: var(--bg-surface);
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-card);
	overflow: hidden;
	transition: var(--t-colors), var(--t-transform), var(--t-shadow);
}

@media (hover: hover) and (pointer: fine) {
	.c:hover {
		transform: translate3d(0, -3px, 0);
		box-shadow: var(--shadow-2);
		border-color: var(--line-strong);
	}
}

.c__media {
	position: relative;
	aspect-ratio: var(--ratio-video);
	background: var(--bg-media);
	overflow: hidden;
	flex: none;
}

.c__body { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-5); flex: 1; }

.c__meta {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-subtle);
	display: flex;
	align-items: center;
	gap: var(--space-2);
	flex-wrap: wrap;
}

.c__title {
	font-family: var(--font-heading-effective);
	font-size: var(--text-lg);
	font-weight: var(--weight-bold);
	line-height: var(--leading-snug);
	letter-spacing: var(--tracking-tight);
	text-wrap: balance;
}

.c__excerpt {
	font-size: var(--text-sm);
	line-height: var(--leading-normal);
	color: var(--fg-muted);
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
	line-clamp: 2;
	overflow: hidden;
}

.c__foot {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-3);
	padding: var(--space-3) var(--space-5);
	border-top: var(--border-hair) solid var(--line-subtle);
	margin-top: auto;
	font-size: var(--text-xs);
	color: var(--fg-subtle);
}

/* Whole-card link: the <a> stays on the title, its ::after covers the card. */
.c__link { text-decoration: none; color: inherit; }
.c__link::after { content: ''; position: absolute; inset: 0; z-index: var(--z-raised); }
.c__above { position: relative; z-index: calc(var(--z-raised) + 1); }

/* ── videos · #video ─────────────────────────────────────────────────────
   Duration pill bottom-right of the media, play disc on hover. The only
   collection that shows a runtime, because a video is the one thing where
   "how long is this" changes the decision. */

.c-video .c__media::after {
	content: attr(data-runtime);
	position: absolute;
	right: var(--space-3);
	bottom: var(--space-3);
	z-index: var(--z-raised);
	padding: 2px 6px;
	border-radius: var(--radius-sm);
	background: rgb(0 0 0 / 0.78);
	color: var(--pure-white);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	font-variant-numeric: tabular-nums;
}

.c-video .c__play {
	position: absolute;
	inset: 0;
	display: grid;
	place-items: center;
	z-index: var(--z-raised);
	opacity: 0;
	transition: opacity var(--dur-2) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
	.c-video:hover .c__play { opacity: 1; }
}

/* ── blog · #blog ────────────────────────────────────────────────────────
   No media by default — an essay's thumbnail is its first sentence. */

.c-blog .c__body { padding: var(--space-6); gap: var(--space-3); }
.c-blog .c__title { font-size: var(--text-xl); }

/* ── projects · #project ─────────────────────────────────────────────────
   A logo tile instead of a hero image, because a project is a thing, not a
   scene.

   TWO shapes, because a repo card is read two different ways and one card
   stretched to both widths does neither well:

     .c-project        the grid card — stacked, for a three-up. The default,
                       since a portfolio shows several at once.
     .c-project-wide   the row — logo left, copy right, for a single
                       full-width slot (a "related" strip, a search result).

   The wide one is a modifier rather than the base precisely so a full-width
   row is a decision someone made, not what you get by putting the grid card
   in a wide container. */

.c-project .c__logo {
	width: 3rem;
	height: 3rem;
	border-radius: var(--radius-md);
	background: var(--bg-muted);
	display: grid;
	place-items: center;
	flex: none;
	overflow: hidden;
}

.c-project .c__body {
	display: flex;
	flex-direction: column;
	gap: var(--space-3);
	align-items: flex-start;
}

.c-project .c__stack { display: flex; flex-wrap: wrap; gap: var(--space-1); }

/* The footer wraps rather than scrolling: six facts on one line in a 1/3-width
   card would either overflow or shrink the type below readable. */
.c-project .c__foot { flex-wrap: wrap; row-gap: var(--space-2); }

/* ── The wide row ────────────────────────────────────────────────────────── */

.c-project-wide { flex-direction: row; align-items: stretch; }

.c-project-wide .c__logo {
	width: 4rem;
	height: 4rem;
	margin: var(--space-5) 0 var(--space-5) var(--space-5);
}

.c-project-wide .c__body { flex: 1; }

@media (max-width: 40rem) {
	/* No room for two columns — the wide card becomes the grid card. */
	.c-project-wide { flex-direction: column; }
	.c-project-wide .c__logo { margin: var(--space-5) var(--space-5) 0; }
}

/* ── project build-log · #project-detail ─────────────────────────────────── */

.c-projectlog { box-shadow: inset var(--border-3) 0 0 0 var(--line-default); }
.c-projectlog .c__step {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	color: var(--fg-accent);
}

/* ── courses · #course ───────────────────────────────────────────────────
   A spine down the left edge — the book device from layer 1, at card scale. */

.c-course { padding-left: 0.9rem; }

.c-course::before {
	content: '';
	position: absolute;
	inset: 0 auto 0 0;
	width: 0.9rem;
	background:
		linear-gradient(to right, rgb(0 0 0 / 0.22), rgb(0 0 0 / 0.06) 55%, rgb(255 255 255 / 0.10));
	background-color: var(--accent);
}

.c-course .c__progress { width: 100%; }

/* ── lessons · #lesson ───────────────────────────────────────────────────
   A row, not a card: lessons are read as a list inside their course. */

.c-lesson {
	flex-direction: row;
	align-items: center;
	gap: var(--space-3);
	padding: var(--space-3) var(--space-4);
	border-radius: var(--radius-lg);
	border-color: transparent;
	background: transparent;
}

.c-lesson:hover { background: var(--hover-wash); transform: none; box-shadow: none; }

.c-lesson[aria-current='true'] {
	background: var(--accent-soft);
	box-shadow: inset var(--border-3) 0 0 0 var(--accent);
}

.c-lesson__no {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	color: var(--fg-faint);
	font-variant-numeric: tabular-nums;
	flex: none;
	width: 2ch;
}

.c-lesson__tick {
	width: var(--icon-sm);
	height: var(--icon-sm);
	border-radius: var(--radius-full);
	border: var(--border-2) solid var(--line-strong);
	flex: none;
}

.c-lesson[data-done] .c-lesson__tick { background: var(--accent); border-color: var(--accent); }

/* ── webseries · #webseries ──────────────────────────────────────────────
   Always cinema: dark, letterboxed, regardless of theme. */

.c-series {
	background: var(--ink-950);
	border-color: rgb(255 255 255 / 0.10);
	color: var(--ink-50);
	color-scheme: dark;
}

.c-series .c__title { color: var(--ink-50); }
.c-series .c__excerpt { color: var(--ink-400); }
.c-series .c__foot { border-top-color: rgb(255 255 255 / 0.08); }

.c-series .c__media::before,
.c-series .c__media::after {
	content: '';
	position: absolute;
	left: 0;
	right: 0;
	height: 8%;
	background: var(--pure-black);
	z-index: var(--z-raised);
}

.c-series .c__media::before { top: 0; }
.c-series .c__media::after { bottom: 0; }

/* ── episodes · #episode ─────────────────────────────────────────────────
   The ghost numeral, at card scale. */

.c-episode .c__no {
	position: absolute;
	right: var(--space-3);
	top: var(--space-1);
	font-family: var(--font-display);
	font-size: 3.5rem;
	font-weight: var(--weight-bold);
	line-height: 1;
	letter-spacing: var(--tracking-tighter);
	color: var(--line-subtle);
	font-variant-numeric: tabular-nums;
	pointer-events: none;
}

/* ── products I use · #product ───────────────────────────────────────────
   Small tiles with a price and a group tag; the sidebar filters on #group-*. */

.c-product { flex-direction: row; align-items: center; gap: var(--space-3); padding: var(--space-3); }
.c-product .c__body { padding: 0; gap: 2px; }
.c-product .c__title { font-size: var(--text-base); }

.c-product .c__thumb {
	width: 3.25rem;
	height: 3.25rem;
	border-radius: var(--radius-md);
	background: var(--bg-muted);
	flex: none;
	display: grid;
	place-items: center;
	overflow: hidden;
}

.c-product .c__price {
	margin-left: auto;
	font-family: var(--font-slate);
	font-size: var(--text-xs);
	color: var(--fg-default);
	font-variant-numeric: tabular-nums;
	flex: none;
}

/* ── shop · #shop ────────────────────────────────────────────────────────
   A ticket notch, so a paid thing never looks like a free thing. */

.c-shop {
	mask-image:
		radial-gradient(circle at 0 62%, transparent 0 10px, #000 10px),
		radial-gradient(circle at 100% 62%, transparent 0 10px, #000 10px);
	mask-composite: intersect;
	-webkit-mask-composite: source-in;
}

.c-shop .c__price {
	font-family: var(--font-display);
	font-size: var(--text-lg);
	font-weight: var(--weight-bold);
	font-variant-numeric: tabular-nums;
}

/* ── timeline · #timeline ────────────────────────────────────────────────
   A year gutter and a node on the rail. The card itself is bare. */

.c-timeline {
	flex-direction: row;
	gap: var(--space-5);
	background: transparent;
	border: 0;
	border-radius: 0;
	padding-bottom: var(--space-8);
}

.c-timeline:hover { transform: none; box-shadow: none; }

.c-timeline__year {
	font-family: var(--font-slate);
	font-size: var(--text-xs);
	letter-spacing: var(--tracking-slate);
	color: var(--fg-subtle);
	width: 4rem;
	flex: none;
	padding-top: 2px;
	font-variant-numeric: tabular-nums;
}

.c-timeline__rail {
	position: relative;
	width: var(--border-hair);
	background: var(--line-default);
	flex: none;
}

.c-timeline__rail::before {
	content: '';
	position: absolute;
	top: 4px;
	left: 50%;
	width: var(--dot-lg);
	height: var(--dot-lg);
	transform: translateX(-50%);
	border-radius: var(--radius-full);
	background: var(--bg-canvas);
	border: var(--border-2) solid var(--accent);
}

.c-timeline .c__body { padding: 0 0 0 var(--space-2); }

/* ── changelog · #changelog ──────────────────────────────────────────────
   Version chip plus a type dot: shipped / fixed / broke. */

.c-changelog { flex-direction: row; align-items: baseline; gap: var(--space-4); padding: var(--space-4) var(--space-5); }
.c-changelog .c__body { padding: 0; }

.c-changelog__v {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-wide);
	padding: 2px 6px;
	border-radius: var(--radius-sm);
	background: var(--bg-sunken);
	border: var(--border-hair) solid var(--line-subtle);
	flex: none;
	font-variant-numeric: tabular-nums;
}

.c-changelog[data-type='shipped'] .c-changelog__v { color: var(--success-fg); }
.c-changelog[data-type='fixed'] .c-changelog__v { color: var(--info-fg); }
.c-changelog[data-type='broke'] .c-changelog__v { color: var(--danger-fg); }

/* ── newsletters · #newsletter ───────────────────────────────────────────
   Issue number and a rule that reads as a folded edge. */

.c-newsletter { background: var(--bg-sunken); }

.c-newsletter::before {
	content: '';
	position: absolute;
	inset: 0 0 auto;
	height: 2px;
	background: repeating-linear-gradient(to right, var(--line-strong) 0 8px, transparent 8px 14px);
}

.c-newsletter .c__issue {
	font-family: var(--font-display);
	font-size: var(--text-2xl);
	font-weight: var(--weight-bold);
	line-height: 1;
	font-variant-numeric: tabular-nums;
	color: var(--fg-faint);
}

/* ── snippets · #snippet ─────────────────────────────────────────────────
   Language tag plus two lines of the code itself as the preview. */

.c-snippet .c__code {
	font-family: var(--font-slate);
	font-size: var(--text-xs);
	line-height: 1.7;
	background: var(--bg-sunken);
	border-top: var(--border-hair) solid var(--line-subtle);
	padding: var(--space-3) var(--space-5);
	white-space: pre;
	overflow: hidden;
	color: var(--fg-muted);
	max-height: 4.6rem;
	-webkit-mask-image: linear-gradient(to bottom, #000 55%, transparent);
	mask-image: linear-gradient(to bottom, #000 55%, transparent);
}

.c-snippet .c__lang {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-accent);
}

/* ── prompts · #prompt ───────────────────────────────────────────────────
   A chat bubble, because that is what a prompt actually is. */

.c-prompt .c__bubble {
	position: relative;
	background: var(--bg-sunken);
	border: var(--border-hair) solid var(--line-subtle);
	border-radius: var(--radius-lg) var(--radius-lg) var(--radius-lg) var(--radius-xs);
	padding: var(--space-4);
	font-size: var(--text-sm);
	line-height: var(--leading-normal);
	margin: var(--space-5) var(--space-5) 0;
}

.c-prompt .c__model {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-subtle);
}

/* ── experiences · #experience ───────────────────────────────────────────
   Photo first, caption under it — a moment, not an article. */

.c-experience { border: 0; background: transparent; }
.c-experience:hover { transform: none; box-shadow: none; }
.c-experience .c__media { border-radius: var(--radius-media); aspect-ratio: var(--ratio-photo); }
.c-experience .c__body { padding: var(--space-3) 0 0; }
.c-experience .c__title { font-size: var(--text-base); font-weight: var(--weight-semibold); }

/* ── travel · trip container · #trip ─────────────────────────────────────
   Coordinates and a country, the same devices as the travel thumbnail. */

.c-trip .c__coords {
	position: absolute;
	right: var(--space-3);
	top: var(--space-3);
	z-index: var(--z-raised);
	text-align: right;
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-wide);
	color: var(--craft);
	line-height: 1.5;
	text-shadow: 0 1px 6px rgb(0 0 0 / 0.6);
}

.c-trip .c__media::after {
	content: '';
	position: absolute;
	left: 0;
	right: 0;
	top: 62%;
	height: 1px;
	background: linear-gradient(to right, transparent, rgb(217 163 58 / 0.7) 20%, rgb(217 163 58 / 0.7) 80%, transparent);
}

/* ── travel post · #travel ───────────────────────────────────────────────── */

.c-travel .c__day {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--craft-fg);
}

/* ── docs · #docs ────────────────────────────────────────────────────────
   A row with a section label and an arrow. No media, ever. */

.c-doc {
	flex-direction: row;
	align-items: center;
	gap: var(--space-4);
	padding: var(--space-4) var(--space-5);
}

.c-doc .c__body { padding: 0; }
.c-doc .c__arrow { margin-left: auto; color: var(--fg-faint); flex: none; }
.c-doc:hover .c__arrow { color: var(--fg-accent); }

/* ── guides · #guide ─────────────────────────────────────────────────────── */

.c-guide .c__steps {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-subtle);
	display: flex;
	align-items: center;
	gap: var(--space-2);
}

.c-guide .c__steps::before {
	content: '';
	width: var(--space-8);
	height: 3px;
	border-radius: var(--radius-pill);
	background: linear-gradient(to right, var(--accent) var(--value, 40%), var(--bg-muted) 0);
}

/* ── tags & authors ──────────────────────────────────────────────────────── */

.c-tag {
	flex-direction: row;
	align-items: center;
	gap: var(--space-3);
	padding: var(--space-3) var(--space-4);
	border-radius: var(--radius-pill);
}

.c-tag .c__count {
	margin-left: auto;
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	color: var(--fg-faint);
	font-variant-numeric: tabular-nums;
}

.c-author { flex-direction: row; align-items: center; gap: var(--space-4); padding: var(--space-4); }
.c-author .c__body { padding: 0; gap: 2px; }

/* ── The deck ────────────────────────────────────────────────────────────
   What a collection template renders into. Sizes match the density each
   collection wants. */

.deck-c { display: grid; gap: var(--grid-gap); grid-template-columns: repeat(auto-fill, minmax(min(19rem, 100%), 1fr)); }
.deck-c-sm { grid-template-columns: repeat(auto-fill, minmax(min(13rem, 100%), 1fr)); }
.deck-c-lg { grid-template-columns: repeat(auto-fill, minmax(min(24rem, 100%), 1fr)); }
.deck-c-list { display: flex; flex-direction: column; gap: var(--space-2); }

/* A collection card that is itself a link: no underline anywhere inside it. */
a.c,
a.c:hover { text-decoration: none; color: inherit; }  /* one card per routes.yaml collection   */
/* =============================================================================
   14 · NAVIGATION
   Nav island, tabs, breadcrumb, pagination, table of contents, and the
   course/series progress rail.

   House rule: the active state is ALWAYS the signal dot or a 2px accent rule —
   never a colour fill, never a pill of background.
   ========================================================================== */

:root {
	--nav-h: 3.5rem;

	/* Space between the floating island and the first thing under it. The
	   island is sticky, so on scroll it passes OVER the content — without a
	   real gap the first heading of a page ends up tucked beneath it, which is
	   worst on a narrow screen where there is no room for the eye to separate
	   the two. Wider on small viewports for exactly that reason. */
	--nav-gap: var(--space-6);

	/* How far a full-bleed hero has to pull up to sit UNDER the island rather
	   than below it. Derived from the three things it depends on, so no page
	   hardcodes the sum and none of them drift when --nav-gap changes. */
	--nav-bleed: calc(var(--nav-h) + var(--space-3) * 2 + var(--nav-gap));
}

@media (max-width: 48rem) {
	:root { --nav-gap: var(--space-8); }
}

/* ── The nav island ──────────────────────────────────────────────────────
   Floats on the canvas, edges aligned to the content column because both
   derive from --w-site and --gutter. */

.nav-shell {
	position: sticky;
	top: var(--space-3);
	z-index: var(--z-nav);
	width: 100%;
	max-width: calc(var(--w-site) + var(--gutter) * 2);
	margin-inline: auto;
	margin-bottom: var(--nav-gap);
	padding-inline: var(--gutter);
}

.nav-bar {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-4);
	height: var(--nav-h);
	padding-inline: var(--space-4);
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-pill);
	background: color-mix(in srgb, var(--bg-canvas) 82%, transparent);
	backdrop-filter: saturate(180%) blur(14px);
	box-shadow: var(--shadow-1);
}

.nav-links {
	display: flex;
	align-items: center;
	gap: var(--space-1);
}

.nav-link {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	height: var(--control-sm);
	padding-inline: var(--space-3);
	border-radius: var(--radius-pill);
	color: var(--fg-muted);
	font-size: var(--text-sm);
	font-weight: var(--weight-medium);
	text-decoration: none;
	transition: var(--t-colors);
	white-space: nowrap;
}

.nav-link:hover { color: var(--fg-default); background: var(--hover-wash); }

/* Active = a dot, not a fill. */
.nav-link[aria-current]::before {
	content: '';
	width: var(--dot-sm);
	height: var(--dot-sm);
	border-radius: var(--radius-full);
	background: var(--accent);
	flex: none;
}

.nav-link[aria-current] { color: var(--fg-default); font-weight: var(--weight-semibold); }

.nav-link > svg,
.nav-link__icon { width: var(--icon-sm); height: var(--icon-sm); flex: none; opacity: 0.7; }

/* Contexts. Series is always dark and ignores the theme — it is cinema.
   Course welds a progress rail to the bottom edge. */

.nav-series {
	background: var(--ink-950);
	border-color: rgb(255 255 255 / 0.12);
	color: var(--ink-50);
	color-scheme: dark;
}

.nav-series .nav-link { color: rgb(255 255 255 / 0.62); }
.nav-series .nav-link:hover,
.nav-series .nav-link[aria-current] { color: var(--pure-white); }

.nav-course { position: relative; overflow: hidden; }

.nav-course .nav-rail {
	position: absolute;
	left: 0;
	bottom: 0;
	height: var(--border-2);
	background: var(--accent);
	border-radius: var(--radius-pill);
	transition: width var(--dur-4) var(--ease-out);
}

/* ── Tabs ────────────────────────────────────────────────────────────────── */

.tabs {
	display: flex;
	gap: var(--space-5);
	border-bottom: var(--border-hair) solid var(--line-default);
	overflow-x: auto;
	scrollbar-width: none;
}

.tabs::-webkit-scrollbar { display: none; }

.tab {
	position: relative;
	padding: var(--space-3) 0;
	border: 0;
	background: none;
	color: var(--fg-muted);
	font-size: var(--text-sm);
	font-weight: var(--weight-medium);
	text-decoration: none;
	white-space: nowrap;
	cursor: pointer;
	transition: var(--t-colors);
}

.tab:hover { color: var(--fg-default); }

.tab[aria-selected='true'],
.tab[aria-current='page'] {
	color: var(--fg-default);
	font-weight: var(--weight-semibold);
}

.tab[aria-selected='true']::after,
.tab[aria-current='page']::after {
	content: '';
	position: absolute;
	left: 0;
	right: 0;
	bottom: -1px;
	height: var(--border-2);
	background: var(--accent);
	border-radius: var(--radius-pill);
}

/* ── Breadcrumb ──────────────────────────────────────────────────────────── */

.breadcrumb {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: var(--space-2);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-subtle);
}

.breadcrumb a { color: inherit; text-decoration: none; }
.breadcrumb a:hover { color: var(--fg-default); }

.breadcrumb li + li::before {
	content: '/';
	margin-right: var(--space-2);
	color: var(--fg-faint);
}

.breadcrumb li { display: inline-flex; align-items: center; }
.breadcrumb [aria-current='page'] { color: var(--fg-default); }

/* ── Pagination ──────────────────────────────────────────────────────────── */

.pagination {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-4);
	padding-block: var(--space-6);
}

.pagination__pages {
	display: flex;
	align-items: center;
	gap: var(--space-1);
	font-family: var(--font-slate);
	font-size: var(--text-xs);
	font-variant-numeric: tabular-nums;
	color: var(--fg-subtle);
}

.page-dot {
	display: inline-grid;
	place-items: center;
	min-width: var(--control-sm);
	height: var(--control-sm);
	padding-inline: var(--space-2);
	border-radius: var(--radius-md);
	color: var(--fg-muted);
	text-decoration: none;
	transition: var(--t-colors);
}

.page-dot:hover { background: var(--hover-wash); color: var(--fg-default); }

.page-dot[aria-current='page'] {
	background: var(--bg-inverse);
	color: var(--fg-on-inverse);
}

/* Lesson/episode pager — "previous · next" with titles, for the player. */
.pager {
	display: grid;
	gap: var(--space-3);
	grid-template-columns: repeat(2, minmax(0, 1fr));
}

.pager__item {
	display: flex;
	flex-direction: column;
	gap: var(--space-1);
	padding: var(--space-4);
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-card);
	text-decoration: none;
	color: inherit;
	transition: var(--t-colors);
}

.pager__item:hover { border-color: var(--line-strong); background: var(--bg-sunken); }
.pager__item-next { text-align: right; }

.pager__dir {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
}

.pager__title {
	font-weight: var(--weight-semibold);
	font-size: var(--text-sm);
	line-height: var(--leading-snug);
}

@media (max-width: 34rem) {
	.pager { grid-template-columns: minmax(0, 1fr); }
	.pager__item-next { text-align: left; }
}

/* ── Table of contents ───────────────────────────────────────────────────
   Sticky rail beside long-form content. Active heading gets the accent rule. */

.toc {
	display: flex;
	flex-direction: column;
	gap: var(--space-1);
	border-left: var(--border-hair) solid var(--line-default);
	padding-left: var(--space-4);
	font-size: var(--text-sm);
}

.toc__title {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
	margin-bottom: var(--space-2);
}

.toc a {
	position: relative;
	color: var(--fg-muted);
	text-decoration: none;
	padding-block: var(--space-1);
	transition: var(--t-colors);
	line-height: var(--leading-snug);
}

.toc a:hover { color: var(--fg-default); }

.toc a[aria-current='true'] { color: var(--fg-default); font-weight: var(--weight-semibold); }

.toc a[aria-current='true']::before {
	content: '';
	position: absolute;
	left: calc(var(--space-4) * -1 - 1px);
	top: var(--space-1);
	bottom: var(--space-1);
	width: var(--border-2);
	background: var(--accent);
	border-radius: var(--radius-pill);
}

.toc-nested { padding-left: var(--space-4); }         /* island, tabs, crumbs, pager, toc      */
/* =============================================================================
   16 · FEEDBACK
   Alerts, toasts, progress, loading, empty states, tooltips.

   Loading has five treatments, each mapped to a KIND of wait:
     shimmer  · content whose shape we know      (cards, text)
     breathe  · one big unknown block            (hero, media)
     scanline · media decoding                   (player, poster)
     progress · a known duration                 (upload, course)
     buffer   · a live stream catching up        (player)
   A spinner is a button state only — never a page.
   ========================================================================== */

/* ── Alert / callout ─────────────────────────────────────────────────────── */

.alert {
	display: flex;
	gap: var(--space-3);
	padding: var(--space-4) var(--space-5);
	border-radius: var(--radius-lg);
	background: var(--bg-sunken);
	box-shadow: inset var(--border-3) 0 0 0 var(--line-strong);
	font-size: var(--text-sm);
	line-height: var(--leading-normal);
}

.alert__title {
	font-weight: var(--weight-semibold);
	margin-bottom: 2px;
}

/* Tone also carries an icon/label in the markup — colour is never alone. */
.alert-success { background: var(--success-bg); color: var(--success-fg); box-shadow: inset var(--border-3) 0 0 0 var(--success-line); }
.alert-warning { background: var(--warning-bg); color: var(--warning-fg); box-shadow: inset var(--border-3) 0 0 0 var(--warning-line); }
.alert-danger  { background: var(--danger-bg);  color: var(--danger-fg);  box-shadow: inset var(--border-3) 0 0 0 var(--danger-line); }
.alert-info    { background: var(--info-bg);    color: var(--info-fg);    box-shadow: inset var(--border-3) 0 0 0 var(--info-line); }
.alert-signal  { background: var(--accent-soft); color: var(--accent-soft-fg); box-shadow: inset var(--border-3) 0 0 0 var(--accent); }

/* ── Toast ───────────────────────────────────────────────────────────────── */

.toast-region {
	position: fixed;
	right: var(--space-4);
	bottom: var(--space-4);
	z-index: var(--z-toast);
	display: flex;
	flex-direction: column;
	gap: var(--space-2);
	pointer-events: none;
}

.toast {
	display: flex;
	align-items: center;
	gap: var(--space-3);
	min-width: 16rem;
	max-width: min(24rem, calc(100vw - var(--space-8)));
	padding: var(--space-3) var(--space-4);
	border-radius: var(--radius-md);
	background: var(--bg-inverse);
	color: var(--fg-on-inverse);
	box-shadow: var(--shadow-4);
	font-size: var(--text-sm);
	pointer-events: auto;
	animation: fx-rise var(--dur-3) var(--ease-out) both;
}

/* ── Empty state ─────────────────────────────────────────────────────────
   An invitation, never an apology. Always offers the next action. */

.empty {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--space-3);
	text-align: center;
	padding: var(--space-12) var(--space-6);
	border: var(--border-2) dashed var(--line-default);
	border-radius: var(--radius-card);
	color: var(--fg-muted);
}

.empty__title {
	font-family: var(--font-heading-effective);
	font-size: var(--text-xl);
	font-weight: var(--weight-semibold);
	color: var(--fg-default);
}

.empty__body { font-size: var(--text-sm); max-width: 40ch; }    /* alert, toast, empty state             */
/* =============================================================================
   17 · MEDIA
   Player chrome, play affordances, episode rows, quote and figure blocks.
   This is the part of the system the site is actually about.
   ========================================================================== */

/* ── Player ──────────────────────────────────────────────────────────────── */

.player {
	position: relative;
	background: var(--bg-media);
	border-radius: var(--radius-media);
	overflow: hidden;
	aspect-ratio: var(--ratio-video);
	isolation: isolate;
	color-scheme: dark;
}

.player > img,
.player > video,
.player > iframe {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	border: 0;
}

/* Control bar. Appears on hover/focus-within, always visible on touch. */
.player__bar {
	position: absolute;
	inset: auto 0 0;
	z-index: var(--z-raised);
	display: flex;
	align-items: center;
	gap: var(--space-3);
	padding: var(--space-3) var(--space-4);
	background: linear-gradient(to top, rgb(0 0 0 / 0.82), transparent);
	color: var(--pure-white);
	opacity: 1;
	transition: opacity var(--dur-2) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
	.player__bar { opacity: 0; }
	.player:hover .player__bar,
	.player:focus-within .player__bar { opacity: 1; }
}

.player__rail {
	position: relative;
	flex: 1;
	height: 4px;
	border-radius: var(--radius-pill);
	background: rgb(255 255 255 / 0.28);
	cursor: pointer;
}

.player__played {
	position: absolute;
	inset: 0 auto 0 0;
	width: var(--value, 0%);
	border-radius: inherit;
	background: var(--accent);
}

/* The scrub head only appears when the rail is hovered — quiet by default. */
.player__played::after {
	content: '';
	position: absolute;
	right: -5px;
	top: 50%;
	width: 10px;
	height: 10px;
	border-radius: var(--radius-full);
	background: var(--accent);
	transform: translateY(-50%) scale(0);
	transition: transform var(--dur-1) var(--ease-out);
}

.player__rail:hover .player__played::after { transform: translateY(-50%) scale(1); }

.player__time {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	font-variant-numeric: tabular-nums;
	letter-spacing: var(--tracking-wide);
	color: rgb(255 255 255 / 0.82);
	white-space: nowrap;
}

/* ── Play button ─────────────────────────────────────────────────────────
   Centred over a poster. Grows on hover; the triangle is drawn in CSS. */

.play {
	position: absolute;
	inset: 0;
	z-index: var(--z-raised);
	display: grid;
	place-items: center;
	border: 0;
	background: none;
	cursor: pointer;
}

.play__disc {
	display: grid;
	place-items: center;
	width: 4rem;
	height: 4rem;
	border-radius: var(--radius-full);
	background: var(--accent);
	box-shadow: var(--shadow-3);
	transition: var(--t-transform), var(--t-shadow);
}

.play__disc::before {
	content: '';
	width: 0;
	height: 0;
	margin-left: 4px;
	border-left: 14px solid var(--fg-on-accent);
	border-top: 9px solid transparent;
	border-bottom: 9px solid transparent;
}

@media (hover: hover) and (pointer: fine) {
	.play:hover .play__disc { transform: scale(1.08); box-shadow: var(--shadow-signal), var(--shadow-3); }
}

.play-sm .play__disc { width: 2.75rem; height: 2.75rem; }
.play-sm .play__disc::before { border-left-width: 10px; border-top-width: 6px; border-bottom-width: 6px; }

/* Ghost variant for use over busy artwork. */
.play-ghost .play__disc {
	background: rgb(0 0 0 / 0.5);
	backdrop-filter: blur(6px);
	box-shadow: inset 0 0 0 var(--border-2) rgb(255 255 255 / 0.55);
}

.play-ghost .play__disc::before { border-left-color: var(--pure-white); }

/* ── Poster ──────────────────────────────────────────────────────────────
   A still with the slate baked in — the thumbnail as it ships to a platform. */

.poster {
	position: relative;
	aspect-ratio: var(--ratio-video);
	border-radius: var(--radius-media);
	overflow: hidden;
	background: var(--bg-media);
}

.poster__label {
	position: absolute;
	left: var(--space-4);
	bottom: var(--space-4);
	right: var(--space-4);
	z-index: var(--z-raised);
	color: var(--pure-white);
	display: flex;
	flex-direction: column;
	gap: var(--space-1);
}

.poster::after {
	content: '';
	position: absolute;
	inset: 0;
	background: linear-gradient(to top, rgb(0 0 0 / 0.75), rgb(0 0 0 / 0.15) 55%, transparent);
}

.poster__label { z-index: calc(var(--z-raised) + 1); }

/* ── Episode row ─────────────────────────────────────────────────────────
   The list beside a player. Current row carries the accent edge. */

.episode {
	display: grid;
	grid-template-columns: 7.5rem 1fr;
	gap: var(--space-3);
	align-items: start;
	padding: var(--space-3);
	border-radius: var(--radius-lg);
	text-decoration: none;
	color: inherit;
	transition: var(--t-colors);
}

.episode:hover { background: var(--hover-wash); }

.episode[aria-current='true'] {
	background: var(--accent-soft);
	box-shadow: inset var(--border-3) 0 0 0 var(--accent);
}

.episode__thumb {
	position: relative;
	aspect-ratio: var(--ratio-video);
	border-radius: var(--radius-sm);
	overflow: hidden;
	background: var(--bg-media);
}

.episode__title {
	font-size: var(--text-sm);
	font-weight: var(--weight-semibold);
	line-height: var(--leading-snug);
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
	line-clamp: 2;
	overflow: hidden;
}

.episode__meta {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-subtle);
	margin-top: 2px;
}

@media (max-width: 26rem) {
	.episode { grid-template-columns: 5rem 1fr; }
}       /* player, poster, episode row, promo    */
/* =============================================================================
   27 · COMPOSITES
   The container-module organs: a course's curriculum, a series' episode
   panel, a project's build log, a trip's itinerary. Each one is the same
   idea — an ordered list that knows where you are — wearing its collection's
   clothes. All state lives in ARIA (aria-current, [data-done]), never in a
   styling-only class.
   ========================================================================== */

/* ── Curriculum — the course syllabus ────────────────────────────────────────
   <section class="curriculum">
     <header class="curriculum__head"> title · meta · .progress </header>
     <details class="curriculum__module" open>
       <summary> … </summary>
       <ol class="curriculum__lessons"> <li><a class="lesson-row"> … </ol>
     </details>
   </section>
   ------------------------------------------------------------------------- */

.curriculum {
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-card);
	background: var(--bg-surface);
	overflow: hidden;
}

.curriculum__head {
	padding: var(--space-5) var(--space-6);
	border-bottom: var(--border-hair) solid var(--line-default);
	display: flex;
	align-items: center;
	justify-content: space-between;
	flex-wrap: wrap;
	gap: var(--space-3) var(--space-5);
}

.curriculum__title {
	font-family: var(--font-display);
	font-size: var(--text-lg);
	font-weight: var(--weight-semibold);
	letter-spacing: var(--tracking-tight);
}

.curriculum__meta {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
}

.curriculum__head .progress { flex: 1 1 100%; }

/* Module = one <details>. The summary is the module row. */
.curriculum__module { border-bottom: var(--border-hair) solid var(--line-subtle); }
.curriculum__module:last-child { border-bottom: 0; }

.curriculum__module > summary {
	list-style: none;
	cursor: pointer;
	display: flex;
	align-items: baseline;
	gap: var(--space-3);
	padding: var(--space-4) var(--space-6);
	transition: var(--t-colors);
}

.curriculum__module > summary::-webkit-details-marker { display: none; }
.curriculum__module > summary:hover { background: var(--bg-sunken); }

.curriculum__no {
	font-family: var(--font-slate);
	font-size: var(--text-xs);
	color: var(--fg-faint);
	font-variant-numeric: tabular-nums;
}

.curriculum__module-title {
	font-weight: var(--weight-semibold);
	font-size: var(--text-sm);
	flex: 1;
}

.curriculum__count {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	color: var(--fg-faint);
	white-space: nowrap;
}

/* The chevron is drawn, not shipped: two strokes that fold. */
.curriculum__module > summary::after {
	content: '';
	align-self: center;
	width: 0.5rem;
	height: 0.5rem;
	border-right: var(--border-2) solid var(--fg-faint);
	border-bottom: var(--border-2) solid var(--fg-faint);
	transform: rotate(45deg);
	transition: var(--t-transform);
	flex: none;
	margin-top: -0.25rem;
}

.curriculum__module[open] > summary::after { transform: rotate(-135deg); margin-top: 0.15rem; }

.curriculum__lessons {
	list-style: none;
	margin: 0;
	padding: 0 0 var(--space-3);
}

/* One lesson row. aria-current="true" = you are here; [data-done] = watched. */
.lesson-row {
	display: flex;
	align-items: center;
	gap: var(--space-3);
	padding: var(--space-2) var(--space-6) var(--space-2) var(--space-8);
	font-size: var(--text-sm);
	color: var(--fg-subtle);
	text-decoration: none;
	transition: var(--t-colors);
}

.lesson-row:hover { color: var(--fg-default); background: var(--bg-sunken); }

/* The tick circle: empty ring → filled on done → accent ring when current. */
.lesson-row__tick {
	width: 1.1rem;
	height: 1.1rem;
	flex: none;
	border: var(--border-2) solid var(--line-strong);
	border-radius: var(--radius-full);
	display: grid;
	place-items: center;
	font-size: 0.6rem;
	color: var(--fg-on-accent);
	transition: var(--t-colors);
}

.lesson-row[data-done] .lesson-row__tick {
	background: var(--fg-default);
	border-color: var(--fg-default);
}

.lesson-row[data-done] .lesson-row__tick::before { content: '✓'; color: var(--bg-canvas); }

.lesson-row[aria-current] {
	color: var(--fg-default);
	font-weight: var(--weight-semibold);
}

.lesson-row[aria-current] .lesson-row__tick {
	border-color: var(--accent);
	box-shadow: 0 0 0 3px var(--accent-ring);
}

.lesson-row__title { flex: 1; min-width: 0; }

.lesson-row__len {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	color: var(--fg-faint);
	font-variant-numeric: tabular-nums;
}

/* Free-preview lessons advertise it. */
.lesson-row__free {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-accent);
}

/* ── Episode panel — the list beside a player ────────────────────────────────
   Wraps 26-media's .episode rows with a header + its own scroll.
   ------------------------------------------------------------------------- */

.ep-panel {
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-card);
	background: var(--bg-surface);
	display: flex;
	flex-direction: column;
	overflow: hidden;
	max-height: 28rem;
}

.ep-panel__head {
	padding: var(--space-4) var(--space-5);
	border-bottom: var(--border-hair) solid var(--line-default);
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-3);
	flex: none;
}

.ep-panel__title { font-weight: var(--weight-semibold); font-size: var(--text-sm); }

.ep-panel__count {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
}

.ep-panel__list {
	overflow-y: auto;
	padding: var(--space-2);
	display: flex;
	flex-direction: column;
	gap: 2px;
	scrollbar-width: thin;
}

/* ── Build log — the project timeline ────────────────────────────────────────
   <ol class="buildlog"> <li class="buildlog__step" data-kind="start|ship"> …
   A vertical rail; the segment above the current step is "walked".
   ------------------------------------------------------------------------- */

.buildlog {
	list-style: none;
	margin: 0;
	padding: 0;
	position: relative;
}

.buildlog::before {
	content: '';
	position: absolute;
	left: 0.7rem;
	top: 0.5rem;
	bottom: 0.5rem;
	width: var(--border-hair);
	background: var(--line-default);
}

.buildlog__step { position: relative; padding: var(--space-2) 0 var(--space-2) var(--space-8); }

.buildlog__node {
	position: absolute;
	left: 0;
	top: 50%;
	transform: translateY(-50%);
	width: 1.45rem;
	height: 1.45rem;
	border-radius: var(--radius-full);
	border: var(--border-2) solid var(--line-strong);
	background: var(--bg-canvas);
	display: grid;
	place-items: center;
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	color: var(--fg-subtle);
	font-variant-numeric: tabular-nums;
}

.buildlog__step[data-kind='start'] .buildlog__node::before { content: '▸'; }
.buildlog__step[data-kind='ship'] .buildlog__node::before { content: '✓'; }

.buildlog__step[data-done] .buildlog__node {
	background: var(--fg-default);
	border-color: var(--fg-default);
	color: var(--bg-canvas);
}

.buildlog__step[aria-current] .buildlog__node {
	border-color: var(--accent);
	color: var(--accent);
	box-shadow: 0 0 0 3px var(--accent-ring);
}

.buildlog__link {
	display: block;
	text-decoration: none;
	color: var(--fg-subtle);
	font-size: var(--text-sm);
	transition: var(--t-colors);
}

.buildlog__link:hover { color: var(--fg-default); }
.buildlog__step[aria-current] .buildlog__link { color: var(--fg-default); font-weight: var(--weight-semibold); }

.buildlog__date {
	display: block;
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	color: var(--fg-faint);
	margin-top: 2px;
}

/* ── Itinerary — the trip, day by day ────────────────────────────────────────
   Same rail as the build log, but the node is the day chip and each entry
   can carry a photo strip.
   ------------------------------------------------------------------------- */

.itinerary { list-style: none; margin: 0; padding: 0; position: relative; }

.itinerary::before {
	content: '';
	position: absolute;
	left: 1.1rem;
	top: 1rem;
	bottom: 1rem;
	width: var(--border-hair);
	background: var(--line-default);
}

.itinerary__day { position: relative; padding: var(--space-3) 0 var(--space-3) var(--space-12); }

.itinerary__chip {
	position: absolute;
	left: 0;
	top: var(--space-3);
	width: 2.25rem;
	height: 2.25rem;
	border-radius: var(--radius-md);
	border: var(--border-hair) solid var(--line-default);
	background: var(--bg-surface);
	display: grid;
	place-content: center;
	text-align: center;
	line-height: 1;
}

.itinerary__chip b {
	font-family: var(--font-display);
	font-size: var(--text-sm);
	font-weight: var(--weight-bold);
	font-variant-numeric: tabular-nums;
}

.itinerary__chip span {
	font-family: var(--font-slate);
	font-size: 0.55rem;
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
}

.itinerary__title { font-weight: var(--weight-semibold); font-size: var(--text-sm); }

.itinerary__note {
	font-size: var(--text-sm);
	color: var(--fg-subtle);
	margin-top: 2px;
	max-width: var(--measure-ui);
}

.itinerary__stops {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-1);
	margin-top: var(--space-2);
}   /* curriculum, episodes, buildlog, trip  */
/* =============================================================================
   28 · DISCLOSURE
   Things that open, close, cycle and slide: close button, collapse, accordion,
   dropdown, list group, carousel, marquee. Everything rides native elements —
   <details>, scroll-snap — so it all works with zero JS.
   ========================================================================== */

/* ── Close button ────────────────────────────────────────────────────────── */

.btn-close {
	width: var(--control-sm);
	height: var(--control-sm);
	flex: none;
	display: inline-grid;
	place-items: center;
	border: 0;
	background: transparent;
	border-radius: var(--radius-md);
	color: var(--fg-subtle);
	cursor: pointer;
	position: relative;
	transition: var(--t-colors);
}

.btn-close::before,
.btn-close::after {
	content: '';
	position: absolute;
	width: 1rem;
	height: var(--border-2);
	background: currentColor;
	border-radius: var(--radius-full);
	transform: rotate(45deg);
}

.btn-close::after { transform: rotate(-45deg); }
.btn-close:hover { color: var(--fg-default); background: var(--bg-sunken); }
.btn-close-inverse { color: var(--fg-on-inverse); }
.btn-close-inverse:hover { color: var(--fg-on-inverse); background: rgb(255 255 255 / 0.12); }

/* ── Collapse — one <details>, styled ────────────────────────────────────── */

.collapse {
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-md);
	background: var(--bg-surface);
}

.collapse > summary {
	list-style: none;
	cursor: pointer;
	display: flex;
	align-items: center;
	gap: var(--space-3);
	padding: var(--space-3) var(--space-4);
	font-weight: var(--weight-medium);
	font-size: var(--text-sm);
	transition: var(--t-colors);
}

.collapse > summary::-webkit-details-marker { display: none; }
.collapse > summary:hover { background: var(--bg-sunken); }

.collapse > summary::after {
	content: '';
	margin-left: auto;
	width: 0.45rem;
	height: 0.45rem;
	border-right: var(--border-2) solid var(--fg-faint);
	border-bottom: var(--border-2) solid var(--fg-faint);
	transform: rotate(45deg) translateY(-2px);
	transition: var(--t-transform);
	flex: none;
}

.collapse[open] > summary::after { transform: rotate(-135deg) translateY(-2px); }

.collapse__body {
	padding: var(--space-1) var(--space-4) var(--space-4);
	font-size: var(--text-sm);
	color: var(--fg-subtle);
	line-height: var(--leading-relaxed);
}

/* ── Accordion — a stack of collapses welded into one surface ────────────── */

.acc {
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-card);
	background: var(--bg-surface);
	overflow: hidden;
}

.acc > .collapse { border: 0; border-radius: 0; background: none; }
.acc > .collapse + .collapse { border-top: var(--border-hair) solid var(--line-subtle); }
.acc > .collapse[open] > summary { color: var(--fg-default); }

/* The signal marks the open panel — one dot, as always. */
.acc > .collapse[open] > summary::before {
	content: '';
	width: var(--dot-sm, 6px);
	height: var(--dot-sm, 6px);
	border-radius: var(--radius-full);
	background: var(--accent);
	flex: none;
}

/* ── Dropdown — <details> again, panel absolutely positioned ─────────────── */

.dropdown { position: relative; display: inline-block; }
.dropdown > summary { list-style: none; cursor: pointer; }
.dropdown > summary::-webkit-details-marker { display: none; }

/* Caret on the trigger button. */
.dropdown > summary .btn::after,
.dropdown > summary.btn::after {
	content: '';
	width: 0.4rem;
	height: 0.4rem;
	border-right: var(--border-2) solid currentColor;
	border-bottom: var(--border-2) solid currentColor;
	transform: rotate(45deg) translateY(-1px);
	margin-left: var(--space-1);
	transition: var(--t-transform);
}

.dropdown[open] > summary .btn::after,
.dropdown[open] > summary.btn::after { transform: rotate(-135deg) translateY(-1px); }

.dropdown__menu {
	position: absolute;
	top: calc(100% + var(--space-2));
	left: 0;
	min-width: 12rem;
	z-index: var(--z-popover, 40);
	background: var(--bg-surface);
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-lg);
	box-shadow: var(--shadow-3);
	padding: var(--space-2);
	display: flex;
	flex-direction: column;
	gap: 1px;
}

.dropdown-end .dropdown__menu { left: auto; right: 0; }

.dropdown__item {
	display: flex;
	align-items: center;
	gap: var(--space-2);
	padding: var(--space-2) var(--space-3);
	border-radius: var(--radius-sm);
	font-size: var(--text-sm);
	color: var(--fg-default);
	text-decoration: none;
	white-space: nowrap;
	transition: var(--t-colors);
}

.dropdown__item:hover { background: var(--bg-sunken); }
.dropdown__item[aria-current] { font-weight: var(--weight-semibold); }
.dropdown__item .icon { width: 1rem; height: 1rem; opacity: 0.7; }

.dropdown__divider { height: var(--border-hair); background: var(--line-subtle); margin: var(--space-1) 0; border: 0; }

.dropdown__head {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
	padding: var(--space-2) var(--space-3) var(--space-1);
}

/* ── List group ──────────────────────────────────────────────────────────── */

.list-group {
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-card);
	background: var(--bg-surface);
	overflow: hidden;
	display: flex;
	flex-direction: column;
	margin: 0;
	padding: 0;
	list-style: none;
}

.list-group__item {
	display: flex;
	align-items: center;
	gap: var(--space-3);
	padding: var(--space-3) var(--space-4);
	font-size: var(--text-sm);
	color: var(--fg-default);
	text-decoration: none;
}

.list-group__item + .list-group__item { border-top: var(--border-hair) solid var(--line-subtle); }

a.list-group__item,
button.list-group__item { cursor: pointer; transition: var(--t-colors); }
a.list-group__item:hover,
button.list-group__item:hover { background: var(--bg-sunken); }

.list-group__item[aria-current] {
	font-weight: var(--weight-semibold);
	box-shadow: inset 3px 0 0 var(--accent);
}

.list-group__item[aria-disabled='true'] { color: var(--fg-faint); pointer-events: none; }
.list-group__item > .badge { margin-left: auto; }

/* Flush — no outer chrome, for use inside cards and rails. */
.list-group-flush { border: 0; border-radius: 0; background: none; }

/* ── Carousel — scroll-snap rail with page dots ──────────────────────────── */

.carousel { position: relative; }

.carousel__track {
	display: flex;
	gap: var(--space-4);
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	scrollbar-width: none;
	overscroll-behavior-x: contain;
	/* Vertical wheel must keep scrolling the page — the trap from the theme's
	   backlog. touch-action pans both axes. */
	touch-action: pan-x pan-y;
	scroll-padding-inline: var(--space-1);
	padding-bottom: var(--space-2);
}

.carousel__track::-webkit-scrollbar { display: none; }

.carousel__slide {
	flex: 0 0 min(18rem, 85%);
	scroll-snap-align: start;
}

.carousel-full .carousel__slide { flex-basis: 100%; }

.carousel__nav {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: var(--space-2);
	margin-top: var(--space-2);
}

.carousel__dot {
	width: 6px;
	height: 6px;
	border-radius: var(--radius-full);
	border: 0;
	padding: 0;
	background: var(--line-strong);
	cursor: pointer;
	transition: var(--t-colors), var(--t-transform);
}

.carousel__dot[aria-current] { background: var(--accent); transform: scale(1.25); }

/* ── Marquee — the looping strip. Duplicate the run, hide the twin from AT. ──
   <div class="marquee"><div class="marquee__run">…</div>
   <div class="marquee__run" aria-hidden="true">…</div></div>
   ------------------------------------------------------------------------- */

.marquee {
	display: flex;
	overflow: hidden;
	gap: var(--marquee-gap, var(--space-8));
	mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
}

.marquee__run {
	display: flex;
	align-items: center;
	gap: var(--marquee-gap, var(--space-8));
	flex: none;
	min-width: 100%;
	animation: marquee var(--marquee-dur, 28s) linear infinite;
}

@keyframes marquee {
	to { transform: translateX(calc(-100% - var(--marquee-gap, var(--space-8)))); }
}

.marquee:hover .marquee__run { animation-play-state: paused; }
.marquee-reverse .marquee__run { animation-direction: reverse; }
.marquee-fast { --marquee-dur: 14s; }
.marquee-slow { --marquee-dur: 48s; }

@media (prefers-reduced-motion: reduce) {
	.marquee { mask-image: none; overflow-x: auto; }
	.marquee__run { animation: none; }
	.marquee__run[aria-hidden] { display: none; }
}  /* close, collapse, accordion, dropdown, list group, carousel, marquee */
/* =============================================================================
   29 · OVERLAY
   Things that float above the page: modal, offcanvas, popover. All three ride
   the platform — <dialog> and the Popover API — so focus trapping, ESC and
   light-dismiss come free.
   ========================================================================== */

/* ── Modal — <dialog class="modal"> ──────────────────────────────────────── */

.modal {
	margin: auto;
	width: min(30rem, calc(100vw - 2 * var(--space-4)));
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-sheet);
	background: var(--bg-surface);
	color: var(--fg-default);
	box-shadow: var(--shadow-5);
	padding: 0;
}

.modal::backdrop {
	background: var(--bg-scrim, rgb(8 8 12 / 0.55));
	backdrop-filter: blur(3px);
}

.modal[open] { animation: modal-in var(--dur-2) var(--ease-out); }

@keyframes modal-in {
	from { opacity: 0; transform: translateY(10px) scale(0.98); }
}

.modal__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-3);
	padding: var(--space-4) var(--space-5);
	border-bottom: var(--border-hair) solid var(--line-subtle);
}

.modal__title {
	font-family: var(--font-display);
	font-size: var(--text-lg);
	font-weight: var(--weight-semibold);
	letter-spacing: var(--tracking-tight);
}

.modal__body {
	padding: var(--space-5);
	font-size: var(--text-sm);
	line-height: var(--leading-relaxed);
	color: var(--fg-subtle);
}

.modal__foot {
	display: flex;
	justify-content: flex-end;
	gap: var(--space-2);
	padding: var(--space-4) var(--space-5);
	border-top: var(--border-hair) solid var(--line-subtle);
	background: var(--bg-sunken);
	border-radius: 0 0 var(--radius-sheet) var(--radius-sheet);
}

.modal-lg { width: min(44rem, calc(100vw - 2 * var(--space-4))); }

/* ── Offcanvas — <dialog class="offcanvas"> docked to an edge ────────────── */

.offcanvas {
	margin: 0;
	margin-left: auto;
	inset-block: 0;
	height: 100dvh;
	max-height: none;
	width: min(20rem, 85vw);
	border: 0;
	border-left: var(--border-hair) solid var(--line-default);
	background: var(--bg-surface);
	color: var(--fg-default);
	box-shadow: var(--shadow-5);
	padding: 0;
	display: flex;
	flex-direction: column;
}

.offcanvas:not([open]) { display: none; }
.offcanvas::backdrop { background: var(--bg-scrim, rgb(8 8 12 / 0.55)); }

.offcanvas[open] { animation: offcanvas-in var(--dur-3) var(--ease-out); }

@keyframes offcanvas-in { from { transform: translateX(100%); } }

.offcanvas-start {
	margin-left: 0;
	margin-right: auto;
	border-left: 0;
	border-right: var(--border-hair) solid var(--line-default);
}

.offcanvas-start[open] { animation-name: offcanvas-in-start; }

@keyframes offcanvas-in-start { from { transform: translateX(-100%); } }

.offcanvas__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-3);
	padding: var(--space-4) var(--space-5);
	border-bottom: var(--border-hair) solid var(--line-subtle);
	flex: none;
}

.offcanvas__title { font-weight: var(--weight-semibold); }
.offcanvas__body { padding: var(--space-5); overflow-y: auto; font-size: var(--text-sm); }

/* ── Popover — [popover] via the Popover API, anchored by the trigger ────── */

.pop {
	position: fixed;
	inset: unset;
	margin: 0;
	max-width: 18rem;
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-lg);
	background: var(--bg-surface);
	color: var(--fg-default);
	box-shadow: var(--shadow-3);
	padding: 0;
}

.pop:popover-open { animation: modal-in var(--dur-1) var(--ease-out); }

.pop__head {
	font-weight: var(--weight-semibold);
	font-size: var(--text-sm);
	padding: var(--space-3) var(--space-4);
	border-bottom: var(--border-hair) solid var(--line-subtle);
	background: var(--bg-sunken);
	border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}

.pop__body {
	padding: var(--space-3) var(--space-4);
	font-size: var(--text-sm);
	line-height: var(--leading-relaxed);
	color: var(--fg-subtle);
}

@media (prefers-reduced-motion: reduce) {
	.modal[open], .offcanvas[open], .pop:popover-open { animation: none; }
}     /* modal, offcanvas, popover             */
/* =============================================================================
   30 · FORM PLUS
   The controls 21-form doesn't cover: range and floating labels.
   ========================================================================== */

/* ── Range ───────────────────────────────────────────────────────────────── */

.range {
	appearance: none;
	width: 100%;
	height: var(--control-sm);
	background: transparent;
	cursor: pointer;
}

.range::-webkit-slider-runnable-track {
	height: 4px;
	border-radius: var(--radius-pill);
	background: linear-gradient(to right, var(--accent) var(--value, 50%), var(--line-default) var(--value, 50%));
}

.range::-moz-range-track {
	height: 4px;
	border-radius: var(--radius-pill);
	background: var(--line-default);
}

.range::-moz-range-progress { height: 4px; border-radius: var(--radius-pill); background: var(--accent); }

.range::-webkit-slider-thumb {
	appearance: none;
	width: 1.05rem;
	height: 1.05rem;
	margin-top: calc((4px - 1.05rem) / 2);
	border-radius: var(--radius-full);
	background: var(--bg-canvas);
	border: var(--border-2) solid var(--accent);
	box-shadow: var(--shadow-1);
	transition: var(--t-transform);
}

.range::-moz-range-thumb {
	width: 1.05rem;
	height: 1.05rem;
	border-radius: var(--radius-full);
	background: var(--bg-canvas);
	border: var(--border-2) solid var(--accent);
	box-shadow: var(--shadow-1);
}

.range:active::-webkit-slider-thumb { transform: scale(1.15); }
.range:focus-visible { outline: var(--border-2) solid var(--accent-ring); outline-offset: 4px; border-radius: var(--radius-pill); }
.range:disabled { opacity: 0.45; cursor: not-allowed; }

/* ── Floating label ──────────────────────────────────────────────────────────
   <label class="float-field"><input class="input" placeholder=" " />
   <span class="float-field__label">Email</span></label>
   The placeholder must be a single space — :placeholder-shown drives the drop.
   ------------------------------------------------------------------------- */

.float-field { position: relative; display: block; }

.float-field > .input,
.float-field > .textarea,
.float-field > .select {
	padding-top: 1.15rem;
	padding-bottom: 0.35rem;
	height: auto;
	min-height: var(--control-lg);
}

.float-field__label {
	position: absolute;
	left: var(--space-3);
	top: 0.4rem;
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
	pointer-events: none;
	transition: var(--t-all);
	transform-origin: left top;
}

/* Resting state: the label sits where the value will be. */
.float-field > :placeholder-shown:not(:focus) ~ .float-field__label {
	top: 50%;
	transform: translateY(-50%);
	font-family: var(--font-body, inherit);
	font-size: var(--text-sm);
	letter-spacing: normal;
	text-transform: none;
	color: var(--fg-faint);
}

.float-field > :focus ~ .float-field__label { color: var(--accent); }

/* ── Input-group affix text (docs discovered the gap) ────────────────────── */

.input-group__text {
	display: inline-flex;
	align-items: center;
	padding-inline: var(--space-3);
	border: var(--border-hair) solid var(--line-default);
	background: var(--bg-sunken);
	color: var(--fg-subtle);
	font-family: var(--font-slate);
	font-size: var(--text-xs);
	white-space: nowrap;
}

.input-group > .input-group__text:first-child { border-radius: var(--radius-control) 0 0 var(--radius-control); border-right: 0; }
.input-group > .input-group__text:last-child { border-radius: 0 var(--radius-control) var(--radius-control) 0; border-left: 0; }   /* range, floating labels                */
/* =============================================================================
   31 · PROSE
   Long-form content: everything a rich-text editor can emit, styled once.
   Wrap the body in `.content` and every heading, list, quote, rule, table and
   editor card inside it inherits the system — templates never style content.

   Built against Ghost's Koenig cards (.kg-*), but the base selectors are
   generic, so any CMS output lands correctly.

   NAMED `.content`, NOT `.prose`, on purpose: @tailwindcss/typography owns
   `.prose`, and a system that ships as a package must never fight a plugin
   that half its users already have installed.

   THE RULE: rhythm comes from the flow, not from margins on each element.
   Elements set margin-top only; the first child never pushes the column down.
   ========================================================================== */

.content {
	max-width: var(--measure-prose);
	font-size: var(--text-md);
	line-height: var(--leading-relaxed);
	color: var(--fg-default);
}

.content > * + * { margin-top: var(--space-5); }
.content > :first-child { margin-top: 0; }

/* ── Headings ────────────────────────────────────────────────────────────── */

.content h2,
.content h3,
.content h4 {
	font-family: var(--font-display);
	font-weight: var(--weight-semibold);
	letter-spacing: var(--tracking-tight);
	line-height: var(--leading-tight);
	text-wrap: balance;
	scroll-margin-top: var(--header-offset, 5rem);
}

.content > h2 { font-size: var(--text-2xl); margin-top: var(--space-10); }
.content > h3 { font-size: var(--text-xl); margin-top: var(--space-8); }
.content > h4 { font-size: var(--text-lg); margin-top: var(--space-6); }

/* An anchor Ghost adds to headings — invisible until hovered. */
.content h2 > a,
.content h3 > a { color: inherit; text-decoration: none; }

/* ── Body copy ───────────────────────────────────────────────────────────── */

.content p { text-wrap: pretty; }

.content a {
	color: var(--fg-link);
	text-decoration: underline;
	text-underline-offset: 0.18em;
	text-decoration-thickness: 1px;
	text-decoration-color: var(--line-strong);
	transition: var(--t-colors);
}

.content a:hover { color: var(--fg-link-hover); text-decoration-color: currentColor; }

.content strong { font-weight: var(--weight-semibold); color: var(--fg-default); }
.content em { font-style: italic; }
.content small { font-size: var(--text-sm); color: var(--fg-subtle); }

.content mark {
	background: var(--accent-soft);
	color: var(--accent-soft-fg);
	padding: 0.1em 0.25em;
	border-radius: var(--radius-xs);
}

/* ── Lists ───────────────────────────────────────────────────────────────── */

.content ul,
.content ol { padding-left: var(--space-6); }
.content ul { list-style: disc; }
.content ol { list-style: decimal; }
.content li + li { margin-top: var(--space-2); }
.content li::marker { color: var(--fg-faint); }
.content ul ul,
.content ol ol,
.content ul ol,
.content ol ul { margin-top: var(--space-2); }

/* ── Quote ───────────────────────────────────────────────────────────────── */

.content blockquote {
	border-left: var(--border-2) solid var(--accent);
	padding-left: var(--space-5);
	font-size: var(--text-lg);
	line-height: var(--leading-snug);
	color: var(--fg-subtle);
}

.content blockquote cite {
	display: block;
	margin-top: var(--space-3);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	font-style: normal;
	color: var(--fg-faint);
}

/* Ghost's alternate blockquote — centred, no rule. */
.content blockquote.kg-blockquote-alt {
	border-left: 0;
	padding-left: 0;
	text-align: center;
	font-family: var(--font-display);
	font-size: var(--text-2xl);
	color: var(--fg-default);
}

/* ── Rule ────────────────────────────────────────────────────────────────── */

.content hr {
	border: 0;
	height: var(--border-hair);
	background: var(--line-default);
	margin-block: var(--space-10);
}

/* ── Code ────────────────────────────────────────────────────────────────── */

.content code {
	font-family: var(--font-slate);
	font-size: 0.875em;
	background: var(--bg-sunken);
	border: var(--border-hair) solid var(--line-subtle);
	border-radius: var(--radius-xs);
	padding: 0.12em 0.35em;
}

.content pre {
	background: var(--ink-950);
	color: var(--ink-100, #e5e5ea);
	border-radius: var(--radius-lg);
	padding: var(--space-4);
	overflow-x: auto;
	font-family: var(--font-slate);
	font-size: var(--text-xs);
	line-height: 1.7;
	color-scheme: dark;
}

.content pre code { background: none; border: 0; padding: 0; font-size: inherit; color: inherit; }

/* ── Media ───────────────────────────────────────────────────────────────── */

.content img,
.content video { border-radius: var(--radius-media); width: 100%; height: auto; }

.content figure { display: flex; flex-direction: column; gap: var(--space-3); }

.content figcaption,
.content .kg-card figcaption {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-wide);
	color: var(--fg-subtle);
	text-align: center;
}

/* ── Table ───────────────────────────────────────────────────────────────── */

.content table {
	width: 100%;
	border-collapse: collapse;
	font-size: var(--text-sm);
	display: block;
	overflow-x: auto;
}

.content th,
.content td {
	text-align: left;
	padding: var(--space-3) var(--space-4);
	border-bottom: var(--border-hair) solid var(--line-subtle);
}

.content th {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-subtle);
	font-weight: var(--weight-medium);
	border-bottom-color: var(--line-default);
}

/* =============================================================================
   EDITOR CARDS — Ghost Koenig
   ========================================================================== */

/* Width escapes. The prose column is capped; wide and full break out of it
   without the template knowing anything about it. */
.content .kg-card { margin-top: var(--space-8); }

.content .kg-width-wide {
	width: var(--gh-wide-width, min(var(--w-site), 100%));
	max-width: none;
	margin-inline: auto;
	position: relative;
	left: 50%;
	transform: translateX(-50%);
}

.content .kg-width-full {
	width: 100vw;
	max-width: 100vw;
	position: relative;
	left: 50%;
	transform: translateX(-50%);
}

.content .kg-width-full img { border-radius: 0; }

/* ── Gallery ─────────────────────────────────────────────────────────────── */

.content .kg-gallery-container { display: flex; flex-direction: column; gap: var(--space-2); }
.content .kg-gallery-row { display: flex; gap: var(--space-2); }
.content .kg-gallery-image img { width: 100%; height: 100%; object-fit: cover; }

/* ── Embed ───────────────────────────────────────────────────────────────── */

.content .kg-embed-card { display: flex; flex-direction: column; align-items: center; }

.content .kg-embed-card iframe,
.content iframe {
	width: 100%;
	aspect-ratio: 16 / 9;
	height: auto;
	border: 0;
	border-radius: var(--radius-media);
	display: block;
}

/* ── Bookmark ────────────────────────────────────────────────────────────── */

.content .kg-bookmark-card a.kg-bookmark-container,
.content .kg-bookmark-container {
	display: flex;
	text-decoration: none;
	color: inherit;
	background: var(--bg-surface);
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-card);
	overflow: hidden;
	transition: var(--t-colors), var(--t-shadow);
}

.content .kg-bookmark-container:hover { border-color: var(--line-strong); box-shadow: var(--shadow-2); }

.content .kg-bookmark-content {
	display: flex;
	flex-direction: column;
	gap: var(--space-2);
	padding: var(--space-5);
	flex: 1 1 auto;
	min-width: 0;
}

.content .kg-bookmark-title {
	font-weight: var(--weight-semibold);
	font-size: var(--text-base);
	color: var(--fg-default);
}

.content .kg-bookmark-description {
	font-size: var(--text-sm);
	color: var(--fg-subtle);
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

.content .kg-bookmark-metadata {
	display: flex;
	align-items: center;
	gap: var(--space-2);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	color: var(--fg-faint);
	margin-top: auto;
}

.content .kg-bookmark-icon { width: 1rem; height: 1rem; border-radius: var(--radius-xs); }

.content .kg-bookmark-thumbnail { flex: 0 0 33%; max-height: 12rem; }
.content .kg-bookmark-thumbnail img { width: 100%; height: 100%; object-fit: cover; border-radius: 0; }

@media (max-width: 40rem) {
	.content .kg-bookmark-container { flex-direction: column-reverse; }
	.content .kg-bookmark-thumbnail { flex: none; aspect-ratio: 16 / 9; }
}

/* ── Callout ─────────────────────────────────────────────────────────────── */

.content .kg-callout-card {
	display: flex;
	gap: var(--space-3);
	padding: var(--space-4) var(--space-5);
	border-radius: var(--radius-lg);
	background: var(--bg-sunken);
	border: var(--border-hair) solid var(--line-subtle);
	font-size: var(--text-sm);
}

.content .kg-callout-emoji { flex: none; line-height: var(--leading-normal); }

.content .kg-callout-card-accent {
	background: var(--accent-soft);
	border-color: var(--line-accent);
	color: var(--accent-soft-fg);
}

/* ── Toggle ──────────────────────────────────────────────────────────────── */

.content .kg-toggle-card {
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-lg);
	padding: var(--space-4) var(--space-5);
	background: var(--bg-surface);
}

.content .kg-toggle-heading {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-3);
	cursor: pointer;
}

.content .kg-toggle-heading-text { font-weight: var(--weight-semibold); font-size: var(--text-base); }

.content .kg-toggle-card-icon { width: 1rem; height: 1rem; transition: var(--t-transform); flex: none; }
.content .kg-toggle-card[data-kg-toggle-state='close'] .kg-toggle-content { display: none; }
.content .kg-toggle-card[data-kg-toggle-state='open'] .kg-toggle-card-icon { transform: rotate(180deg); }
.content .kg-toggle-content { margin-top: var(--space-3); font-size: var(--text-sm); color: var(--fg-subtle); }

/* ── Button ──────────────────────────────────────────────────────────────── */

.content .kg-button-card { display: flex; }
.content .kg-button-card.kg-align-center { justify-content: center; }

.content .kg-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	height: var(--control-md);
	padding-inline: var(--space-5);
	border-radius: var(--radius-pill);
	background: var(--accent);
	color: var(--fg-on-accent);
	font-weight: var(--weight-medium);
	font-size: var(--text-sm);
	text-decoration: none;
	transition: var(--t-colors);
}

.content .kg-btn:hover { background: var(--accent-hover); color: var(--fg-on-accent); }
.content .kg-btn-accent { background: var(--accent); }

/* ── Header ──────────────────────────────────────────────────────────────── */

.content .kg-header-card {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--space-4);
	text-align: center;
	padding: var(--section-sm) var(--space-6);
	border-radius: var(--radius-sheet);
	background: var(--bg-sunken);
}

.content .kg-header-card-header {
	font-family: var(--font-display);
	font-size: var(--text-3xl);
	font-weight: var(--weight-bold);
	letter-spacing: var(--tracking-tight);
}

.content .kg-header-card-subheader { color: var(--fg-subtle); }

/* ── Audio · Video · File ────────────────────────────────────────────────── */

.content .kg-audio-card,
.content .kg-file-card {
	display: flex;
	align-items: center;
	gap: var(--space-4);
	padding: var(--space-4);
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-card);
	background: var(--bg-surface);
}

.content .kg-file-card-container {
	display: flex;
	align-items: center;
	gap: var(--space-4);
	width: 100%;
	text-decoration: none;
	color: inherit;
}

.content .kg-file-card-title { font-weight: var(--weight-semibold); font-size: var(--text-sm); }
.content .kg-file-card-caption { font-size: var(--text-xs); color: var(--fg-subtle); }

.content .kg-file-card-metadata {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	color: var(--fg-faint);
}

.content .kg-video-card { border-radius: var(--radius-media); overflow: hidden; }

/* ── Product ─────────────────────────────────────────────────────────────── */

.content .kg-product-card-container {
	display: flex;
	flex-direction: column;
	gap: var(--space-3);
	padding: var(--space-5);
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-card);
	background: var(--bg-surface);
}

.content .kg-product-card-title { font-weight: var(--weight-semibold); font-size: var(--text-lg); }
.content .kg-product-card-description { font-size: var(--text-sm); color: var(--fg-subtle); }
.content .kg-product-card-rating-active { color: var(--accent); }

/* ── Signup ──────────────────────────────────────────────────────────────── */

.content .kg-signup-card {
	border-radius: var(--radius-sheet);
	padding: var(--section-sm) var(--space-6);
	background: var(--bg-inverse);
	color: var(--fg-on-inverse);
}

.content .kg-signup-card-heading {
	font-family: var(--font-display);
	font-size: var(--text-3xl);
	font-weight: var(--weight-bold);
	letter-spacing: var(--tracking-tight);
}

.content .kg-signup-card-subheading { color: var(--fg-on-inverse); opacity: 0.75; margin-top: var(--space-3); }

.content .kg-signup-card-input {
	height: var(--control-md);
	padding-inline: var(--space-4);
	border-radius: var(--radius-control);
	border: var(--border-hair) solid var(--line-inverse);
	background: color-mix(in srgb, var(--fg-on-inverse) 8%, transparent);
	color: var(--fg-on-inverse);
}

.content .kg-signup-card-button {
	height: var(--control-md);
	padding-inline: var(--space-5);
	border-radius: var(--radius-pill);
	background: var(--accent);
	color: var(--fg-on-accent);
	border: 0;
	font-weight: var(--weight-medium);
	cursor: pointer;
}

/* ── Members gating ──────────────────────────────────────────────────────── */

.content .kg-nft-card,
.content .kg-embed-card > * { max-width: 100%; }     /* long-form content — headings … kg-cards */
/* =============================================================================
   32 · EDITORIAL
   The organs a publishing site needs once it has more than one kind of post:
   a sticky rail beside the reading column, a conversation, a release note,
   and the share row. Each one exists because a real layout asked for it.
   ========================================================================== */

/* ── Rail — the sticky sidebar ───────────────────────────────────────────────
   Sits inside .grid-rail (layer 1). Sticks under the nav, scrolls itself when
   it is taller than the viewport, and unsticks entirely on small screens so a
   phone never gets a frozen column.
   ------------------------------------------------------------------------- */

.rail {
	display: flex;
	flex-direction: column;
	gap: var(--space-6);
	align-self: start;
}

@media (min-width: 64rem) {
	.rail {
		position: sticky;
		top: calc(var(--header-offset, 5rem) + var(--space-4));
		max-height: calc(100dvh - var(--header-offset, 5rem) - var(--space-8));
		overflow-y: auto;
		scrollbar-width: thin;
		overscroll-behavior: contain;
	}
}

/* A titled block inside the rail. */
.rail__block {
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-card);
	background: var(--bg-surface);
	padding: var(--space-4) var(--space-5);
}

.rail__title {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
	margin-bottom: var(--space-3);
	display: flex;
	align-items: center;
	gap: var(--space-2);
}

/* ── Chat — a prompt and its answer ──────────────────────────────────────────
   <div class="chat">
     <div class="chat__turn chat__turn-you"> <span class="chat__who">You</span>
       <div class="chat__bubble"> … </div> </div>
     <div class="chat__turn"> … </div>
   </div>
   ------------------------------------------------------------------------- */

.chat { display: flex; flex-direction: column; gap: var(--space-4); }

.chat__turn { display: flex; flex-direction: column; gap: var(--space-2); max-width: 90%; }

.chat__who {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
	display: flex;
	align-items: center;
	gap: var(--space-2);
}

.chat__bubble {
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-lg);
	background: var(--bg-surface);
	padding: var(--space-4) var(--space-5);
	font-size: var(--text-sm);
	line-height: var(--leading-relaxed);
	position: relative;
}

.chat__bubble > :first-child { margin-top: 0; }
.chat__bubble pre { margin: var(--space-3) 0 0; }

/* The human turn: aligned right, accent-washed, so a long thread stays
   scannable without avatars. */
.chat__turn-you { align-self: flex-end; align-items: flex-end; }
.chat__turn-you .chat__bubble {
	background: var(--accent-soft);
	border-color: var(--line-accent);
	color: var(--accent-soft-fg);
	border-bottom-right-radius: var(--radius-xs);
}

.chat__turn:not(.chat__turn-you) .chat__bubble { border-bottom-left-radius: var(--radius-xs); }

/* Copy affordance parked in the bubble's corner. */
.chat__copy {
	position: absolute;
	top: var(--space-2);
	inset-inline-end: var(--space-2);
	opacity: 0;
	transition: var(--t-colors);
}

.chat__bubble:hover .chat__copy,
.chat__copy:focus-visible { opacity: 1; }

/* ── Release — one changelog entry ───────────────────────────────────────────
   A version, a date, and what changed. Versions are tabular so a column of
   them lines up.
   ------------------------------------------------------------------------- */

.release {
	display: grid;
	gap: var(--space-2) var(--space-6);
	padding-block: var(--space-6);
	border-bottom: var(--border-hair) solid var(--line-subtle);
}

@media (min-width: 48rem) {
	.release { grid-template-columns: 10rem 1fr; }
}

.release:last-child { border-bottom: 0; }

.release__meta { display: flex; flex-direction: column; gap: var(--space-2); }

.release__ver {
	font-family: var(--font-slate);
	font-size: var(--text-sm);
	font-weight: var(--weight-medium);
	font-variant-numeric: tabular-nums;
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
}

.release__date {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
}

/* The newest release is the live one. */
.release-current .release__ver { color: var(--accent); }

/* ── Share ───────────────────────────────────────────────────────────────────
   A row of quiet actions under an article. Never a wall of brand colours.
   ------------------------------------------------------------------------- */

.share {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: var(--space-2);
	padding-block: var(--space-5);
	border-top: var(--border-hair) solid var(--line-subtle);
	border-bottom: var(--border-hair) solid var(--line-subtle);
}

.share__label {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
	margin-inline-end: var(--space-2);
}   /* rail, chat, release, share            */
/* =============================================================================
   33 · NAVBAR
   The site's chrome, in every shape it takes. 24-nav.css holds the primitives
   (the island, links, tabs, crumbs, pager, toc); this file is the navigation
   SYSTEM built on them: alignment, active styles, dropdowns, the mega panel,
   the mobile sheet, and the submenu row a collection adds beneath it.

   HOUSE RULES
   · Active is a dot or a 2px rule — never a filled pill.
   · One level of dropdown. A second level is a mega panel, not a fly-out.
   · Every panel is a <details> or a <dialog>: keyboard and ESC come free.
   · The site bar stays; context becomes a submenu row beneath it.
   ========================================================================== */

/* ── Alignment ───────────────────────────────────────────────────────────────
   Two honest choices, because "same width as the content" means two different
   things and picking silently is how navs end up looking 16px wrong.

   .nav-shell        the island's BORDER aligns with the content column edge
                     (default — the island reads as a floating object)
   .nav-shell-flush  the island's CONTENT aligns with the page's text, so the
                     wordmark sits directly above the page title
   .nav-shell-full   no island: a full-bleed bar with a hairline underneath
   ------------------------------------------------------------------------- */

.nav-shell-flush .nav-bar { padding-inline: 0; border-inline: 0; border-radius: 0; background: none; backdrop-filter: none; box-shadow: none; }
.nav-shell-flush .nav-bar { border-top: 0; border-bottom: var(--border-hair) solid var(--line-default); }

.nav-shell-full {
	max-width: none;
	padding-inline: 0;
	top: 0;
}

/* The SURFACE goes edge to edge — a full-width bar whose hairline stops short
   of the window is just an island with square corners. Only the contents are
   pulled in, to the same column the page's text uses.

   No border either: the island earns its outline by floating, a bar that fills
   the width does not, and a full-bleed hairline is a seam across the page. The
   background alone separates it. */
.nav-shell-full .nav-bar {
	padding-inline: max(var(--gutter), calc((100% - var(--w-site)) / 2));
	border: 0;
	border-radius: 0;
	box-shadow: none;
}

/* Scrolled state — the island tightens. Toggle `data-scrolled` from JS. */
.nav-shell[data-scrolled] .nav-bar {
	background: color-mix(in srgb, var(--bg-canvas) 94%, transparent);
	box-shadow: var(--shadow-2);
}

/* ── Active styles ───────────────────────────────────────────────────────────
   The dot is the default. Two alternates for contexts where a dot reads as
   decoration — a docs bar, a player bar.
   ------------------------------------------------------------------------- */

/* Rule: a 2px accent underline instead of the dot. */
.nav-links-rule .nav-link[aria-current]::before { display: none; }

.nav-links-rule .nav-link {
	border-radius: 0;
	border-bottom: var(--border-2) solid transparent;
	padding-inline: var(--space-2);
}

.nav-links-rule .nav-link[aria-current] { border-bottom-color: var(--accent); }

/* Soft: a sunken wash. For dense bars where neither dot nor rule survives. */
.nav-links-soft .nav-link[aria-current]::before { display: none; }
.nav-links-soft .nav-link[aria-current] { background: var(--bg-sunken); }

/* ── Dropdown ────────────────────────────────────────────────────────────────
   <details class="nav-menu">
     <summary class="nav-link">Learn</summary>
     <div class="nav-menu__panel"> … .dropdown__item … </div>
   </details>
   ------------------------------------------------------------------------- */

.nav-menu { position: relative; }

.nav-menu > summary {
	list-style: none;
	cursor: pointer;
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
}

.nav-menu > summary::-webkit-details-marker { display: none; }

.nav-menu > summary::after {
	content: '';
	width: 0.35rem;
	height: 0.35rem;
	border-right: 1.5px solid currentColor;
	border-bottom: 1.5px solid currentColor;
	transform: rotate(45deg) translateY(-1px);
	transition: var(--t-transform);
	opacity: 0.6;
}

.nav-menu[open] > summary::after { transform: rotate(-135deg) translateY(-1px); }

.nav-menu__panel {
	position: absolute;
	top: calc(100% + var(--space-3));
	left: 50%;
	transform: translateX(-50%);
	min-width: 14rem;
	z-index: var(--z-popover, 40);
	background: var(--bg-surface);
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-lg);
	box-shadow: var(--shadow-3);
	padding: var(--space-2);
	display: flex;
	flex-direction: column;
	gap: 1px;
	animation: nav-drop var(--dur-2) var(--ease-out);
}

@keyframes nav-drop {
	from { opacity: 0; transform: translateX(-50%) translateY(-6px); }
}

/* ── Mega panel ──────────────────────────────────────────────────────────────
   A full-width sheet under the island. Columns of links plus one featured
   card, because a mega menu that is only links is a dropdown wearing a costume.
   ------------------------------------------------------------------------- */

.nav-mega { position: static; }        /* so the panel can span the island */

.nav-mega__panel {
	position: absolute;
	top: calc(100% + var(--space-3));
	left: 0;
	right: 0;
	z-index: var(--z-popover, 40);
	background: var(--bg-surface);
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-sheet);
	box-shadow: var(--shadow-4);
	padding: var(--space-6);
	display: grid;
	gap: var(--space-6);
	grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
	animation: nav-mega-in var(--dur-2) var(--ease-out);
	transform-origin: top center;
}

@keyframes nav-mega-in {
	from { opacity: 0; transform: translateY(-8px) scaleY(0.98); }
}

.nav-mega__col { display: flex; flex-direction: column; gap: var(--space-1); }

.nav-mega__title {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
	margin-bottom: var(--space-2);
}

.nav-mega__link {
	display: flex;
	align-items: flex-start;
	gap: var(--space-3);
	padding: var(--space-2);
	border-radius: var(--radius-md);
	text-decoration: none;
	color: var(--fg-default);
	transition: var(--t-colors);
}

.nav-mega__link:hover { background: var(--bg-sunken); }
.nav-mega__link .icon { margin-top: 2px; opacity: 0.7; flex: none; }
.nav-mega__link b { display: block; font-size: var(--text-sm); font-weight: var(--weight-semibold); }
.nav-mega__link span { display: block; font-size: var(--text-xs); color: var(--fg-subtle); }

/* The featured cell — one promoted thing, on the sunken surface. */
.nav-mega__feature {
	grid-column: span 1;
	background: var(--bg-sunken);
	border-radius: var(--radius-lg);
	padding: var(--space-4);
	display: flex;
	flex-direction: column;
	gap: var(--space-3);
	text-decoration: none;
	color: inherit;
}

.nav-mega__feature img,
.nav-mega__feature .pattern {
	border-radius: var(--radius-md);
	aspect-ratio: 16 / 9;
	object-fit: cover;
	width: 100%;
}

/* ── Mobile sheet ────────────────────────────────────────────────────────────
   <dialog class="nav-sheet"> — opens like a lens: the panel irises out from
   the button, a scanline sweeps down, and the items rack into focus one after
   another. All CSS; <dialog> gives focus trapping and ESC.
   ------------------------------------------------------------------------- */

.nav-sheet {
	margin: 0;
	inset: 0;
	width: 100%;
	max-width: none;
	height: 100dvh;
	max-height: none;
	border: 0;
	padding: 0;
	background: var(--bg-canvas);
	color: var(--fg-default);
	overflow: hidden;
}

.nav-sheet::backdrop { background: var(--bg-scrim); }

.nav-sheet[open] { animation: nav-iris var(--dur-4) var(--ease-out); }

/* The iris: a circular reveal from the top-right, where the button lives. */
@keyframes nav-iris {
	from { clip-path: circle(0% at calc(100% - 2.5rem) 2.5rem); }
	to   { clip-path: circle(150% at calc(100% - 2.5rem) 2.5rem); }
}

.nav-sheet__in {
	display: flex;
	flex-direction: column;
	height: 100%;
	padding: var(--space-5);
	position: relative;
}

/* The scanline that sweeps the panel once on open — the "recording started"
   beat. Purely decorative, and gone under reduced motion. */
.nav-sheet__scan {
	position: absolute;
	inset-inline: 0;
	top: 0;
	height: 2px;
	background: linear-gradient(90deg, transparent, var(--accent), transparent);
	opacity: 0.9;
	animation: nav-scan 900ms var(--ease-out) 120ms both;
	pointer-events: none;
}

@keyframes nav-scan {
	0% { transform: translateY(0); opacity: 0; }
	20%  { opacity: 0.9; }
	100%   { transform: translateY(100dvh); opacity: 0; }
}

.nav-sheet__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-3);
	flex: none;
	padding-block: var(--space-2);
}

.nav-sheet__links {
	display: flex;
	flex-direction: column;
	gap: var(--space-1);
	margin-top: var(--space-8);
	flex: 1;
	overflow-y: auto;
}

/* Each row racks into focus, staggered — like a lens finding its subject. */
.nav-sheet__links > * {
	animation: nav-rack var(--dur-3) var(--ease-out) both;
	animation-delay: calc(120ms + var(--i, 0) * 45ms);
}

@keyframes nav-rack {
	from { opacity: 0; transform: translateY(10px); filter: blur(6px); }
	to   { opacity: 1; transform: none; filter: blur(0); }
}

.nav-sheet__link {
	display: flex;
	align-items: center;
	gap: var(--space-4);
	padding: var(--space-4) var(--space-2);
	border-bottom: var(--border-hair) solid var(--line-subtle);
	font-family: var(--font-display);
	font-size: var(--text-2xl);
	font-weight: var(--weight-semibold);
	letter-spacing: var(--tracking-tight);
	color: var(--fg-default);
	text-decoration: none;
}

.nav-sheet__link .icon { width: 1.25rem; height: 1.25rem; opacity: 0.5; flex: none; }

.nav-sheet__link[aria-current] { color: var(--accent); }
.nav-sheet__link[aria-current] .icon { opacity: 1; }

.nav-sheet__foot {
	flex: none;
	padding-top: var(--space-5);
	border-top: var(--border-hair) solid var(--line-default);
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-3);
}

@media (prefers-reduced-motion: reduce) {
	.nav-sheet[open] { animation: none; }
	.nav-sheet__scan { display: none; }
	.nav-sheet__links > * { animation: none; }
	.nav-menu__panel,
	.nav-mega__panel { animation: none; }
}

/* ── Hamburger ───────────────────────────────────────────────────────────────
   The button that opens the sheet. Three bars that become an X — one element,
   two pseudo-elements, no icon font and no swapped SVG.

     <button class="nav-burger" aria-expanded="false" aria-controls="menu">
       <span class="nav-burger__box"><span class="nav-burger__bars"></span></span>
       <span class="u-sr-only">Menu</span>
     </button>

   Drive it with aria-expanded — the same attribute a screen reader reads.
   ------------------------------------------------------------------------- */

.nav-burger {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: var(--control-sm);
	height: var(--control-sm);
	padding: 0;
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-full);
	background: var(--bg-surface);
	color: var(--fg-default);
	cursor: pointer;
	transition: var(--t-colors);
}

.nav-burger:hover { border-color: var(--line-strong); background: var(--bg-sunken); }

.nav-burger__box { position: relative; width: 1rem; height: 0.75rem; }

.nav-burger__bars,
.nav-burger__bars::before,
.nav-burger__bars::after {
	position: absolute;
	left: 0;
	width: 100%;
	height: 1.5px;
	background: currentColor;
	border-radius: var(--radius-pill);
	transition: transform var(--dur-2) var(--ease-out), opacity var(--dur-1) linear;
}

.nav-burger__bars { top: calc(50% - 0.75px); }
.nav-burger__bars::before { content: ''; top: -5px; }
.nav-burger__bars::after { content: ''; top: 5px; }

/* Open: the middle bar vanishes, the outer two cross. */
.nav-burger[aria-expanded='true'] .nav-burger__bars { background: transparent; }
.nav-burger[aria-expanded='true'] .nav-burger__bars::before { transform: translateY(5px) rotate(45deg); }
.nav-burger[aria-expanded='true'] .nav-burger__bars::after { transform: translateY(-5px) rotate(-45deg); }

/* Squeeze — the bars pull to the centre before crossing. */
.nav-burger-squeeze .nav-burger__bars::before,
.nav-burger-squeeze .nav-burger__bars::after { transition-duration: var(--dur-3); }
.nav-burger-squeeze[aria-expanded='true'] .nav-burger__bars::before,
.nav-burger-squeeze[aria-expanded='true'] .nav-burger__bars::after { width: 90%; left: 5%; }

/* Aperture — the bars rotate as one, like a lens closing. Pairs with the
   sheet's iris. */
.nav-burger-aperture .nav-burger__box { transition: transform var(--dur-3) var(--ease-spring); }
.nav-burger-aperture[aria-expanded='true'] .nav-burger__box { transform: rotate(90deg); }

/* Bare — no ring, for bars that already have a border. */
.nav-burger-bare { border: 0; background: none; }

/* Labelled — the word beside the bars, on roomy bars. */
.nav-burger-labelled {
	width: auto;
	border-radius: var(--radius-pill);
	padding-inline: var(--space-3);
	gap: var(--space-2);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
}

/* ── Responsive collapse ─────────────────────────────────────────────────────
   Put `.nav-collapse` on the island: the link row hides and the burger
   appears below the breakpoint. Above it, the burger is gone and the links
   are back — no duplicate markup, no JS.
   ------------------------------------------------------------------------- */

.nav-collapse .nav-burger { display: none; }

@media (max-width: 48rem) {
	.nav-collapse .nav-links { display: none; }
	.nav-collapse .nav-burger { display: inline-flex; }
}

/* ── GitHub chip ─────────────────────────────────────────────────────────────
   A quiet link out to the repo, with room for a star count that JS fills in.
   Sits at the end of the actions cluster, after the theme toggle.
   ------------------------------------------------------------------------- */

.nav-gh {
	display: inline-grid;
	place-items: center;
	height: var(--control-sm);
	width: var(--control-sm);
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-pill);
	background: var(--bg-surface);
	color: var(--fg-default);
	text-decoration: none;
	transition: var(--t-colors);
}

.nav-gh:hover { border-color: var(--line-strong); background: var(--bg-sunken); }
.nav-gh .icon { width: 1.05rem; height: 1.05rem; flex: none; }

.nav-gh__stars {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	font-variant-numeric: tabular-nums;
	color: var(--fg-subtle);
}


/* =============================================================================
   NAVBAR VARIANTS
   Not ten skins — four positions. A bar's shape is a decision you make once in
   the design; where it goes when the reader scrolls is a decision about
   behaviour, and that is the one worth a variant.

     .nav-shell         island   — the floating pill, sticky from the start
     .nav-shell-fixed   fixed    — pinned to the top, always
     .nav-shell-morph   island on scroll — full-bleed at rest, drawing itself
                        into the island once the page has moved
     .nav-shell-auto    fixed on scroll  — in flow at the top, pinned once you
                        scroll, and out of the way while you are reading down

   All four are defined above with the shell behaviours. Skins that used to be
   variants are now the things they always were: .nav-bar-center,
   .nav-bar-inverse and .nav-bar-compact for the shape, .nav-shell-flush and
   .nav-shell-full for the alignment. Nothing was lost; the list stopped
   pretending that "bordered" and "island" are the same kind of choice.
   ========================================================================== */

/* The ghost: transparent over a hero, materialising once you scroll. It is a
   behaviour, not a skin, so it survives the cull. */
.nav-ghost {
	background: transparent;
	border-color: transparent;
	box-shadow: none;
	backdrop-filter: none;
	transition: var(--t-colors), var(--t-shadow), backdrop-filter var(--dur-2);
}

.nav-shell[data-scrolled] .nav-ghost,
.nav-shell-morph[data-scrolled] .nav-ghost {
	background: color-mix(in srgb, var(--bg-canvas) 88%, transparent);
	border-color: var(--line-default);
	backdrop-filter: saturate(180%) blur(14px);
	box-shadow: var(--shadow-1);
}

/* The shell's own position modifiers, usable with any of the four. */
.nav-s-static { position: static; }
.nav-s-top { top: 0; }


/* =============================================================================
   COLLECTION STYLES
   A creator does not publish one kind of thing, and the bar above a web series
   should not look like the bar above a shop. Each collection gets a class that
   sets its defaults — and only defaults: every one of them is written in the
   same tokens the rest of the system uses, so overriding any part of it is a
   variable, not a fork.

     <nav class="nav-bar nav-video">   … the series, over its own footage
     <nav class="nav-bar nav-course">  … the course, with progress
     <nav class="nav-bar nav-blog">    … the writing, out of the way
     <nav class="nav-bar nav-shop">    … the shop, cart-led
     <nav class="nav-bar nav-trip">    … the journal, warm
     <nav class="nav-bar nav-docs">    … the reference, dense

   The knobs each one sets:
     --bar-bg · --bar-fg · --bar-line · --bar-radius · --bar-h · --bar-blur
   Set any of them yourself and the collection style yields.
   ========================================================================== */

.nav-bar {
	background: var(--bar-bg, color-mix(in srgb, var(--bg-canvas) 82%, transparent));
	color: var(--bar-fg, inherit);
	border-color: var(--bar-line, var(--line-default));
	border-radius: var(--bar-radius, var(--radius-pill));
	height: var(--bar-h, var(--nav-h));
	backdrop-filter: var(--bar-blur, saturate(180%) blur(14px));
}

/* ── Video · the series ──────────────────────────────────────────────────────
   A series opens on its own footage, so the bar does not sit ON the page — it
   sits OVER the film, and owns nothing until you scroll. No island, no border,
   no plate: just the mark and the links on top of the picture, with a gradient
   doing the legibility work a background would otherwise have to do.

     <header class="nav-shell nav-shell-full nav-over">
       <nav class="nav-bar nav-video"> … </nav>
     </header>
     <div class="nav-over__media"> … your <video> or poster … </div>
   ------------------------------------------------------------------------- */

.nav-video {
	--bar-bg: transparent;
	--bar-fg: var(--pure-white, #fff);
	--bar-line: transparent;
	--bar-radius: 0;
	--bar-blur: none;

	box-shadow: none;
	color-scheme: dark;
}

.nav-video .nav-link { color: rgb(255 255 255 / 0.72); }
.nav-video .nav-link:hover,
.nav-video .nav-link[aria-current] { color: var(--pure-white, #fff); }
.nav-video .logo,
.nav-video .cds-mark { color: var(--pure-white, #fff); }
.nav-video .nav-burger,
.nav-video .nav-gh {
	border-color: rgb(255 255 255 / 0.24);
	background: rgb(255 255 255 / 0.08);
	color: var(--pure-white, #fff);
}

/* The shell that holds a bar over media. The gradient is the only thing making
   white type legible on an unknown frame, so it is part of the shell rather
   than something the author remembers to add. */
.nav-over {
	position: absolute;
	inset-inline: 0;
	top: 0;
	z-index: var(--z-nav);
	background: linear-gradient(to bottom,
		rgb(0 0 0 / 0.55), rgb(0 0 0 / 0.28) 55%, transparent);
}

/* Once you have scrolled past the film, the bar has to become furniture again
   or it will float over the text with nothing behind it. */
.nav-over[data-scrolled] {
	position: fixed;
	background: var(--ink-950);
	box-shadow: var(--shadow-2);
}

.nav-over__media {
	position: relative;
	overflow: hidden;
	background: var(--bg-media, var(--ink-900));
	aspect-ratio: 16 / 9;
	max-height: 82dvh;
}

.nav-over__media > img,
.nav-over__media > video {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

/* ── Course · the syllabus ───────────────────────────────────────────────── */

.nav-course-bar {
	--bar-bg: color-mix(in srgb, var(--bg-canvas) 94%, transparent);
	--bar-radius: var(--radius-lg);
}

/* ── Blog · the writing ──────────────────────────────────────────────────────
   Reading wants the chrome to leave. Flat, hairline, no plate, no shadow. */

.nav-blog {
	--bar-bg: transparent;
	--bar-radius: 0;
	--bar-blur: none;
	--bar-line: transparent;

	border-bottom-color: var(--line-default);
	box-shadow: none;
}

/* ── Shop · the cart leads ───────────────────────────────────────────────── */

.nav-shop {
	--bar-bg: var(--bg-surface);
	--bar-radius: var(--radius-md);
}

.nav-shop .nav-actions { gap: var(--space-3); }

/* ── Trip · the journal ──────────────────────────────────────────────────── */

.nav-trip {
	--bar-bg: color-mix(in srgb, var(--accent) 8%, var(--bg-canvas));
	--bar-line: color-mix(in srgb, var(--accent) 24%, transparent);
	--bar-radius: var(--radius-xl, 1.25rem);
}

/* ── Docs · the reference ────────────────────────────────────────────────── */

.nav-docs-bar {
	--bar-h: 2.75rem;
	--bar-radius: 0;
	--bar-bg: var(--bg-surface);
	--bar-blur: none;
}

.nav-docs-bar .nav-link { height: 1.75rem; font-size: var(--text-sm); }

/* ── Where the dot sits ──────────────────────────────────────────────────────
   The leading slot is never shared. A plain link keeps the dot in front, where
   it reads as a marker beside the word; a link that already carries an icon
   sends the dot to the end rather than stacking two marks at the head, which
   reads as one four-part smudge and tells you nothing.

   :has() asks the link what it holds, so nobody has to remember a modifier.
   ------------------------------------------------------------------------- */

.nav-link:has(> svg, > .icon, > .nav-link__icon)[aria-current]::before { display: none; }

.nav-link:has(> svg, > .icon, > .nav-link__icon)[aria-current]::after {
	content: '';
	width: var(--dot-sm);
	height: var(--dot-sm);
	border-radius: var(--radius-full);
	background: var(--accent);
	flex: none;
}

/* ── Slot order ──────────────────────────────────────────────────────────────
   The links follow the mark when there is one, and lead when there is not, so
   a bar without a logo never opens with a hole where the logo would have been.
   :has() asks the bar what it contains instead of making the author remember
   a modifier class.
   ------------------------------------------------------------------------- */

.nav-bar:has(> .logo, > .cds-mark, > .nav-bar__mark) .nav-links { margin-inline: auto; }
.nav-bar:not(:has(> .logo, > .cds-mark, > .nav-bar__mark)) .nav-links { margin-right: auto; }

/* ── The line is the progress ────────────────────────────────────────────────
   The island's hairline doubles as the read-through bar: the filled part is
   the accent, the rest is the accent at a whisper. One line does both jobs, so
   a reader never has to learn a second piece of chrome.

     <div class="nav-shell nav-progress" style="--progress:38%">

   It stays a hairline — the line is doing two jobs, not becoming a component.
   The unread part is the page's own rule by default; data-track="accent" tints
   it instead, for bars where the read-through is the point rather than a note.

   The ring is painted on a pseudo-element and masked to the border, which is
   the only way to gradient a border without giving up the radius.
   ------------------------------------------------------------------------- */

/* No `position` on the shell here. .nav-shell is already positioned — sticky,
   or fixed in the -auto/-fixed variants — and re-declaring it `relative`
   silently un-sticks the bar, which looks like the navbar scrolling away.
   The bar itself is reached through :is() because a second row puts it inside
   .nav-stack. */
.nav-progress[data-track='accent'] { --track: color-mix(in srgb, var(--accent) 18%, transparent); }

:is(.nav-progress > .nav-bar, .nav-progress > .nav-stack > .nav-bar) {
	position: relative;
	border-color: transparent;
}

:is(.nav-progress > .nav-bar, .nav-progress > .nav-stack > .nav-bar)::after {
	content: '';
	position: absolute;
	inset: 0;
	border-radius: inherit;
	padding: var(--border-hair);
	background: linear-gradient(90deg,
		var(--accent) 0 var(--progress, 0%),
		var(--track, var(--line-default)) var(--progress, 0%) 100%);
	mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
	mask-composite: exclude;
	-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
	-webkit-mask-composite: xor;
	pointer-events: none;
	transition: background var(--dur-4) var(--ease-out);
}

/* ── Shell behaviours ────────────────────────────────────────────────────────
   Four ways a bar can behave, none of which change its markup:

   .nav-shell-fixed   pinned to the top, always
   .nav-shell-auto    pinned, but hides on the way down and returns on the way
                      up — reading gets the screen, navigating gets the bar
   .nav-shell-reveal  the same, but sticky: it keeps its place in the flow
   .nav-shell-morph   full-bleed at rest, contracting into the island once the
                      page has moved
   (default)          sticky island

   nav.js sets data-scrolled and data-dir; everything else is CSS.
   ------------------------------------------------------------------------- */

.nav-shell-fixed { position: fixed; top: 0; left: 0; right: 0; }

.nav-shell-auto {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	transition: transform var(--dur-3) var(--ease-out);
}

.nav-shell-auto[data-dir='down'] { transform: translateY(-115%); }
.nav-shell-auto[data-dir='up'] { transform: none; }

/* The same disappearing act, but sticky rather than fixed — so the bar keeps
   its place in the flow and the first screenful is never hidden underneath it.
   Pair with .nav-shell-full for a plain bar that leaves on the way down. */
.nav-shell-reveal {
	position: sticky;
	top: 0;
	transition: transform var(--dur-3) var(--ease-out);
}

.nav-shell-reveal[data-dir='down'] { transform: translateY(-115%); }
.nav-shell-reveal[data-dir='up'] { transform: none; }

/* Full-bleed until the page moves, then it draws itself in.

   The trick is where the horizontal padding lives. The SHELL carries the
   alignment padding and gives up exactly one gutter on scroll; the BAR takes
   that same gutter back. The sum never changes, so the wordmark and the
   controls sit still at one x while the island's own edges move around them —
   the shape animates, the contents do not.

   Both are addressed through :is() because a second row puts the bar inside
   .nav-stack, and a bare `>` would silently stop matching.
   ------------------------------------------------------------------------- */

/* At rest there is no bar — only what it holds. No surface, no blur, no line
   under it: the page starts at the top of the page. Everything the island is
   made of fades in together as it draws itself. */
.nav-shell-morph {
	max-width: none;
	top: 0;
	padding-inline: max(var(--gutter), calc((100% - var(--w-site)) / 2));
	background: none;
	transition: padding var(--dur-4) var(--ease-out),
		top var(--dur-4) var(--ease-out);
}

/* At rest the bar is not a surface at all: the shell is. */
:is(.nav-shell-morph > .nav-bar, .nav-shell-morph > .nav-stack > .nav-bar) {
	padding-inline: 0;
	border-color: transparent;
	border-radius: 0;
	background: none;
	backdrop-filter: none;
	box-shadow: none;
	transition: padding var(--dur-4) var(--ease-out),
		border-radius var(--dur-4) var(--ease-out),
		box-shadow var(--dur-4) var(--ease-out),
		border-color var(--dur-3) var(--ease-out),
		background var(--dur-3) var(--ease-out);
}

.nav-shell-morph[data-scrolled] {
	padding-inline: max(var(--gutter), calc((100% - var(--w-site)) / 2 - var(--gutter)));
	top: var(--space-3);
}

:is(.nav-shell-morph[data-scrolled] > .nav-bar,
	.nav-shell-morph[data-scrolled] > .nav-stack > .nav-bar) {
	padding-inline: var(--gutter);
	border-radius: var(--radius-pill);
	border-color: var(--line-default);
	background: color-mix(in srgb, var(--bg-canvas) 94%, transparent);
	backdrop-filter: saturate(180%) blur(14px);
	box-shadow: var(--shadow-2);
}

/* ── The progress border when the bar is flat ────────────────────────────────
   A ring makes sense around an island. Across a full-width bar it would draw a
   rectangle over the whole window — a border where the design asked for none.
   Flat, the same gradient becomes one line along the bottom edge: still the
   border, still the progress, no box.
   ------------------------------------------------------------------------- */

:is(.nav-progress.nav-shell-full > .nav-bar,
	.nav-progress.nav-shell-full > .nav-stack > .nav-bar)::after {
	inset: auto 0 0;
	height: var(--border-2);
	padding: 0;
	border-radius: 0;
	mask: none;
	-webkit-mask: none;
}

/* …and nothing at all while a morph bar is still at the top: a progress ring
   around an invisible bar is a rectangle drawn on the page. It fades in with
   the rest of the island. */
:is(.nav-progress.nav-shell-morph > .nav-bar,
	.nav-progress.nav-shell-morph > .nav-stack > .nav-bar)::after {
	opacity: 0;
	transition: opacity var(--dur-3) var(--ease-out),
		background var(--dur-4) var(--ease-out);
}

:is(.nav-progress.nav-shell-morph[data-scrolled] > .nav-bar,
	.nav-progress.nav-shell-morph[data-scrolled] > .nav-stack > .nav-bar)::after {
	opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
	.nav-shell-auto,
	.nav-shell-reveal,
	.nav-shell-morph,
	.nav-shell-morph > .nav-bar,
	.nav-shell-morph > .nav-stack > .nav-bar { transition: none; }
}

/* ── Centred mark, inverse bar, compact ──────────────────────────────────────
   Three shapes that are token swaps rather than layouts of their own.
   ------------------------------------------------------------------------- */

.nav-bar-center {
	display: grid;
	grid-template-columns: 1fr auto 1fr;
	justify-items: center;
}

.nav-bar-center > :first-child { justify-self: start; }
.nav-bar-center > :last-child { justify-self: end; }
.nav-bar-center .nav-links { margin: 0; }

/* Inverse — ink regardless of theme. Pair with the light mark: the logo reads
   currentColor, so it inverts with the bar and needs no second asset. */
.nav-bar-inverse {
	background: var(--ink-950);
	border-color: rgb(255 255 255 / 0.14);
	color: var(--ink-50);
	color-scheme: dark;
}

.nav-bar-inverse .logo,
.nav-bar-inverse .cds-mark { color: var(--pure-white, #fff); }
.nav-bar-inverse .nav-link { color: rgb(255 255 255 / 0.66); }
.nav-bar-inverse .nav-link:hover,
.nav-bar-inverse .nav-link[aria-current] { color: var(--pure-white, #fff); }
.nav-bar-inverse .nav-burger { border-color: rgb(255 255 255 / 0.2); background: rgb(255 255 255 / 0.06); }

.nav-bar-compact { --nav-h: 2.75rem; height: var(--nav-h); font-size: var(--text-sm); }
.nav-bar-compact .nav-link { height: 1.75rem; padding-inline: var(--space-2); }

/* ── Dropdown: hover and click, and room to grow ─────────────────────────────
   Pointer users get it on hover, everyone else on click, and both land on the
   same <details open> state — so there is one open state, not two. nav.js does
   the hover with an intent delay; the keyboard path is untouched.

   The panel grows to its content instead of jumping: 0fr → 1fr on a grid row
   animates to auto height, which `height: auto` still cannot.
   ------------------------------------------------------------------------- */

.nav-menu__panel { display: grid; grid-template-rows: 1fr; }

.nav-menu-grow > .nav-menu__panel {
	grid-template-rows: 0fr;
	transition: grid-template-rows var(--dur-3) var(--ease-out);
	animation: none;
	padding-block: 0;
	overflow: hidden;
}

.nav-menu-grow[open] > .nav-menu__panel {
	grid-template-rows: 1fr;
	padding-block: var(--space-2);
}

.nav-menu-grow > .nav-menu__panel > * { min-height: 0; }

/* Second level. It discloses in place rather than flying out, because a
   fly-out is unusable on touch and this is the same menu on every device. */
.nav-sub-menu { border-top: var(--border-hair) solid var(--line-subtle); margin-top: var(--space-1); }

.nav-sub-menu > summary {
	list-style: none;
	cursor: pointer;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-2);
	padding: var(--space-2) var(--space-3);
	border-radius: var(--radius-md);
	font-size: var(--text-sm);
	color: var(--fg-default);
	transition: var(--t-colors);
}

.nav-sub-menu > summary::-webkit-details-marker { display: none; }
.nav-sub-menu > summary:hover { background: var(--bg-sunken); }

.nav-sub-menu > summary::after {
	content: '';
	width: 0.35rem;
	height: 0.35rem;
	border-right: 1.5px solid currentColor;
	border-bottom: 1.5px solid currentColor;
	transform: rotate(-45deg);
	transition: var(--t-transform);
	opacity: 0.5;
}

.nav-sub-menu[open] > summary::after { transform: rotate(45deg); }

.nav-sub-menu__items {
	display: grid;
	grid-template-rows: 0fr;
	transition: grid-template-rows var(--dur-2) var(--ease-out);
	overflow: hidden;
	padding-left: var(--space-3);
	margin-left: var(--space-2);
	border-left: var(--border-hair) solid var(--line-subtle);
}

.nav-sub-menu[open] .nav-sub-menu__items { grid-template-rows: 1fr; }
.nav-sub-menu__items > * { min-height: 0; }

@media (prefers-reduced-motion: reduce) {
	.nav-menu-grow > .nav-menu__panel,
	.nav-sub-menu__items { transition: none; }
}

/* ── The record burger ───────────────────────────────────────────────────────
   Not three bars becoming an X. The bars collapse into the record light the
   whole system is built around, and the light unfolds into a play triangle —
   closed is "ready", open is "rolling". Same markup as every other burger, so
   it is a class swap.
   ------------------------------------------------------------------------- */

.nav-burger-rec .nav-burger__box { position: relative; }

.nav-burger-rec .nav-burger__bars,
.nav-burger-rec .nav-burger__bars::before,
.nav-burger-rec .nav-burger__bars::after {
	transition: transform var(--dur-2) var(--ease-out), opacity var(--dur-1) linear;
}

/* Open: the outer bars fold into the middle and go, the middle one goes last. */
.nav-burger-rec[aria-expanded='true'] .nav-burger__bars { opacity: 0; transition-delay: 60ms; }
.nav-burger-rec[aria-expanded='true'] .nav-burger__bars::before { transform: translateY(5px) scaleX(0.2); opacity: 0; }
.nav-burger-rec[aria-expanded='true'] .nav-burger__bars::after { transform: translateY(-5px) scaleX(0.2); opacity: 0; }

/* The record light, on its way to becoming the play head. */
.nav-burger-rec .nav-burger__box::before {
	content: '';
	position: absolute;
	inset: 50% auto auto 50%;
	width: 0.5rem;
	height: 0.5rem;
	margin: -0.25rem 0 0 -0.25rem;
	border-radius: var(--radius-full);
	background: var(--accent);
	transform: scale(0);
	transition: transform var(--dur-2) var(--ease-spring, var(--ease-out));
}

.nav-burger-rec[aria-expanded='true'] .nav-burger__box::before {
	transform: scale(1);
	transition-delay: 110ms;
}

/* The play triangle. Borders, so it inherits the accent and costs no asset. */
.nav-burger-rec .nav-burger__box::after {
	content: '';
	position: absolute;
	inset: 50% auto auto 50%;
	width: 0;
	height: 0;
	margin: -0.3rem 0 0 -0.2rem;
	border-top: 0.3rem solid transparent;
	border-bottom: 0.3rem solid transparent;
	border-left: 0.48rem solid var(--accent);
	transform: scale(0) rotate(-90deg);
	transition: transform var(--dur-3) var(--ease-spring, var(--ease-out));
}

.nav-burger-rec[aria-expanded='true'] .nav-burger__box::after {
	transform: scale(1) rotate(0deg);
	transition-delay: 220ms;
}

/* The dot ducks out as the triangle arrives, so one shape becomes the other. */
.nav-burger-rec[aria-expanded='true'] .nav-burger__box::before {
	animation: nav-rec-hand var(--dur-4) var(--ease-out) 110ms forwards;
}

@keyframes nav-rec-hand {
	0% { transform: scale(0); }
	45% { transform: scale(1); }
	100% { transform: scale(0); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
	.nav-burger-rec .nav-burger__box::before,
	.nav-burger-rec .nav-burger__box::after,
	.nav-burger-rec .nav-burger__bars,
	.nav-burger-rec .nav-burger__bars::before,
	.nav-burger-rec .nav-burger__bars::after { transition: none; animation: none; }

	.nav-burger-rec[aria-expanded='true'] .nav-burger__box::before { display: none; }
}

/* ── Three ways to open on small screens ─────────────────────────────────────
   The full-screen sheet is one answer, not the only one. All three take the
   same markup for the links, so choosing is a class, not a rewrite.

   .nav-sheet        full-screen dialog, irises open        (already above)
   .nav-sheet-drop   full-width panel dropping from the top edge
   .nav-panel        the island itself grows into the panel — nothing covers
                     the page, and the bar you pressed is still the bar
   ------------------------------------------------------------------------- */

/* 1 · The drop. Full width, hinged at the top, no iris. */
.nav-sheet-drop {
	height: auto;
	max-height: 100dvh;
	bottom: auto;
	border-radius: 0 0 var(--radius-sheet) var(--radius-sheet);
	border-bottom: var(--border-hair) solid var(--line-default);
	box-shadow: var(--shadow-4);
	overflow: hidden auto;
}

.nav-sheet-drop[open] { animation: nav-drop-in var(--dur-4) var(--ease-out); }

@keyframes nav-drop-in {
	from { transform: translateY(-100%); opacity: 0.4; }
	to { transform: none; opacity: 1; }
}

.nav-sheet-drop .nav-sheet__in { height: auto; padding-bottom: var(--space-8); }
.nav-sheet-drop .nav-sheet__links { margin-top: var(--space-5); overflow: visible; }
.nav-sheet-drop .nav-sheet__link { font-size: var(--text-xl); padding-block: var(--space-3); }

/* 2 · The island becomes the panel. No dialog, no backdrop, no page hidden
   behind a sheet — the thing you pressed simply has more in it now. */
.nav-panel {
	display: grid;
	/* minmax(0, …) matters: a bare `0fr` leaves the track's MINIMUM at auto,
	   so the panel's own padding (min-height:0 only zeroes the content box)
	   keeps ~40px of it on screen while closed. */
	grid-template-rows: minmax(0, 0fr);
	transition: grid-template-rows var(--dur-4) var(--ease-out);
	overflow: hidden;
	border-top: var(--border-hair) solid transparent;
}

.nav-panel > * { min-height: 0; overflow: hidden; }

/* The open state lands on whichever box the panel is a row of: the shell when
   the bar is the only row, the stack once there are more. Both are spelled out
   — a descendant selector would let an outer island open an inner one's panel. */
.nav-shell[data-open] > .nav-panel,
.nav-stack[data-open] > .nav-panel {
	grid-template-rows: minmax(0, 1fr);
	border-top-color: var(--line-subtle);
}

/* ── The open panel is a surface, not a hole ─────────────────────────────────
   The island's surface — background, blur, shadow — lives on .nav-bar, and the
   bar is only row 1. Left plain, the panel is a window onto the page: at rest
   there is nothing behind it yet so it looks right, but once the bar has drawn
   itself into an island the page scrolls straight through the open menu.

   So row 2 takes the same surface. Only while open: a closed panel is a
   0-height box, and a shadow on a 0-height box is a smudge across the page.
   ------------------------------------------------------------------------- */
.nav-shell[data-open] > .nav-panel,
.nav-stack[data-open] > .nav-panel {
	background: color-mix(in srgb, var(--bg-canvas) 94%, transparent);
	backdrop-filter: saturate(180%) blur(14px);
}

/* Island forms only — a full-bleed bar has no corners to continue. Here the
   two rows stop reading as two boxes: the bar gives up its bottom corners and
   the panel picks them up, so what opens is one island, taller. */
.nav-shell:not(.nav-shell-full, .nav-shell-morph)[data-open] > .nav-panel,
.nav-shell:not(.nav-shell-full, .nav-shell-morph) > .nav-stack[data-open] > .nav-panel,
.nav-shell-morph[data-scrolled][data-open] > .nav-panel,
.nav-shell-morph[data-scrolled] > .nav-stack[data-open] > .nav-panel {
	border: var(--border-hair) solid var(--line-default);
	border-top-color: var(--line-subtle);
	border-radius: 0 0 var(--radius-sheet) var(--radius-sheet);
	box-shadow: var(--shadow-2);
}

.nav-shell:not(.nav-shell-full, .nav-shell-morph)[data-open] > .nav-bar,
.nav-shell:not(.nav-shell-full, .nav-shell-morph) > .nav-stack[data-open] > .nav-bar,
.nav-shell-morph[data-scrolled][data-open] > .nav-bar,
.nav-shell-morph[data-scrolled] > .nav-stack[data-open] > .nav-bar {
	border-bottom-left-radius: 0;
	border-bottom-right-radius: 0;
	border-bottom-color: transparent;
}

.nav-panel__in {
	padding: var(--space-5);
	display: grid;
	gap: var(--space-5);
}

@media (min-width: 48rem) {
	.nav-panel__in { grid-template-columns: 1.4fr 1fr; }
}

.nav-panel__links { display: flex; flex-direction: column; gap: 2px; }

/* A dozen links in one column is a scroll; in two it is a menu you can read
   at a glance. The group labels keep their own full-width row. */
@media (min-width: 48rem) {
	.nav-panel__links-split {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 1fr));
		column-gap: var(--space-2);
		align-content: start;
	}

	.nav-panel__links-split .nav-panel__label { grid-column: 1 / -1; }
}

.nav-panel__link {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-3);
	padding: var(--space-3);
	border-radius: var(--radius-md);
	font-family: var(--font-display);
	font-size: var(--text-lg);
	font-weight: var(--weight-semibold);
	letter-spacing: var(--tracking-tight);
	color: var(--fg-default);
	text-decoration: none;
	transition: var(--t-colors);
}

.nav-panel__link:hover { background: var(--bg-sunken); }
.nav-panel__link[aria-current] { color: var(--accent); }

/* The other half: whatever the reader might want that is not a link — the
   latest thing, a form, the numbers. A menu is a chance to say something. */
.nav-panel__aside {
	background: var(--bg-sunken);
	border-radius: var(--radius-lg);
	padding: var(--space-4);
	display: grid;
	gap: var(--space-3);
	align-content: start;
}

.nav-panel__label {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
}

@media (prefers-reduced-motion: reduce) {
	.nav-panel { transition: none; }
	.nav-sheet-drop[open] { animation: none; }
}

/* ── Actions: the chips, the call, the form ──────────────────────────────────
   A creator's bar carries more than links: where else to find them, the one
   thing they want you to do, and the list. All three sit in the actions slot.
   ------------------------------------------------------------------------- */

.nav-actions { display: flex; align-items: center; gap: var(--space-2); }

.nav-icons { display: flex; align-items: center; gap: 2px; }

.nav-icon {
	display: inline-grid;
	place-items: center;
	width: var(--control-sm);
	height: var(--control-sm);
	border-radius: var(--radius-full);
	color: var(--fg-subtle);
	text-decoration: none;
	transition: var(--t-colors);
}

.nav-icon:hover { color: var(--fg-default); background: var(--hover-wash); }
.nav-icon .icon { width: 1.05rem; height: 1.05rem; }

/* The newsletter, folded into its own icon. Closed it is one more chip; open
   it is a field that grew out of that chip — the form was always there, it
   just had nothing to say yet. <details> so Escape and focus come free. */
.nav-form { position: relative; }

.nav-form > summary {
	list-style: none;
	cursor: pointer;
	display: inline-grid;
	place-items: center;
	width: var(--control-sm);
	height: var(--control-sm);
	border-radius: var(--radius-full);
	color: var(--fg-subtle);
	transition: var(--t-colors);
}

.nav-form > summary::-webkit-details-marker { display: none; }
.nav-form > summary:hover { color: var(--fg-default); background: var(--hover-wash); }
.nav-form[open] > summary { color: var(--accent); background: var(--accent-soft); }

.nav-form__field {
	display: flex;
	align-items: center;
	gap: var(--space-2);
	overflow: hidden;
}

/* Inline on a roomy bar: the field unrolls beside the chip. */
@media (min-width: 48rem) {
	.nav-form__field {
		position: absolute;
		right: calc(100% + var(--space-2));
		top: 50%;
		transform: translateY(-50%);
		width: 15rem;
		padding: var(--space-1);
		border: var(--border-hair) solid var(--line-default);
		border-radius: var(--radius-pill);
		background: var(--bg-surface);
		box-shadow: var(--shadow-2);
		animation: nav-unroll var(--dur-3) var(--ease-out);
		transform-origin: right center;
	}

	@keyframes nav-unroll {
		from { width: 0; opacity: 0; padding-inline: 0; }
	}
}

/* On a narrow bar it drops underneath instead, where there is room to type. */
@media (max-width: 47.999rem) {
	.nav-form__field {
		position: absolute;
		right: 0;
		top: calc(100% + var(--space-2));
		width: min(18rem, 78vw);
		padding: var(--space-2);
		border: var(--border-hair) solid var(--line-default);
		border-radius: var(--radius-lg);
		background: var(--bg-surface);
		box-shadow: var(--shadow-3);
		z-index: var(--z-popover, 40);
		animation: nav-drop-form var(--dur-2) var(--ease-out);
	}

	@keyframes nav-drop-form {
		from { opacity: 0; transform: translateY(-6px); }
	}
}

.nav-form__field input {
	flex: 1;
	min-width: 0;
	border: 0;
	background: none;
	font: inherit;
	font-size: var(--text-sm);
	color: inherit;
	padding-inline: var(--space-3);
}

.nav-form__field input:focus { outline: none; }

@media (prefers-reduced-motion: reduce) {
	.nav-form__field { animation: none; }
}

/* ── The publication logo, when the owner sets one ───────────────────────── */

.logo__img {
	display: block;
	height: 1.75rem;
	width: auto;
	max-width: 12rem;
	object-fit: contain;
}

/* ── The sheet's secondary section ───────────────────────────────────────── */

.nav-sheet__more {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-1) var(--space-3);
}

.nav-sheet__more .dropdown__item {
	padding: var(--space-1) 0;
	font-size: var(--text-sm);
	color: var(--fg-subtle);
}

.nav-sheet__more .dropdown__item:hover { background: none; color: var(--fg-default); }

/* ── Active state when the link carries an icon ──────────────────────────────
   The dot and an icon side by side are two marks competing for the same job.
   When there is an icon, IT becomes the active marker — accent, full opacity —
   and the dot stands down. Icon-less bars keep the dot.
   ------------------------------------------------------------------------- */

.nav-link:has(.icon)[aria-current]::before { display: none; }

.nav-link:has(.icon)[aria-current] .icon {
	color: var(--accent);
	opacity: 1;
}

/* Same idea in the sheet. */
.nav-sheet__link:has(.icon)[aria-current] .icon { color: var(--accent); opacity: 1; }

/* =============================================================================
   THE SITE BAR, ARRANGED
   Mark left · links CENTRED · actions right. The links are the page's subject,
   so they take the middle; the mark and the controls are furniture and hold
   the edges.
   ========================================================================== */

.nav-bar-center {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, auto) minmax(0, 1fr);
	gap: var(--space-3);
}

/* The centre column must be allowed to shrink, or a long menu pushes the
   actions off the end instead of overflowing into "More". */
.nav-bar-center .nav-links { min-width: 0; flex-wrap: nowrap; }
.nav-bar-center > :first-child,
.nav-bar-center > :last-child { min-width: 0; }
.nav-bar-center .cluster-sm { flex-wrap: nowrap; }
.nav-bar-center > :first-child { justify-self: start; }
.nav-bar-center > :last-child { justify-self: end; }
.nav-bar-center .nav-links { justify-self: center; }

/* Below the collapse breakpoint the grid collapses to mark | burger. */
@media (max-width: 62rem) {
	.nav-bar-center { display: flex; justify-content: space-between; }
}

/* ── Overflow: the 7th item onward lives behind "More" ────────────────────
   main.js moves the items; this only hides the trigger until it has any. */
.nav-more[hidden] { display: none; }

/* ── Per-item submenu ─────────────────────────────────────────────────────
   A nav item that owns a collection can list it. The trigger stays a link —
   the caret is a separate button so the label is still clickable. */

.nav-item { position: relative; display: inline-flex; align-items: center; }

/* The disclosure holds only the caret and the panel — never the link. */
.nav-sub { position: static; display: inline-flex; }
.nav-sub > summary { list-style: none; }
.nav-sub > summary::-webkit-details-marker { display: none; }

.nav-item__caret {
	display: inline-grid;
	place-items: center;
	width: 1.5rem;
	height: 1.5rem;
	margin-left: -0.25rem;
	border: 0;
	background: none;
	color: var(--fg-faint);
	cursor: pointer;
	border-radius: var(--radius-full);
	transition: var(--t-colors), var(--t-transform);
}

.nav-item__caret::after {
	content: '';
	width: 0.3rem;
	height: 0.3rem;
	border-right: 1.5px solid currentColor;
	border-bottom: 1.5px solid currentColor;
	transform: rotate(45deg) translate(-1px, -1px);
	transition: var(--t-transform);
}

.nav-item__caret:hover { color: var(--fg-default); background: var(--hover-wash); }
.nav-sub[open] > .nav-item__caret::after { transform: rotate(-135deg) translate(-1px, -1px); }

.nav-item__panel {
	position: absolute;
	top: calc(100% + var(--space-3));
	left: 50%;
	transform: translateX(-50%);
	min-width: 15rem;
	z-index: var(--z-popover, 40);
	background: var(--bg-surface);
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-lg);
	box-shadow: var(--shadow-3);
	padding: var(--space-2);
	display: flex;
	flex-direction: column;
	gap: 1px;
	animation: nav-drop var(--dur-2) var(--ease-out);
}

.nav-item__panel .dropdown__item { white-space: nowrap; }

/* ── The mega launcher — an app-grid button ──────────────────────────────── */

.nav-apps { position: static; }

.nav-apps-btn,
.nav-apps > summary {
	list-style: none;
	cursor: pointer;
	display: inline-grid;
	place-items: center;
	width: var(--control-sm);
	height: var(--control-sm);
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-full);
	background: var(--bg-surface);
	transition: var(--t-colors);
}

.nav-apps > summary::-webkit-details-marker { display: none; }
.nav-apps-btn { color: var(--fg-default); }
.nav-apps-btn:hover,
.nav-apps > summary:hover { border-color: var(--line-strong); background: var(--bg-sunken); }
.nav-apps[open] > summary { background: var(--bg-sunken); border-color: var(--line-strong); }

/* The nine dots, drawn — no icon file for something this geometric. */
.nav-apps__grid {
	display: grid;
	grid-template-columns: repeat(3, 3px);
	gap: 2px;
}

.nav-apps__grid i {
	width: 3px;
	height: 3px;
	border-radius: var(--radius-full);
	background: currentColor;
}

/* The middle dot is the record light — the mark hiding in the furniture. */
.nav-apps__grid i:nth-child(5) { background: var(--accent); }

/* The bar is the positioning context for its own dropdowns. Reading progress
   is NOT an element here — `.nav-progress` on the shell paints the island's
   hairline from --progress, so there is never a second bar under the bar.
   See "The line is the progress" in the docs. */
.nav-bar { position: relative; }

/* ── Closed panels must not paint ────────────────────────────────────────────
   A closed <details> hides its children with content-visibility, but an
   absolutely positioned child sits outside that flow and Chrome can still
   paint it — which shows up as a pale ghost box floating beside the caret.
   Say it explicitly instead of trusting the default.
   ------------------------------------------------------------------------- */

.nav-sub:not([open]) .nav-item__panel,
.nav-menu:not([open]) .nav-menu__panel,
.nav-menu:not([open]) .nav-mega__panel,
.dropdown:not([open]) .dropdown__menu { display: none; }

/* ── Making the submenu findable ─────────────────────────────────────────────
   A caret is a 24px target that nobody thinks to click. On a pointer device
   the whole item opens its panel on hover (JS adds a short intent delay);
   the caret stays the explicit control for touch and keyboard.
   ------------------------------------------------------------------------- */

/* The caret announces itself as interactive rather than hiding as decoration. */
.nav-item:hover .nav-item__caret { color: var(--fg-default); }

/* Bridge the gap between the item and its panel, so the pointer can travel
   down without crossing dead space and closing it. */
.nav-sub[open]::after {
	content: '';
	position: absolute;
	top: 100%;
	left: 0;
	right: 0;
	height: var(--space-3);
}

/* The open item keeps its own highlight while the panel is down. */
.nav-item:has(.nav-sub[open]) .nav-link { background: var(--hover-wash); }

/* ── The burger that is not a fallback ───────────────────────────────────────
   `.nav-collapse` hides the burger while the links are visible, because there
   it IS the links. When the burger opens the full panel instead — every
   collection, the latest post, the newsletter — it is a destination of its
   own and belongs on screen at every width.
   ------------------------------------------------------------------------- */

.nav-collapse .nav-burger-pinned { display: inline-flex; }

/* ── The panel stays inside the viewport ─────────────────────────────────────
   A menu taller than the screen is a scroll inside a scroll: the page moves
   under you while you are still reading the menu. Cap it at what is visible
   and let the panel itself scroll.
   ------------------------------------------------------------------------- */

.nav-panel__in {
	max-height: calc(100dvh - var(--nav-h, 3.5rem) - var(--space-10));
	overflow-y: auto;
	overscroll-behavior: contain;
	scrollbar-width: thin;
}

/* The newsletter is the one ask in the panel, so it reads as a block rather
   than another link in a list. */
.nav-panel__aside {
	background: var(--bg-sunken);
	border-radius: var(--radius-lg);
	padding: var(--space-4);
	align-content: start;
}

.nav-panel__cta {
	display: grid;
	gap: var(--space-2);
	margin-top: var(--space-3);
}

.nav-panel__cta .input { width: 100%; }

/* The label group. `.nav-panel__link` is space-between so a count can sit on
   the right; without one, the icon and its label must still travel together
   or the label drifts to the far edge. */
.nav-panel__name {
	display: inline-flex;
	align-items: center;
	gap: var(--space-3);
	min-width: 0;
}

.nav-panel__name .icon { flex: none; opacity: 0.55; }
.nav-panel__link[aria-current] .nav-panel__name .icon { color: var(--accent); opacity: 1; }      /* alignment, dropdown, mega, sheet, contexts */
/* =============================================================================
   34 · AD
   Placeholder ad units — IAB-shaped slots that behave like the real thing:
   a skeleton while nothing has "loaded" yet, a lazy load that only fires once
   the slot is actually on screen (src/ad.js, IntersectionObserver), and a
   reader's own "hide ads" switch. Every state is an attribute the script sets;
   this file is the only place any of it is drawn.

   Sizes are IAB standard units, chosen by class, capped by max-width so a
   728×90 leaderboard still fits a phone. `.ad-responsive` is the one with no
   fixed ratio — full width, a floor height, the shape AdSense itself calls
   "responsive".

   Requires 1-foundation, 2-elements (.skeleton), 3-components/28 (.btn-close).
   ========================================================================== */

.ad {
	position: relative;
	display: grid;
	place-items: center;
	width: 100%;
	margin-inline: auto;
	overflow: hidden;
	isolation: isolate;
	border: var(--border-hair) solid var(--line-subtle);
	border-radius: var(--radius-md);
	background: var(--bg-sunken);
}

.ad::before {
	content: 'Advertisement';
	position: absolute;
	top: var(--space-2);
	left: var(--space-2);
	z-index: 2;
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
	pointer-events: none;
}

/* ── Sizes — IAB standard units ─────────────────────────────────────────── */

.ad-leaderboard   { max-width: 728px; aspect-ratio: 728 / 90; }
.ad-rectangle     { max-width: 300px; aspect-ratio: 300 / 250; }
.ad-skyscraper    { max-width: 160px; aspect-ratio: 160 / 600; }
.ad-mobile-banner { max-width: 320px; aspect-ratio: 320 / 50; }
.ad-mobile-large  { max-width: 320px; aspect-ratio: 320 / 100; }

.ad-responsive { min-height: 6rem; }

@media (max-width: 40rem) {
	.ad-leaderboard { aspect-ratio: 320 / 100; max-width: 320px; }
	.ad-skyscraper { aspect-ratio: 300 / 250; max-width: 300px; }
}

/* ── The two layers — skeleton underneath, the "creative" on top ─────────── */

.ad__skeleton { position: absolute; inset: 0; border-radius: 0; }

.ad__slot {
	position: relative;
	z-index: 1;
	display: grid;
	place-items: center;
	gap: var(--space-2);
	text-align: center;
	padding: var(--space-6);
	opacity: 0;
	pointer-events: none;
	transition: opacity var(--dur-3) var(--ease-out);
}

.ad__cta {
	font-family: var(--font-display);
	font-weight: var(--weight-semibold);
	color: var(--fg-muted);
}

.ad__dims {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
}

/* ── Hide control — the reader's own switch ───────────────────────────────
   .btn-close already draws the × from two pseudo-lines; this only places it. */
.ad__hide {
	position: absolute;
	top: var(--space-2);
	right: var(--space-2);
	z-index: 3;
	background: var(--bg-surface);
	border: var(--border-hair) solid var(--line-subtle);
}

/* ── States — set by src/ad.js, never guessed at by CSS ───────────────────
   idle/loading: skeleton only. loaded: creative fades in, skeleton drops out.
   hidden: the reader asked, so the whole slot collapses out of the layout. */

.ad[data-ad-state='idle']    .ad__skeleton,
.ad[data-ad-state='loading'] .ad__skeleton { opacity: 1; }

.ad[data-ad-state='loaded'] .ad__skeleton {
	opacity: 0;
	transition: opacity var(--dur-2) var(--ease-out);
}

.ad[data-ad-state='loaded'] .ad__slot {
	opacity: 1;
	pointer-events: auto;
}

.ad-animate[data-ad-state='loaded'] .ad__slot {
	animation: fx-rise var(--dur-3) var(--ease-out) both;
}

.ad[data-ad-state='hidden'] {
	display: none;
}

/* No JS / no IntersectionObserver: everything below still reads as a slot,
   not a hole in the page — the skeleton is the resting state regardless. */
.ad:not([data-ad-state]) .ad__skeleton { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
	.ad-animate[data-ad-state='loaded'] .ad__slot { animation: none; }
}          /* IAB ad slots — skeleton, lazy, hide  */
/* =============================================================================
   35 · TIMELINE
   An ordered sequence with a rail through it. 27-composite.css opens by saying
   its four organs are "the same idea — an ordered list that knows where you
   are — wearing its collection's clothes"; this is that idea with no clothes
   on, so a page that needs a plain sequence does not have to borrow a build
   log and override the parts that say "project".

   Two axes, and the axis is the only structural decision:

     .tl        vertical   — the default. Unbounded length, reads as a list.
     .tl-h      horizontal — bounded, reads as a process. Scrolls on overflow
                             and folds back to vertical when the viewport is
                             too narrow to hold it honestly.

   Everything else is a modifier that works on both:

     .tl-compact   tighter rhythm, no notes expected
     .tl-lg        roomier, for three or four milestones that matter
     .tl-alt       vertical only — items alternate sides of the rail
     .tl-ranged    the rail is a span (a role, a trip) rather than points

   State is ARIA and data attributes, never a styling-only class:

     [data-done]     behind you — the node fills
     [aria-current]  you are here — the node takes the accent ring
     [data-kind]     start | ship | now — swaps the node glyph

   MARKUP
     <ol class="tl">
       <li class="tl__item" data-done>
         <span class="tl__node"></span>
         <div class="tl__body">
           <span class="tl__time">Jun 2024</span>
           <span class="tl__title">Foundation tokens land</span>
           <p class="tl__note">Three tiers, one rename from a rebrand.</p>
         </div>
       </li>
     </ol>
   ========================================================================== */

.tl {
	--tl-node: 1.45rem;
	--tl-gap: var(--space-5);
	--tl-rail: var(--border-hair);

	list-style: none;
	margin: 0;
	padding: 0;
	position: relative;
	display: flex;
	flex-direction: column;
	gap: var(--tl-gap);
}

/* The rail. Inset by half a node so it runs through their centres, and stopped
   short at both ends — a line that runs past the first and last node reads as
   "there is more", which is a lie in a finished sequence. */
.tl::before {
	content: '';
	position: absolute;
	left: calc(var(--tl-node) / 2 - var(--tl-rail) / 2);
	top: calc(var(--tl-node) / 2);
	bottom: calc(var(--tl-node) / 2);
	width: var(--tl-rail);
	background: var(--line-default);
}

.tl__item {
	position: relative;
	display: flex;
	align-items: flex-start;
	gap: var(--space-4);
	min-width: 0;
}

.tl__node {
	position: relative;
	z-index: 1;
	flex: none;
	width: var(--tl-node);
	height: var(--tl-node);
	border-radius: var(--radius-full);
	border: var(--border-2) solid var(--line-strong);
	background: var(--bg-canvas);
	display: grid;
	place-items: center;
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	font-variant-numeric: tabular-nums;
	color: var(--fg-subtle);
	transition: var(--t-colors), var(--t-shadow);
}

.tl__body {
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 2px;
	padding-top: calc((var(--tl-node) - 1.15rem) / 2);
}

.tl__time {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
	font-variant-numeric: tabular-nums;
	order: -1;
}

.tl__title {
	font-weight: var(--weight-semibold);
	font-size: var(--text-sm);
	line-height: var(--leading-snug);
	color: var(--fg-default);
}

/* A whole item can be a link. Kept as its own class so the title stays a title
   when it isn't one. */
a.tl__title { text-decoration: none; transition: var(--t-colors); }
a.tl__title:hover { color: var(--fg-accent); }

.tl__note {
	font-size: var(--text-sm);
	line-height: var(--leading-normal);
	color: var(--fg-subtle);
	max-width: var(--measure-ui);
}

.tl__meta {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-1);
	margin-top: var(--space-2);
}

/* ── State ────────────────────────────────────────────────────────────────── */

.tl__item[data-done] .tl__node {
	background: var(--fg-default);
	border-color: var(--fg-default);
	color: var(--bg-canvas);
}

.tl__item[data-done] .tl__node::before { content: '✓'; }

.tl__item[aria-current] .tl__node {
	border-color: var(--accent);
	color: var(--accent);
	background: var(--bg-canvas);
	box-shadow: 0 0 0 3px var(--accent-ring);
}

.tl__item[aria-current] .tl__node::before { content: none; }
.tl__item[aria-current] .tl__title { color: var(--fg-default); }

/* Everything after the current item is ahead of you — the rail behind stays
   solid, the copy ahead recedes. */
.tl__item[aria-current] ~ .tl__item .tl__title { font-weight: var(--weight-medium); }
.tl__item[aria-current] ~ .tl__item .tl__node { border-style: dashed; }

/* A node carrying a real mark — a company logo, an avatar — does not also get
   the generated tick: two glyphs in one 2rem circle is one too many. The mark
   wins, because it says more than "done" does. Sized so a 1rem icon sits inside
   the 2rem node of .tl-lg with room to breathe. */
.tl__node:has(> svg)::before { content: none !important; }

.tl__node > svg {
	width: 1rem;
	height: 1rem;
	/* Inherits the node's colour, so it flips to the canvas colour on a filled
	   [data-done] node without a second rule. */
	color: currentcolor;
}

/* Glyph kinds. Content only — the node geometry never changes. */
.tl__item[data-kind='start'] .tl__node::before { content: '▸'; }
.tl__item[data-kind='ship'] .tl__node::before { content: '✓'; }
.tl__item[data-kind='now'] .tl__node::before { content: ''; }

.tl__item[data-kind='now'] .tl__node {
	border-color: var(--accent);
	background: var(--accent);
	color: var(--fg-on-accent);
}

/* ── Density ──────────────────────────────────────────────────────────────── */

.tl-compact { --tl-node: 1rem; --tl-gap: var(--space-3); }
.tl-compact .tl__title { font-size: var(--text-xs); }
.tl-compact .tl__node { font-size: 0; }

.tl-lg { --tl-node: 2rem; --tl-gap: var(--space-8); }
.tl-lg .tl__title { font-size: var(--text-base); }

/* ── Ranged — the rail is a span, not a point ─────────────────────────────────
   A role held for three years is a bar, not a dot. The node becomes a capsule
   whose height fills its own row, which is what makes a CV read as duration. */

.tl-ranged .tl__node {
	border-radius: var(--radius-pill);
	height: auto;
	align-self: stretch;
	min-height: var(--tl-node);
	/* A capsule that fills the row carries its mark at the TOP, where the span
	   begins — an icon floating in the vertical middle of a three-year bar is
	   pointing at nothing. */
	place-items: start center;
	padding-top: calc((var(--tl-node) - 1rem) / 2);
}

/* The tick/glyph is meaningless on a duration: the bar IS the statement. An
   icon placed in the node deliberately (a company mark) still shows. */
.tl-ranged .tl__item[data-done] .tl__node::before,
.tl-ranged .tl__node::before { content: none; }

.tl-ranged .tl__node > .icon,
.tl-ranged .tl__node > .icon-sm { opacity: 0.9; }

/* ── Alternating — vertical only ──────────────────────────────────────────────
   Items zig-zag either side of a centred rail. Only honest with short copy and
   an even-ish count, and only above the fold-point — below it, it collapses to
   the plain vertical rail rather than shrinking two columns into nothing. */

@media (min-width: 48rem) {
	.tl-alt::before { left: 50%; transform: translateX(-50%); }

	.tl-alt .tl__item {
		width: 50%;
		padding-right: var(--space-8);
		flex-direction: row-reverse;
		text-align: right;
	}

	.tl-alt .tl__item .tl__body { align-items: flex-end; }

	.tl-alt .tl__node {
		position: absolute;
		right: calc(-1 * var(--tl-node) / 2);
		top: 0;
	}

	.tl-alt .tl__item:nth-child(even) {
		margin-left: 50%;
		padding-right: 0;
		padding-left: var(--space-8);
		flex-direction: row;
		text-align: left;
	}

	.tl-alt .tl__item:nth-child(even) .tl__body { align-items: flex-start; }

	.tl-alt .tl__item:nth-child(even) .tl__node {
		right: auto;
		left: calc(-1 * var(--tl-node) / 2);
	}
}

/* ── Horizontal ───────────────────────────────────────────────────────────────
   A process, not a list. Scrolls rather than wraps: a horizontal timeline that
   wraps to a second row has stopped being one line of time. */

@media (min-width: 48rem) {
	.tl-h {
		flex-direction: row;
		gap: 0;
		overflow-x: auto;
		overscroll-behavior-x: contain;
		scroll-snap-type: x proximity;
		padding-bottom: var(--space-3);
		scrollbar-width: thin;
	}

	.tl-h::before {
		left: var(--space-6);
		right: var(--space-6);
		top: calc(var(--tl-node) / 2);
		bottom: auto;
		width: auto;
		height: var(--tl-rail);
	}

	.tl-h .tl__item {
		flex-direction: column;
		align-items: flex-start;
		gap: var(--space-3);
		flex: 1 0 12rem;
		min-width: 12rem;
		padding-right: var(--space-5);
		scroll-snap-align: start;
	}

	.tl-h .tl__body { padding-top: 0; }
	.tl-h .tl__note { max-width: none; }

	/* Centre the nodes over the rail rather than left-aligning them, so the
	   first and last node sit inside the rail's inset. */
	.tl-h .tl__item:first-child { padding-left: 0; }
}

/* ── Reduced motion / print ───────────────────────────────────────────────── */

@media print {
	.tl-h { flex-direction: column; overflow: visible; }
	.tl-h::before { left: calc(var(--tl-node) / 2); right: auto; top: 0; bottom: 0; width: var(--tl-rail); height: auto; }
	.tl__item[aria-current] .tl__node { box-shadow: none; }
	.tl-alt .tl__item,
	.tl-alt .tl__item:nth-child(even) { width: auto; margin-left: 0; padding: 0; text-align: left; flex-direction: row; }
	.tl-alt::before { left: calc(var(--tl-node) / 2); transform: none; }
	.tl-alt .tl__node { position: relative; right: auto; left: auto; }
}    /* timeline — vertical, horizontal, alt, ranged */
/* =============================================================================
   36 · COMMENTS
   A thread, and the box that adds to it. Framework-agnostic on purpose: this is
   the markup Ghost, Disqus, Giscus and a hand-rolled endpoint all end up
   needing, so the CSS commits to a shape and nothing else.

   Every collection's single page can carry it — a blog post, a lesson, an
   episode, a build-log entry — because "someone said something about this
   thing" is not a blog-specific idea, and the alternative is four slightly
   different comment sections.

     <section class="comments">
       <header class="comments__head"> count · sort </header>
       <form class="comment-box"> … </form>
       <ol class="comment-list">
         <li class="comment">
           <span class="comment__face"> avatar </span>
           <div class="comment__body">
             <div class="comment__head"> author · time · badge </div>
             <div class="comment__text"> … </div>
             <div class="comment__actions"> reply · like </div>
             <ol class="comment-list"> … nested replies … </ol>
           </div>
         </li>
       </ol>
     </section>

   Nesting is ONE level deep by design. A thread that indents five times is a
   thread nobody can read on a phone, and the fix people reach for — shrinking
   the indent — just makes the hierarchy invisible instead of unreadable.
   ========================================================================== */

.comments {
	margin-top: var(--space-12);
	padding-top: var(--space-8);
	border-top: var(--border-hair) solid var(--line-default);
}

.comments__head {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	flex-wrap: wrap;
	gap: var(--space-3);
	margin-bottom: var(--space-6);
}

.comments__count {
	font-family: var(--font-display);
	font-size: var(--text-xl);
	font-weight: var(--weight-semibold);
	letter-spacing: var(--tracking-tight);
}

.comments__count span {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
	font-weight: var(--weight-regular);
	margin-left: var(--space-2);
}

/* ── The box ──────────────────────────────────────────────────────────────── */

.comment-box {
	display: grid;
	gap: var(--space-3);
	padding: var(--space-4);
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-card);
	background: var(--bg-surface);
	margin-bottom: var(--space-8);
}

.comment-box__row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	flex-wrap: wrap;
	gap: var(--space-3);
}

/* The textarea grows with its content where the browser supports it, so a long
   comment does not get typed into a three-line window. */
.comment-box textarea.input {
	min-height: 5.5rem;
	resize: vertical;
	field-sizing: content;
}

.comment-box__note {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	color: var(--fg-faint);
}

/* Signed-out state: the box is replaced by the ask, not disabled — a disabled
   form is a dead end with no explanation attached. */
.comment-box-locked {
	display: flex;
	align-items: center;
	justify-content: space-between;
	flex-wrap: wrap;
	gap: var(--space-4);
	background: var(--bg-sunken);
}

/* ── The thread ───────────────────────────────────────────────────────────── */

.comment-list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	gap: var(--space-6);
}

.comment {
	display: flex;
	gap: var(--space-4);
	min-width: 0;
}

.comment__face {
	flex: none;
	width: 2.5rem;
	height: 2.5rem;
	border-radius: var(--radius-full);
	overflow: hidden;
	background: var(--bg-sunken);
	display: grid;
	place-items: center;
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	color: var(--fg-subtle);
}

.comment__face > img { width: 100%; height: 100%; object-fit: cover; }

.comment__body { min-width: 0; flex: 1; }

.comment__head {
	display: flex;
	align-items: baseline;
	flex-wrap: wrap;
	gap: var(--space-2) var(--space-3);
	margin-bottom: var(--space-2);
}

.comment__author { font-weight: var(--weight-semibold); font-size: var(--text-sm); }

.comment__time {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	color: var(--fg-faint);
}

.comment__text {
	font-size: var(--text-sm);
	line-height: var(--leading-relaxed);
	color: var(--fg-muted);
}

.comment__text > * + * { margin-top: var(--space-3); }

.comment__actions {
	display: flex;
	align-items: center;
	gap: var(--space-4);
	margin-top: var(--space-3);
}

.comment__action {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	background: none;
	border: 0;
	padding: 0;
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
	cursor: pointer;
	transition: var(--t-colors);
}

.comment__action:hover { color: var(--fg-default); }
.comment__action .icon { width: 0.9rem; height: 0.9rem; }

/* Liked is a state, so it lives in ARIA. */
.comment__action[aria-pressed='true'] { color: var(--accent); }
.comment__action[aria-pressed='true'] .icon { fill: currentcolor; }

/* The author of the thing being discussed. Named, not just coloured. */
.comment-author .comment__author::after {
	content: 'Author';
	margin-left: var(--space-2);
	padding: 1px 6px;
	border-radius: var(--radius-pill);
	background: var(--accent-soft);
	color: var(--accent-soft-fg);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	vertical-align: 1px;
}

/* ── Replies — one level, with a rail ─────────────────────────────────────── */

.comment .comment-list {
	margin-top: var(--space-5);
	padding-left: var(--space-5);
	border-left: var(--border-hair) solid var(--line-subtle);
	gap: var(--space-5);
}

.comment .comment .comment__face { width: 2rem; height: 2rem; }

/* A reply to a reply flattens rather than indenting again — the rail already
   said "this is a response", and a second one says nothing new. */
.comment .comment .comment-list {
	padding-left: 0;
	border-left: 0;
}

@media (max-width: 40rem) {
	/* On a phone the indent costs more than the hierarchy is worth. */
	.comment .comment-list { padding-left: var(--space-3); }
	.comment { gap: var(--space-3); }
}     /* comments — thread, box, one level of replies */
/* =============================================================================
   SWARNIL DESIGN SYSTEM · 5 — SECTIONS
   Full-width bands: a section is a component that owns its own rhythm. Cards
   know nothing about the page; sections ARE the page. Everything here is a
   composition of layers 1–3 — no new colours, no new type sizes, no new
   radii. If a section needs a token that doesn't exist, the section is wrong.

   Requires 1-foundation, 2-elements, 3-components.
   ========================================================================== */

/* =============================================================================
   30 · HEADERS
   Two jobs: open a page (.page-head) and open a section within a page
   (.sec-head-row). Both speak the same voice — slate eyebrow, display face,
   one optional action on the right — so a collection index and a homepage
   band read as the same publication.
   ========================================================================== */

/* ── Page header — the top of a collection index ─────────────────────────── */

.page-head {
	padding-block: var(--space-12) var(--space-10);
	border-bottom: var(--border-hair) solid var(--line-default);
}

.page-head__kicker {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--accent);
	margin-bottom: var(--space-4);
}

.page-head__kicker .icon { width: 1rem; height: 1rem; }

.page-head__title {
	font-family: var(--font-display);
	font-size: var(--text-5xl);
	font-weight: var(--weight-bold);
	letter-spacing: var(--tracking-tighter);
	line-height: var(--leading-tight);
	max-width: 16ch;
	text-wrap: balance;
}

.page-head__desc {
	margin-top: var(--space-4);
	font-size: var(--text-lg);
	line-height: var(--leading-relaxed);
	color: var(--fg-subtle);
	max-width: var(--measure-lead);
}

/* The meta row: count, feed link, filters — quiet, mono, bottom-aligned. */
.page-head__meta {
	display: flex;
	align-items: center;
	justify-content: space-between;
	flex-wrap: wrap;
	gap: var(--space-3) var(--space-5);
	margin-top: var(--space-8);
}

.page-head__count {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
}

.page-head__count b { color: var(--fg-default); font-weight: var(--weight-medium); }

/* ── Split page header — copy left, facts right ───────────────────────────────
   For a page whose header has to carry a block of standing facts as well as a
   title: a résumé (location, nationality, phone, availability), a profile, a
   product page's spec block. Those are a *reference table*, not a lead — they
   are scanned, and stacking them under the lead pushes the content down while
   making them read as prose.

     <header class="page-head page-head-split">
       <div class="page-head__main"> kicker · title · desc · actions </div>
       <aside class="page-head__aside"> the facts </aside>
     </header>

   One column below 56rem, because two columns of small print on a phone is
   worse than either one alone. */

.page-head-split {
	display: grid;
	gap: var(--space-8);
	align-items: start;
}

/* The copy column. Its own class so the grid has two named children rather than
   one named and one positional — a bare <div> here is the thing that breaks the
   next time someone adds a wrapper. */
.page-head__main { min-width: 0; }

@media (min-width: 56rem) {
	.page-head-split {
		grid-template-columns: minmax(0, 1fr) minmax(0, 19rem);
		gap: var(--space-12);
	}

	/* Optically aligned to the title rather than the kicker above it — the
	   kicker is a label, and the facts are answering the title. */
	.page-head__aside { padding-top: var(--space-6); }
}

/* A definition row: label above, value below, so the column scans down the
   values without the labels competing for the same weight. */
.page-head__facts {
	display: grid;
	gap: var(--space-3);
}

.page-head__fact {
	display: flex;
	align-items: flex-start;
	gap: var(--space-3);
	font-size: var(--text-sm);
}

.page-head__fact > .icon {
	color: var(--fg-faint);
	flex: none;
	margin-top: 1px;
}

.page-head__fact dt {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
}

.page-head__fact dd { margin: 0; color: var(--fg-default); }

/* Compact page header — child pages (a lesson index, a tag) breathe less. */
.page-head-sm { padding-block: var(--space-8) var(--space-6); }
.page-head-sm .page-head__title { font-size: var(--text-3xl); }
.page-head-sm .page-head__desc { font-size: var(--text-base); margin-top: var(--space-3); }
.page-head-sm .page-head__meta { margin-top: var(--space-5); }

/* ── Section header — a band's opening line ──────────────────────────────── */

.sec-head-row {
	display: flex;
	align-items: flex-end;
	justify-content: space-between;
	gap: var(--space-4);
	margin-bottom: var(--space-8);
}

.sec-head-row__title {
	font-family: var(--font-display);
	font-size: var(--text-2xl);
	font-weight: var(--weight-semibold);
	letter-spacing: var(--tracking-tight);
	line-height: var(--leading-tight);
}

.sec-head-row__kicker {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
	margin-bottom: var(--space-2);
}

/* "View all →" — the one action a band is allowed. */
.sec-head-row__more {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	font-family: var(--font-slate);
	font-size: var(--text-xs);
	letter-spacing: var(--tracking-wide);
	text-transform: uppercase;
	color: var(--fg-subtle);
	text-decoration: none;
	white-space: nowrap;
	padding-bottom: 2px;
	transition: var(--t-colors);
}

.sec-head-row__more .icon { width: 0.9em; height: 0.9em; transition: var(--t-transform); }
.sec-head-row__more:hover { color: var(--fg-default); }
.sec-head-row__more:hover .icon { transform: translateX(3px); }  /* page + section headers, collection intro  */
/* =============================================================================
   31 · HERO
   Three shapes, one rule: a hero states, it does not list. One headline with
   one accent word, one supporting line, at most two actions.

   .hero-statement  — centred manifesto (about, 404, landing bands)
   .hero-split      — copy left, stage right (the homepage shape)
   .hero-band       — full-bleed inverse billboard (webseries, travel)
   ========================================================================== */

/* ── Shared bones ────────────────────────────────────────────────────────── */

.hero { position: relative; padding-block: var(--space-16) var(--space-12); }

.hero__eyebrow {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-subtle);
}

.hero__title {
	font-family: var(--font-display);
	font-size: var(--text-6xl);
	font-weight: var(--weight-bold);
	letter-spacing: var(--tracking-tighter);
	line-height: var(--leading-flat);
	text-wrap: balance;
	margin-top: var(--space-5);
}

/* The one rationed accent word. */
.hero__title em { font-style: normal; color: var(--accent); }

.hero__lead {
	margin-top: var(--space-5);
	font-size: var(--text-lg);
	line-height: var(--leading-relaxed);
	color: var(--fg-subtle);
	max-width: var(--measure-lead);
}

.hero__actions {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: var(--space-3);
	margin-top: var(--space-8);
}

/* ── Statement — centred ─────────────────────────────────────────────────── */

.hero-statement { text-align: center; }
.hero-statement .hero__lead { margin-inline: auto; }
.hero-statement .hero__actions { justify-content: center; }
.hero-statement .hero__title { max-width: 18ch; margin-inline: auto; }

/* ── Split — copy beside a stage ─────────────────────────────────────────── */

.hero-split {
	display: grid;
	gap: var(--space-10);
	align-items: center;
}

@media (min-width: 64rem) {
	.hero-split { grid-template-columns: 1.05fr 1fr; gap: var(--space-12); }
}

/* A headline in a HALF-WIDTH column cannot take the full-width statement size:
   at --text-6xl the homepage headline ran to six lines and the eye had to
   re-find the left edge on every one of them. One step down the scale, and a
   measure in ch so it breaks at three lines instead of wherever the column
   happens to end. */
.hero-split .hero__title { font-size: var(--text-4xl); max-width: 16ch; }
.hero-split .hero__lead { max-width: 42ch; }

/* The stage holds one framed thing — a player, a poster, a device. The frame
   device (layer 1) does the chrome; the stage only reserves the ratio. */
.hero-split__stage { position: relative; }

.hero-split__stage > * { width: 100%; }

/* Caption under the stage — camera-log voice. */
.hero-split__caption {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-3);
	margin-top: var(--space-3);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: 0.06em;
	color: var(--fg-faint);
}

/* ── Band — full-bleed inverse billboard ─────────────────────────────────── */

.hero-band {
	background: var(--bg-inverse);
	color: var(--fg-on-inverse);
	border-radius: var(--radius-sheet);
	overflow: hidden;
	padding: var(--space-16) var(--space-8);
}

.hero-band[data-surface] { border-radius: var(--radius-sheet); }

.hero-band .hero__eyebrow { color: var(--fg-on-inverse); opacity: 0.7; }
.hero-band .hero__lead { color: var(--fg-on-inverse); opacity: 0.75; }

/* Media behind the copy: an absolutely-filled poster + scrim, copy on top. */
.hero-band__media {
	position: absolute;
	inset: 0;
	z-index: 0;
}

.hero-band__media img,
.hero-band__media video { width: 100%; height: 100%; object-fit: cover; }

.hero-band__media::after {
	content: '';
	position: absolute;
	inset: 0;
	background:
		linear-gradient(to top, var(--bg-scrim) 0%, transparent 55%),
		linear-gradient(to right, var(--bg-scrim) 0%, transparent 65%);
}

.hero-band-media { position: relative; }
.hero-band-media > :not(.hero-band__media) { position: relative; z-index: 1; }

/* Bottom-anchored copy for the media variant — billboard grammar. */
.hero-band-media { display: flex; flex-direction: column; justify-content: flex-end; min-height: 24rem; }

/* ── Cinematic — the band at full viewport, with film over the footage ──────
   travel/index.html carried this as forty lines of inline style before it was
   a component, which meant no other page could have it. Three parts, each
   independent: the full-bleed height, the scanline film, and a pointer-tracked
   ripple that reveals the film only where the cursor is.

   Composes with .hero-band-media — that still owns the media and the scrim. */

.hero-band-full {
	width: 100vw;
	margin-left: calc(50% - 50vw);
	min-height: 100dvh;
	border-radius: 0;
	/* Sit UNDER the floating island rather than below it, so the nav reads as
	   furniture on the footage. Same pull-up the collection hero uses. */
	margin-top: calc(-1 * var(--nav-bleed));
	justify-content: center;
	isolation: isolate;
}

/* The film. Put a .pattern-scanline (or -fine / -coarse) inside it. */
.hero-band__scan {
	position: absolute;
	inset: 0;
	z-index: 0;
	pointer-events: none;
	mix-blend-mode: overlay;
}

.hero-band__scan > * { position: absolute; inset: 0; }

/* Ripple: --mx/--my are set by a pointermove handler (src/nav.js does it for
   any [data-ripple]); with no script the mask rests centred and still, which
   is a vignette rather than a broken effect. */
.hero-band__scan[data-ripple] {
	--mx: 50%;
	--my: 40%;
	mask-image: radial-gradient(circle 260px at var(--mx) var(--my), #000 0%, transparent 72%);
	-webkit-mask-image: radial-gradient(circle 260px at var(--mx) var(--my), #000 0%, transparent 72%);
}

/* Reduced motion: no chase, no displacement — the film sits still and quiet
   over the whole frame instead of following anything. */
@media (prefers-reduced-motion: reduce) {
	.hero-band__scan[data-ripple] { mask-image: none; -webkit-mask-image: none; opacity: 0.3; }
}

@media (max-width: 40rem) {
	.hero { padding-block: var(--space-12) var(--space-10); }
	.hero__title { font-size: var(--text-4xl); }
	.hero-band { padding: var(--space-10) var(--space-5); }
	/* A 100dvh hero on a phone is a full screen of nothing but a headline. */
	.hero-band-full { min-height: 82dvh; }
}

/* ── Size modifier — for pages whose hero is an announcement, not a stage
   (sign-in, 404, error). Keeps every other hero rule intact. ────────────── */

.hero-sm .hero__title { font-size: var(--text-4xl); }
.hero-sm .hero__lead { font-size: var(--text-base); }    /* statement, split, band                    */
/* =============================================================================
   32 · STATS
   Numbers as a band. The rule: the number is display-face and tabular, the
   label is slate, and nothing else competes. Hairlines separate, borders
   don't box — a stat grid is one surface, not four cards.
   ========================================================================== */

.stats {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-card);
	background: var(--bg-surface);
	overflow: hidden;
}

.stat {
	position: relative;
	padding: var(--space-6);
	display: flex;
	flex-direction: column;
	gap: var(--space-2);
	/* Hairline grid without doubled edges: every cell draws top+left, the
	   container's border owns the outside. */
	margin: calc(-1 * var(--border-hair)) 0 0 calc(-1 * var(--border-hair));
	border-top: var(--border-hair) solid var(--line-subtle);
	border-left: var(--border-hair) solid var(--line-subtle);
}

.stat__value {
	font-family: var(--font-display);
	font-size: var(--text-4xl);
	font-weight: var(--weight-bold);
	letter-spacing: var(--tracking-tighter);
	line-height: var(--leading-flat);
	font-variant-numeric: tabular-nums;
}

.stat__value small {
	font-size: 0.45em;
	font-weight: var(--weight-medium);
	color: var(--fg-faint);
	letter-spacing: var(--tracking-normal);
	margin-left: 2px;
}

.stat__label {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-subtle);
	order: -1;
}

/* Optional third line: a delta or a context note. */
.stat__note {
	font-size: var(--text-xs);
	color: var(--fg-faint);
	display: inline-flex;
	align-items: center;
	gap: var(--space-1);
}

.stat__note[data-trend='up'] { color: var(--fg-accent); }

/* A live stat gets the dot, nothing else. */
.stat-live .stat__label { color: var(--accent); }

/* ── Bare variant — no chrome, for use inside heroes and footers ─────────── */

.stats-bare { border: 0; border-radius: 0; background: none; overflow: visible; }
.stats-bare .stat { padding: var(--space-4) var(--space-5) var(--space-4) 0; }
.stats-bare .stat { border-top: 0; border-left: var(--border-hair) solid var(--line-default); padding-left: var(--space-5); }
.stats-bare .stat:first-child { border-left: 0; padding-left: 0; }

/* ── Inverse — on a dark band the hairlines lighten, values stay loud ────── */

.stats-inverse,
[data-surface='inverse'] .stats {
	background: transparent;
	border-color: var(--line-inverse);
}

.stats-inverse .stat,
[data-surface='inverse'] .stat {
	border-color: var(--line-inverse);
}

.stats-inverse .stat__label,
[data-surface='inverse'] .stat__label { color: var(--fg-on-inverse); opacity: 0.6; }

/* ── Counter roll — the value counts up on reveal (foundation's tx-num) ──── */

.stat__value[data-count] { animation: none; }   /* the stat grid + counters                  */
/* =============================================================================
   33 · CTA
   The ask. One per page, near the end, and it earns its contrast by being the
   only inverse band in view. Three shapes: the plain band, the newsletter
   (band + form), and the sponsor strip (quiet, bordered, not inverse — money
   asks politely).
   ========================================================================== */

.cta {
	position: relative;
	background: var(--bg-inverse);
	color: var(--fg-on-inverse);
	border-radius: var(--radius-sheet);
	padding: var(--space-12) var(--space-8);
	overflow: hidden;
	text-align: center;
}

.cta__kicker {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	opacity: 0.65;
}

.cta__title {
	font-family: var(--font-display);
	font-size: var(--text-3xl);
	font-weight: var(--weight-bold);
	letter-spacing: var(--tracking-tight);
	line-height: var(--leading-tight);
	text-wrap: balance;
	max-width: 22ch;
	margin: var(--space-4) auto 0;
}

.cta__title em { font-style: normal; color: var(--accent); }

.cta__body {
	margin: var(--space-4) auto 0;
	max-width: var(--measure-lead);
	line-height: var(--leading-relaxed);
	opacity: 0.75;
}

.cta__actions {
	display: flex;
	justify-content: center;
	flex-wrap: wrap;
	gap: var(--space-3);
	margin-top: var(--space-8);
}

/* Fine print under the actions — the honest line. */
.cta__fine {
	margin-top: var(--space-4);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: 0.05em;
	opacity: 0.5;
}

/* Left-aligned variant for column layouts. */
.cta-left { text-align: left; }
.cta-left .cta__title,
.cta-left .cta__body { margin-inline: 0; }
.cta-left .cta__actions { justify-content: flex-start; }

/* ── Newsletter — the band with a form in it ─────────────────────────────── */

.cta-newsletter__form {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: var(--space-2);
	margin-top: var(--space-8);
}

.cta-newsletter__form .input {
	flex: 1 1 14rem;
	max-width: 22rem;
	background: color-mix(in srgb, var(--fg-on-inverse) 8%, transparent);
	border-color: var(--line-inverse);
	color: var(--fg-on-inverse);
}

.cta-newsletter__form .input::placeholder { color: var(--fg-on-inverse); opacity: 0.45; }

.cta-left .cta-newsletter__form { justify-content: flex-start; }

/* ── Sponsor strip — quiet, bordered, on the paper ───────────────────────── */

.cta-sponsor {
	background: var(--bg-surface);
	color: var(--fg-default);
	border: var(--border-hair) solid var(--line-default);
	border-radius: var(--radius-card);
	padding: var(--space-6);
	display: flex;
	align-items: center;
	justify-content: space-between;
	flex-wrap: wrap;
	gap: var(--space-4);
	text-align: left;
}

.cta-sponsor .cta__title { font-size: var(--text-lg); margin: var(--space-1) 0 0; max-width: none; }
.cta-sponsor .cta__kicker { color: var(--fg-faint); opacity: 1; }
.cta-sponsor .cta__actions { margin-top: 0; }

@media (max-width: 40rem) {
	.cta { padding: var(--space-10) var(--space-5); }
}     /* CTA band, newsletter, sponsor             */
/* =============================================================================
   34 · FOOTER
   The end credits. Brand column + link columns over a hairline, then the
   sign-off row: legal in slate on the left, the signal dot signing off on the
   right. The footer is the one place the sitemap gets to be exhaustive.
   ========================================================================== */

.footer {
	border-top: var(--border-hair) solid var(--line-default);
	margin-top: var(--space-20);
	padding-block: var(--space-12) var(--space-8);
}

.footer__grid {
	display: grid;
	gap: var(--space-8);
	grid-template-columns: repeat(auto-fit, minmax(min(9rem, 100%), 1fr));
}

/* Brand column takes double width when there's room. */
@media (min-width: 48rem) {
	.footer__grid { grid-template-columns: 2fr repeat(auto-fit, minmax(8rem, 1fr)); }
}

.footer__brand { max-width: 24rem; }

.footer__tag {
	margin-top: var(--space-3);
	font-size: var(--text-sm);
	line-height: var(--leading-relaxed);
	color: var(--fg-subtle);
}

.footer__social {
	display: flex;
	gap: var(--space-2);
	margin-top: var(--space-5);
}

/* Column heading — same slate voice as everywhere. */
.footer__head {
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: var(--tracking-slate);
	text-transform: uppercase;
	color: var(--fg-faint);
	margin-bottom: var(--space-4);
}

.footer__links {
	display: flex;
	flex-direction: column;
	gap: var(--space-2);
}

.footer__links a {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	font-size: var(--text-sm);
	color: var(--fg-subtle);
	text-decoration: none;
	width: fit-content;
	transition: var(--t-colors);
}

.footer__links a:hover { color: var(--fg-default); }
.footer__links a .icon { width: 0.95rem; height: 0.95rem; opacity: 0.7; }

/* ── Sign-off row ────────────────────────────────────────────────────────── */

.footer__signoff {
	display: flex;
	align-items: center;
	justify-content: space-between;
	flex-wrap: wrap;
	gap: var(--space-3);
	margin-top: var(--space-12);
	padding-top: var(--space-6);
	border-top: var(--border-hair) solid var(--line-subtle);
	font-family: var(--font-slate);
	font-size: var(--text-2xs);
	letter-spacing: 0.05em;
	color: var(--fg-faint);
}

.footer__signoff a { color: inherit; }
.footer__signoff a:hover { color: var(--fg-default); }

/* "Still rolling" — the dot ends the page the way it opened it. */
.footer__rec {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	text-transform: uppercase;
	letter-spacing: var(--tracking-slate);
}

/* ── Inverse footer — for sites that end on ink ──────────────────────────── */

.footer-inverse {
	background: var(--bg-inverse);
	color: var(--fg-on-inverse);
	border-top: 0;
	padding-inline: var(--space-8);
	border-radius: var(--radius-sheet) var(--radius-sheet) 0 0;
}

.footer-inverse .footer__tag,
.footer-inverse .footer__links a { color: var(--fg-on-inverse); opacity: 0.7; }
.footer-inverse .footer__links a:hover { opacity: 1; color: var(--fg-on-inverse); }
.footer-inverse .footer__signoff { border-top-color: var(--line-inverse); color: var(--fg-on-inverse); }
.footer-inverse .footer__signoff > * { opacity: 0.6; }
.footer-inverse .footer__head { color: var(--fg-on-inverse); opacity: 0.5; }  /* the site footer + sign-off                */
/* =============================================================================
   SWARNIL / CREATOR DESIGN SYSTEM · 6 — UTILITIES
   Single-purpose classes, all `u-` prefixed so they can never collide with
   Tailwind while both live in the theme. Every value comes off a foundation
   ladder — a utility is a token with a classname, nothing more.
   Spacing scale here is the 4px ladder: 0 1 2 3 4 5 6 8 10 12 16.
   ========================================================================== */

/* ── Display ─────────────────────────────────────────────────────────────── */
.u-block { display: block; } .u-inline { display: inline; }
.u-inline-block { display: inline-block; }
.u-flex { display: flex; } .u-inline-flex { display: inline-flex; }
.u-grid { display: grid; } .u-none { display: none; }
.u-contents { display: contents; }

/* ── Visibility (kept in the a11y tree vs removed) ───────────────────────── */
.u-invisible { visibility: hidden; }
.u-visible { visibility: visible; }
.u-sr-only {
	position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
	overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ── Flex ────────────────────────────────────────────────────────────────── */
.u-row { flex-direction: row; } .u-col { flex-direction: column; }
.u-wrap { flex-wrap: wrap; } .u-nowrap-flex { flex-wrap: nowrap; }
.u-items-start { align-items: flex-start; } .u-items-center { align-items: center; }
.u-items-end { align-items: flex-end; } .u-items-baseline { align-items: baseline; }
.u-justify-start { justify-content: flex-start; } .u-justify-center { justify-content: center; }
.u-justify-end { justify-content: flex-end; } .u-justify-between { justify-content: space-between; }
.u-grow { flex-grow: 1; } .u-grow-0 { flex-grow: 0; }
.u-shrink-0 { flex-shrink: 0; } .u-flex-1 { flex: 1 1 0%; }
.u-self-start { align-self: flex-start; } .u-self-center { align-self: center; }
.u-self-end { align-self: flex-end; } .u-self-stretch { align-self: stretch; }
.u-order-first { order: -1; } .u-order-last { order: 999; }

/* ── Float ───────────────────────────────────────────────────────────────── */
.u-float-start { float: inline-start; } .u-float-end { float: inline-end; }
.u-float-none { float: none; }
.u-clearfix::after { content: ''; display: block; clear: both; }

/* ── Position ────────────────────────────────────────────────────────────── */
.u-static { position: static; } .u-relative { position: relative; }
.u-absolute { position: absolute; } .u-fixed { position: fixed; }
.u-sticky { position: sticky; top: var(--space-4); }
.u-inset-0 { inset: 0; }
.u-top-0 { top: 0; } .u-bottom-0 { bottom: 0; }
.u-start-0 { inset-inline-start: 0; } .u-end-0 { inset-inline-end: 0; }

/* ── Z-index (the foundation ladder only) ────────────────────────────────── */
.u-z-below { z-index: -1; } .u-z-0 { z-index: 0; }
.u-z-raised { z-index: var(--z-raised, 10); } .u-z-nav { z-index: var(--z-nav, 100); }
.u-z-overlay { z-index: var(--z-overlay, 200); } .u-z-top { z-index: var(--z-top, 999); }

/* ── Overflow ────────────────────────────────────────────────────────────── */
.u-overflow-auto { overflow: auto; } .u-overflow-hidden { overflow: hidden; }
.u-overflow-visible { overflow: visible; }
.u-overflow-x-auto { overflow-x: auto; } .u-overflow-y-auto { overflow-y: auto; }
.u-scroll-thin { scrollbar-width: thin; }

/* ── Sizing ──────────────────────────────────────────────────────────────── */
.u-w-full { width: 100%; } .u-w-fit { width: fit-content; }
.u-w-min { width: min-content; } .u-w-max { width: max-content; }
.u-h-full { height: 100%; } .u-h-dvh { height: 100dvh; }
.u-max-w-full { max-width: 100%; }
.u-max-w-prose { max-width: var(--measure-prose); }
.u-max-w-lead { max-width: var(--measure-lead); }
.u-max-w-ui { max-width: var(--measure-ui); }
.u-min-w-0 { min-width: 0; }

/* ── Spacing — margin ────────────────────────────────────────────────────── */
.u-m-0 { margin: 0; } .u-m-auto { margin: auto; }
.u-mx-auto { margin-inline: auto; }
.u-mt-0 { margin-top: 0; } .u-mt-1 { margin-top: var(--space-1); }
.u-mt-2 { margin-top: var(--space-2); } .u-mt-3 { margin-top: var(--space-3); }
.u-mt-4 { margin-top: var(--space-4); } .u-mt-5 { margin-top: var(--space-5); }
.u-mt-6 { margin-top: var(--space-6); } .u-mt-8 { margin-top: var(--space-8); }
.u-mt-10 { margin-top: var(--space-10); } .u-mt-12 { margin-top: var(--space-12); }
.u-mt-16 { margin-top: var(--space-16); }
.u-mb-0 { margin-bottom: 0; } .u-mb-1 { margin-bottom: var(--space-1); }
.u-mb-2 { margin-bottom: var(--space-2); } .u-mb-3 { margin-bottom: var(--space-3); }
.u-mb-4 { margin-bottom: var(--space-4); } .u-mb-5 { margin-bottom: var(--space-5); }
.u-mb-6 { margin-bottom: var(--space-6); } .u-mb-8 { margin-bottom: var(--space-8); }
.u-mb-10 { margin-bottom: var(--space-10); } .u-mb-12 { margin-bottom: var(--space-12); }
.u-ms-auto { margin-inline-start: auto; } .u-me-auto { margin-inline-end: auto; }

/* ── Spacing — padding ───────────────────────────────────────────────────── */
.u-p-0 { padding: 0; } .u-p-1 { padding: var(--space-1); }
.u-p-2 { padding: var(--space-2); } .u-p-3 { padding: var(--space-3); }
.u-p-4 { padding: var(--space-4); } .u-p-5 { padding: var(--space-5); }
.u-p-6 { padding: var(--space-6); } .u-p-8 { padding: var(--space-8); }
.u-px-2 { padding-inline: var(--space-2); } .u-px-3 { padding-inline: var(--space-3); }
.u-px-4 { padding-inline: var(--space-4); } .u-px-6 { padding-inline: var(--space-6); }
.u-py-2 { padding-block: var(--space-2); } .u-py-3 { padding-block: var(--space-3); }
.u-py-4 { padding-block: var(--space-4); } .u-py-6 { padding-block: var(--space-6); }

/* ── Gap ─────────────────────────────────────────────────────────────────── */
.u-gap-1 { gap: var(--space-1); } .u-gap-2 { gap: var(--space-2); }
.u-gap-3 { gap: var(--space-3); } .u-gap-4 { gap: var(--space-4); }
.u-gap-5 { gap: var(--space-5); } .u-gap-6 { gap: var(--space-6); }
.u-gap-8 { gap: var(--space-8); }

/* ── Text ────────────────────────────────────────────────────────────────── */
.u-text-start { text-align: start; } .u-text-center { text-align: center; }
.u-text-end { text-align: end; }
.u-uppercase { text-transform: uppercase; } .u-capitalize { text-transform: capitalize; }
.u-normal-case { text-transform: none; }
.u-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.u-nowrap { white-space: nowrap; } .u-break { overflow-wrap: anywhere; }
.u-balance { text-wrap: balance; } .u-pretty { text-wrap: pretty; }
.u-tabular { font-variant-numeric: tabular-nums; }
.u-weight-regular { font-weight: var(--weight-regular); }
.u-weight-medium { font-weight: var(--weight-medium); }
.u-weight-semibold { font-weight: var(--weight-semibold); }
.u-weight-bold { font-weight: var(--weight-bold); }
.u-italic { font-style: italic; } .u-underline { text-decoration: underline; }
.u-no-underline { text-decoration: none; }

/* ── Vertical align (inline / table cells) ───────────────────────────────── */
.u-align-baseline { vertical-align: baseline; } .u-align-top { vertical-align: top; }
.u-align-middle { vertical-align: middle; } .u-align-bottom { vertical-align: bottom; }
.u-align-text-top { vertical-align: text-top; } .u-align-text-bottom { vertical-align: text-bottom; }

/* ── Colors (semantic only — never raw ramp steps in markup) ─────────────── */
.u-fg-default { color: var(--fg-default); } .u-fg-subtle { color: var(--fg-subtle); }
.u-fg-muted { color: var(--fg-muted); } .u-fg-faint { color: var(--fg-faint); }
.u-fg-accent { color: var(--fg-accent); } .u-fg-on-inverse { color: var(--fg-on-inverse); }

/* ── Background ──────────────────────────────────────────────────────────── */
.u-bg-canvas { background: var(--bg-canvas); } .u-bg-surface { background: var(--bg-surface); }
.u-bg-sunken { background: var(--bg-sunken); } .u-bg-raised { background: var(--bg-raised); }
.u-bg-inverse { background: var(--bg-inverse); color: var(--fg-on-inverse); }
.u-bg-accent { background: var(--accent); color: var(--fg-on-accent); }
.u-bg-accent-soft { background: var(--accent-soft); color: var(--accent-soft-fg); }
.u-bg-transparent { background: transparent; }

/* ── Borders ─────────────────────────────────────────────────────────────── */
.u-border { border: var(--border-hair) solid var(--line-default); }
.u-border-0 { border: 0; }
.u-border-top { border-top: var(--border-hair) solid var(--line-default); }
.u-border-bottom { border-bottom: var(--border-hair) solid var(--line-default); }
.u-border-subtle { border-color: var(--line-subtle); }
.u-border-strong { border-color: var(--line-strong); }
.u-border-accent { border-color: var(--line-accent); }
.u-rounded-none { border-radius: var(--radius-none); }
.u-rounded-sm { border-radius: var(--radius-sm); } .u-rounded { border-radius: var(--radius-md); }
.u-rounded-lg { border-radius: var(--radius-lg); } .u-rounded-card { border-radius: var(--radius-card); }
.u-rounded-pill { border-radius: var(--radius-pill); } .u-rounded-full { border-radius: var(--radius-full); }

/* ── Shadows ─────────────────────────────────────────────────────────────── */
.u-shadow-0 { box-shadow: var(--shadow-0); } .u-shadow-1 { box-shadow: var(--shadow-1); }
.u-shadow-2 { box-shadow: var(--shadow-2); } .u-shadow-3 { box-shadow: var(--shadow-3); }
.u-shadow-4 { box-shadow: var(--shadow-4); } .u-shadow-5 { box-shadow: var(--shadow-5); }
.u-shadow-none { box-shadow: none; }

/* ── Interactions ────────────────────────────────────────────────────────── */
.u-pointer { cursor: pointer; } .u-cursor-default { cursor: default; }
.u-not-allowed { cursor: not-allowed; }
.u-pe-none { pointer-events: none; } .u-pe-auto { pointer-events: auto; }
.u-select-none { user-select: none; } .u-select-all { user-select: all; }
.u-select-text { user-select: text; }
.u-touch-pan { touch-action: pan-x pan-y; }

/* ── Object fit (images/video in frames) ─────────────────────────────────── */
.u-object-cover { object-fit: cover; } .u-object-contain { object-fit: contain; }

/* ── Responsive display — the one breakpoint pair that earns utilities ───── */
@media (max-width: 47.999rem) {
	.u-md-down-none { display: none; }
}

@media (min-width: 48rem) {
	.u-md-up-none { display: none; }
}

@media (max-width: 63.999rem) {
	.u-lg-down-none { display: none; }
}

@media (min-width: 64rem) {
	.u-lg-up-none { display: none; }
}

/* ── Aspect ratio — media boxes reserve their space before the image loads,
   so a slow network can never shift the layout. ──────────────────────────── */
.u-ratio-square { aspect-ratio: 1 / 1; }
.u-ratio-video { aspect-ratio: 16 / 9; }
.u-ratio-wide { aspect-ratio: 21 / 9; }
.u-ratio-portrait { aspect-ratio: 4 / 5; }
.u-ratio-poster { aspect-ratio: 2 / 3; }
.u-ratio-story { aspect-ratio: 9 / 16; }
