/* ============================================================
   DESIGN TOKENS — Samira Ray Portfolio (two-mode edition)
   One page, two identities, one toggle:

   PROFESSIONAL — the data-science/full-stack résumé identity.
   Eilaria Regular / Eilaria Script / Arches / SIREN display type,
   a moody denim-blue/wine/olive palette, torn paper, polaroid
   photo frames, washi tape.

   FUN — a denim-jacket scrapbook identity: dark denim backdrop,
   thick pink bubble-outline headlines, heart/circle photo frames
   with a lacy scalloped edge, charm stickers (gems, bows, pins)
   like patches pinned to a jacket, torn-paper note panels.

   Toggle switches --mode; each mode still quietly follows the
   visitor's system light/dark preference underneath.
   ============================================================ */

/* ---- Professional mode type (real files) ----
   Lives in ./assets/fonts/professional/ — swap files here without
   touching the fun-mode set below. */
@font-face {
  font-family: 'SIREN';
  src: url('./assets/fonts/professional/Siren.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Eilaria Regular';
  src: url('./assets/fonts/professional/Eilaria Regular.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Eilaria Script';
  src: url('./assets/fonts/professional/Eilaria Script.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Arches';
  src: url('./assets/fonts/professional/Arches.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* ---- Faces from the Y2K/specimen pack ----
   Quilla Sugar is the one in active use (display, subtitle and accent
   roles). Honey Mustard, Kingfoil and Craos were trialled and are kept
   registered so they can be swapped back in from a single token change.
   All verified to cover lowercase, digits and é. */
@font-face {
  font-family: 'Honey Mustard';
  src: url('./assets/fonts/professional-new/HoneyMustard.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Kingfoil';
  src: url('./assets/fonts/professional-new/Kingfoil.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Quilla Sugar';
  src: url('./assets/fonts/professional-new/QuillaSugar.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Craos';
  src: url('./assets/fonts/professional-new/Craos.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* ---- Elegant Serif Bundle — the chosen professional faces ----
   Bosca is declared as ONE family across four real weights so that
   <strong> in body copy resolves to a genuine bold cut rather than a
   browser-synthesised fake. Keep all four registered together. */
@font-face {
  font-family: 'Bosca';
  src: url('./assets/fonts/serif-bundle/bosca-light-webfont.woff2') format('woff2'),
       url('./assets/fonts/serif-bundle/bosca-light-webfont.woff') format('woff');
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Bosca';
  src: url('./assets/fonts/serif-bundle/bosca-regular-webfont.woff2') format('woff2'),
       url('./assets/fonts/serif-bundle/bosca-regular-webfont.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Bosca';
  src: url('./assets/fonts/serif-bundle/bosca-medium-webfont.woff2') format('woff2'),
       url('./assets/fonts/serif-bundle/bosca-medium-webfont.woff') format('woff');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Bosca';
  src: url('./assets/fonts/serif-bundle/bosca-bold-webfont.woff2') format('woff2'),
       url('./assets/fonts/serif-bundle/bosca-bold-webfont.woff') format('woff');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Operasignature Serif';
  src: url('./assets/fonts/serif-bundle/operasignature-serif-webfont.woff2') format('woff2'),
       url('./assets/fonts/serif-bundle/operasignature-serif-webfont.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Operasignature Scriptswash';
  src: url('./assets/fonts/serif-bundle/operasignature-scriptswash-webfont.woff2') format('woff2'),
       url('./assets/fonts/serif-bundle/operasignature-scriptswash-webfont.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* ---- Fun mode type ----
   From the purchased Y2K bundle, in ./assets/fonts/fun/. Only the
   two faces actually in use are declared here; the other 23 are on
   disk and can be auditioned via font-test.html without touching
   this file. Both verified to cover lowercase, digits, é and curly
   quotes, so no missing-glyph boxes in nav or fact-card copy. */
@font-face {
  font-family: 'Cyber Soulja';
  src:
    url('./assets/fonts/fun/cyber_soulja-webfont.woff2') format('woff2'),
    url('./assets/fonts/fun/cyber_soulja-webfont.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Cyber Angel';
  src:
    url('./assets/fonts/fun/cyber_angel-webfont.woff2') format('woff2'),
    url('./assets/fonts/fun/cyber_angel-webfont.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
/* Loading-screen-only callback to the original Y2K pack — the hollow
   outline cut reads well at the large single-word size the loading
   label needs, without competing with Cyber Soulja/Angel elsewhere. */
@font-face {
  font-family: 'Cyberwave2000';
  src:
    url('./assets/fonts/fun/cyberwave2000-regular-webfont.woff2') format('woff2'),
    url('./assets/fonts/fun/cyberwave2000-regular-webfont.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
/* Ticker-font candidates, registered so they're just a name to type
   into :root[data-mode='fun'] .ticker-track's font-family (style.css,
   search "ticker-track") — swap the value and hard-refresh to compare. */
@font-face {
  font-family: 'Snaxware';
  src:
    url('./assets/fonts/fun/snaxware-regular-webfont.woff2') format('woff2'),
    url('./assets/fonts/fun/snaxware-regular-webfont.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'BinaryFever';
  src:
    url('./assets/fonts/fun/binaryfever-webfont.woff2') format('woff2'),
    url('./assets/fonts/fun/binaryfever-webfont.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Snaxware Black';
  src:
    url('./assets/fonts/fun/snaxwareblack-black-webfont.woff2') format('woff2'),
    url('./assets/fonts/fun/snaxwareblack-black-webfont.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
/* Two new heading-font candidates — registered only, not applied
   anywhere yet. Swap either into a --font-zine-* token (or a specific
   rule) once you've decided which one and where. */
@font-face {
  font-family: 'Dreamer TM';
  src:
    url('./assets/fonts/y2k-extras/dreamer_tm-webfont.woff2') format('woff2'),
    url('./assets/fonts/y2k-extras/dreamer_tm-webfont.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Telefax TM';
  src: url('./assets/fonts/y2k-extras/telefax-tm-webfont.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
/* The loading-screen name font — this block going missing (twice now)
   is what "knocked out" the font on the loading screen: without an
   @font-face, 'KonamiKiss' is just a name nothing points at, so the
   browser silently falls through to the next font in the stack. If
   this keeps vanishing, check for an editor auto-revert/undo touching
   this file — the .loading-label rule further down was never itself
   edited, only this registration disappearing. */
@font-face {
  font-family: 'KonamiKiss';
  src:
    url('./assets/fonts/fun/konamikiss-webfont.woff2') format('woff2'),
    url('./assets/fonts/fun/konamikiss-webfont.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Type scale (fluid clamp) — shared by both modes */
  --text-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
  --text-sm: clamp(0.875rem, 0.8rem + 0.35vw, 1rem);
  --text-base: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
  --text-lg: clamp(1.125rem, 1rem + 0.75vw, 1.5rem);
  --text-xl: clamp(1.5rem, 1.2rem + 1.25vw, 2.25rem);
  --text-2xl: clamp(2rem, 1.2rem + 2.5vw, 3.5rem);
  --text-3xl: clamp(2.5rem, 1rem + 4vw, 4.5rem);
  --text-hero: clamp(2.5rem, 1.1rem + 4.2vw, 4.75rem);

  /* 4px spacing system */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-6: 1.5rem;
  --space-8: 2rem;
  --space-10: 2.5rem;
  --space-12: 3rem;
  --space-16: 4rem;
  --space-20: 5rem;
  --space-24: 6rem;
  --space-32: 8rem;

  --font-mono: ui-monospace, 'Consolas', 'SF Mono', monospace;

  /* Radius */
  --radius-sm: 0.5rem;
  --radius-md: 0.85rem;
  --radius-lg: 1.25rem;
  --radius-xl: 1.75rem;
  --radius-full: 9999px;

  /* Transitions */
  --transition-interactive: 180ms cubic-bezier(0.16, 1, 0.3, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in: cubic-bezier(0.4, 0, 1, 1);
  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);

  /* Content widths */
  --content-narrow: 640px;
  --content-default: 1020px;
  --content-wide: 1240px;
  --content-full: 100%;
}

/* ============================================================
   FONT TOKENS — per mode
   ============================================================ */
:root,
:root[data-mode='professional'] {
  --font-display: 'Quilla Sugar', Georgia, serif;
  --font-subtitle: 'Quilla Sugar', Georgia, serif;
  --font-hand: 'Quilla Sugar', Georgia, serif;
  /* Bosca is the one genuine text family in the serif bundle —
     Light/Regular/Medium/Bold registered as one family, so <strong>
     resolves to a real bold cut instead of a synthesised one. That is
     why body copy reads cleanly here where single-weight display faces
     did not. */
  --font-body: 'Bosca', 'Inter', -apple-system, sans-serif;
  --font-script: 'Operasignature Scriptswash', 'Brush Script MT', cursive;
  --font-nav: 'Operasignature Serif', Georgia, serif;
}


:root[data-mode='fun'] {
  --font-display: 'Display Bubble', ui-rounded, 'Segoe UI Rounded', 'Arial Rounded MT Bold', system-ui, sans-serif;
  --font-subtitle: 'Display Bubble', ui-rounded, 'Segoe UI Rounded', system-ui, sans-serif;
  --font-hand: 'Diary Hand', 'Segoe Script', 'Bradley Hand', cursive;
  --font-body: 'Segoe UI', system-ui, -apple-system, sans-serif;
}

/* ============================================================
   COLOR TOKENS — per mode, per light/dark
   (light/dark still follows the visitor's system preference;
   PROFESSIONAL / FUN is the only manual toggle)
   ============================================================ */

/* ---- PROFESSIONAL · light (default / bare :root fallback) ---- */
:root,
:root[data-mode='professional'][data-theme='light'] {
  --color-bg: #eef1f4;
  --color-surface: #fffcf9;
  --color-surface-2: #e4e9f0;
  --color-surface-offset: #f3e6e2;
  --color-border: #ccd2dc;
  --color-divider: oklch(from #1d2b45 l c h / 0.09);

  --color-text: #1d2b45;
  --color-text-muted: #48546e;
  --color-text-faint: #586480;
  --color-text-inverse: #eef1f4;

  --color-primary: #339cca;
  --color-primary-ink: #1c7098;
  --color-primary-hover: #2c86ac;
  --color-primary-highlight: #dcf0f7;
  --color-primary-text-on: #1d2b45;

  /* Secondary accent sampled from the watercolor florals' own foliage
     rather than the old wine/rose, which had become the only warm-pink
     element on the page and read as a leftover. Sampled straight from
     the border art's leaves, so it belongs to the florals. Contrast on the
     page background: ink 7.02:1, all AA-passing. */
  --color-secondary: #5a6b4a;
  --color-secondary-ink: #465633;
  --color-secondary-soft: #e8ece0;

  --color-olive: #3f5a63;
  --color-olive-text-on: #ffffff;

  --color-success: #3f5a63;
  --color-error: #a71e22;

  --shadow-sm: 0 1px 2px oklch(from #1d2b45 l c h / 0.12);
  --shadow-md: 0 8px 20px oklch(from #1d2b45 l c h / 0.16);
  --shadow-lg: 0 20px 45px oklch(from #1d2b45 l c h / 0.2);
}

/* ---- PROFESSIONAL · dark ---- */
:root[data-mode='professional'][data-theme='dark'] {
  --color-bg: #12151f;
  --color-surface: #1b2236;
  --color-surface-2: #232b3f;
  --color-surface-offset: #2a1c22;
  --color-border: oklch(from #eaecf4 l c h / 0.14);
  --color-divider: oklch(from #eaecf4 l c h / 0.08);

  --color-text: #eaecf4;
  --color-text-muted: #aab3c7;
  --color-text-faint: #7f8ba1;
  --color-text-inverse: #12151f;

  --color-primary: #5ec2e8;
  --color-primary-ink: #5ec2e8;
  --color-primary-hover: #85d4f2;
  --color-primary-highlight: #1c3a48;
  --color-primary-text-on: #0d1b22;

  --color-secondary: #dd93a8;
  --color-secondary-ink: #dd93a8;
  --color-secondary-soft: #35202a;

  --color-olive: #7fa8b3;
  --color-olive-text-on: #0d1b22;

  --color-success: #7fa8b3;
  --color-error: #e8747a;

  --shadow-sm: 0 1px 2px oklch(0 0 0 / 0.35);
  --shadow-md: 0 8px 20px oklch(0 0 0 / 0.4);
  --shadow-lg: 0 20px 45px oklch(0 0 0 / 0.5);
}

/* ---- FUN · light ("denim by day") ---- */
:root[data-mode='fun'][data-theme='light'] {
  /* Was #7f97c2 (denim blue) — swapped to match the zine cream now
     that the header/ticker/About page have all moved to that system,
     so the page background doesn't clash with them everywhere. */
  --color-bg: #fbf5ea;
  --color-surface: #fffaf7;
  --color-surface-2: #f7ecf3;
  --color-surface-offset: #6a80ab;
  --color-border: #e6c9dc;
  --color-divider: oklch(from #2b0f28 l c h / 0.12);

  --color-text: #2b0f28;
  --color-text-muted: #6b4066;
  --color-text-faint: #8a5e85;
  --color-text-inverse: #fff5fa;

  --color-primary: #ff2e88;
  --color-primary-ink: #c2166a;
  --color-primary-hover: #ff4d9b;
  --color-primary-highlight: #ffd6ea;
  --color-primary-text-on: #2b0f28;

  --color-secondary: #7b2ff7;
  --color-secondary-ink: #5c1ecf;
  --color-secondary-soft: #ece0ff;

  --color-gold: #e0a930;
  --color-success: #1f9d6c;
  --color-error: #d63a2b;

  --color-denim-weave: #4c6098;
  --color-denim-wash: #c3d2ec;

  --shadow-sm: 0 1px 2px oklch(0 0 0 / 0.25);
  --shadow-md: 0 10px 24px oklch(0 0 0 / 0.3);
  --shadow-lg: 0 22px 48px oklch(0 0 0 / 0.38);
}

/* ---- FUN · dark ("denim after hours") ---- */
:root[data-mode='fun'][data-theme='dark'] {
  --color-bg: #1c2740;
  --color-surface: #241a2e;
  --color-surface-2: #2e2038;
  --color-surface-offset: #141c30;
  --color-border: oklch(from #f5eaf9 l c h / 0.18);
  --color-divider: oklch(from #f5eaf9 l c h / 0.1);

  --color-text: #f5eaf9;
  --color-text-muted: #cbb3d6;
  --color-text-faint: #a98bb8;
  --color-text-inverse: #140a1f;

  --color-primary: #ff5fa8;
  --color-primary-ink: #ff5fa8;
  --color-primary-hover: #ff85bd;
  --color-primary-highlight: #3a1530;
  --color-primary-text-on: #140a1f;

  --color-secondary: #b98bff;
  --color-secondary-ink: #cdaaff;
  --color-secondary-soft: #2a1a45;

  --color-gold: #e8c25a;
  --color-success: #4ad999;
  --color-error: #ff6b5e;

  --color-denim-weave: #141c30;
  --color-denim-wash: #384c78;

  --shadow-sm: 0 1px 2px oklch(0 0 0 / 0.4);
  --shadow-md: 0 10px 24px oklch(0 0 0 / 0.45);
  --shadow-lg: 0 22px 48px oklch(0 0 0 / 0.55);
}

/* ---- No-JS fallback (system dark, mode not yet resolved) ---- */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --color-bg: #12151f;
    --color-surface: #1b2236;
    --color-surface-2: #232b3f;
    --color-surface-offset: #2a1c22;
    --color-border: oklch(from #eaecf4 l c h / 0.14);
    --color-divider: oklch(from #eaecf4 l c h / 0.08);
    --color-text: #eaecf4;
    --color-text-muted: #aab3c7;
    --color-text-faint: #7f8ba1;
    --color-text-inverse: #12151f;
    --color-primary: #5ec2e8;
    --color-primary-ink: #5ec2e8;
    --color-primary-hover: #85d4f2;
    --color-primary-highlight: #1c3a48;
    --color-primary-text-on: #0d1b22;
    --color-secondary: #dd93a8;
    --color-secondary-ink: #dd93a8;
    --color-secondary-soft: #35202a;
    --color-olive: #7fa8b3;
    --color-olive-text-on: #0d1b22;
    --color-success: #7fa8b3;
    --color-error: #e8747a;
    --shadow-sm: 0 1px 2px oklch(0 0 0 / 0.35);
    --shadow-md: 0 8px 20px oklch(0 0 0 / 0.4);
    --shadow-lg: 0 20px 45px oklch(0 0 0 / 0.5);
  }
}

/* ============================================================
   PAGE BACKGROUND — subtle grain in professional mode, a denim
   twill approximation in fun mode (swap in a real denim photo
   texture on body whenever one's ready — this is a CSS stand-in)
   ============================================================ */
body {
  background-color: var(--color-bg);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.03 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-repeat: repeat;
}
:root[data-mode='fun'] body {
  /* A soft fabric grain (not a graphic crosshatch — that read as a
     bold plaid) plus a scattered glitter layer of small bright
     flecks, like the rose-gold/icy glitter tape in the reference. */
  background-image:
    radial-gradient(circle at 8% 15%, oklch(1 0 0 / 0.85) 0 1.4px, transparent 2px),
    radial-gradient(circle at 24% 70%, oklch(from var(--color-gold) l c h / 0.85) 0 1.4px, transparent 2px),
    radial-gradient(circle at 41% 32%, oklch(1 0 0 / 0.7) 0 1px, transparent 1.5px),
    radial-gradient(circle at 58% 82%, oklch(from var(--color-primary) l c h / 0.8) 0 1.4px, transparent 2px),
    radial-gradient(circle at 71% 14%, oklch(1 0 0 / 0.75) 0 1px, transparent 1.5px),
    radial-gradient(circle at 88% 58%, oklch(from var(--color-gold) l c h / 0.8) 0 1.4px, transparent 2px),
    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.7' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.09 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size:
    620px 620px,
    620px 620px,
    620px 620px,
    620px 620px,
    620px 620px,
    620px 620px,
    auto;
  background-repeat: repeat;
  background-attachment: fixed;
}

/* ============================================================
   MODE VISIBILITY — show only the active mode's content
   ============================================================ */
.mode-fun,
.mode-professional {
  display: none;
}
:root[data-mode='fun'] .mode-fun {
  display: revert;
}
:root[data-mode='professional'] .mode-professional {
  display: revert;
}
:root[data-mode='fun'] .mode-fun.flex {
  display: flex;
}
:root[data-mode='professional'] .mode-professional.flex {
  display: flex;
}
:root[data-mode='fun'] .mode-fun.grid {
  display: grid;
}
:root[data-mode='professional'] .mode-professional.grid {
  display: grid;
}
:root[data-mode='fun'] .mode-fun.inline {
  display: inline;
}
:root[data-mode='professional'] .mode-professional.inline {
  display: inline;
}
:root[data-mode='fun'] .mode-fun.inline-flex {
  display: inline-flex;
}
:root[data-mode='professional'] .mode-professional.inline-flex {
  display: inline-flex;
}
/* ============================================================
   LAYOUT PRIMITIVES
   ============================================================ */
.container {
  max-width: var(--content-default);
  margin-inline: auto;
  padding-inline: var(--space-6);
}
.container--narrow {
  max-width: var(--content-narrow);
}

section {
  /* Tightened from clamp(space-16, 9vw, space-32), which produced
     ~108px top / 85px bottom and left the professional pages feeling
     stretched next to fun mode's 40/48 zine rhythm. */
  padding-block: clamp(var(--space-10), 5vw, var(--space-16));
  position: relative;
}

.eyebrow {
  font-family: var(--font-hand);
  font-size: clamp(1.15rem, 1rem + 0.6vw, 1.4rem);
  font-weight: 400;
  color: var(--color-secondary-ink);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
  transform: rotate(-2deg);
}
:root[data-mode='professional'] .eyebrow::before {
  content: '✦';
  color: var(--color-primary-ink);
  font-size: 0.85em;
}
:root[data-mode='fun'] .eyebrow::before {
  content: '★';
  color: var(--color-primary-ink);
  font-size: 0.8em;
}

h1,
.h1 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--text-hero);
  line-height: 1.08;
}
:root[data-mode='fun'] h1,
:root[data-mode='fun'] .h1 {
  font-weight: 700;
  line-height: 1.05;
}
h2,
.h2 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--text-xl);
  line-height: 1.18;
}
:root[data-mode='fun'] h2,
:root[data-mode='fun'] .h2 {
  font-weight: 700;
}
h3,
.h3 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--text-lg);
}
:root[data-mode='fun'] h3,
:root[data-mode='fun'] .h3 {
  font-weight: 700;
}
p {
  color: var(--color-text-muted);
}
.text-body {
  color: var(--color-text);
}
.accent {
  color: var(--color-primary-ink);
}

/* ============================================================
   DECORATIVE PRIMITIVES — shared mechanics
   ============================================================ */
.washi {
  position: absolute;
  width: 100px;
  height: 34px;
  opacity: 0.95;
  z-index: 3;
  pointer-events: none;
  box-shadow: var(--shadow-md);
  background: repeating-linear-gradient(45deg, var(--color-primary) 0 10px, var(--color-secondary) 10px 20px);
  /* A rectangle with roughly torn short ends (left/right only) —
     like tape ripped off a roll, not a hand-torn all-over edge. */
  clip-path: polygon(
    2% 0%, 0% 25%, 3% 45%, 0% 62%, 2% 100%,
    98% 100%, 100% 75%, 97% 55%, 100% 38%, 98% 0%
  );
}

/* Glitter-ribbon washi variant — fun mode accent, a sparkly torn
   ribbon trim echoing the rose-gold/icy glitter tape corners */
.washi-glitter {
  position: absolute;
  width: 118px;
  height: 36px;
  opacity: 0.97;
  z-index: 3;
  pointer-events: none;
  box-shadow: var(--shadow-md);
  background: linear-gradient(100deg, var(--color-primary) 0%, #ffd6ea 25%, var(--color-primary) 50%, #ffd6ea 75%, var(--color-primary) 100%);
  background-size: 220% 100%;
  animation: glitter-shimmer 5s linear infinite;
  clip-path: polygon(
    2% 0%, 0% 22%, 3% 40%, 0% 58%, 2% 78%, 0% 100%,
    98% 100%, 100% 80%, 97% 60%, 100% 42%, 97% 22%, 98% 0%
  );
}
@media (prefers-reduced-motion: reduce) {
  .washi-glitter {
    animation: none;
  }
}
@keyframes glitter-shimmer {
  to {
    background-position: 220% 0;
  }
}

.sticker {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-hand);
  font-weight: 400;
  font-size: var(--text-base);
  padding: var(--space-1) var(--space-5);
  border-radius: var(--radius-full);
  border: 2px solid currentColor;
  transform: rotate(-1.5deg);
}

.doodle {
  position: absolute;
  pointer-events: none;
  z-index: 1;
  opacity: 0.75;
}

/* Raster line art is opaque black — invert it to read on the dark bg */
:root[data-mode='professional'][data-theme='dark'] .doodle {
  filter: invert(1);
  opacity: 0.55;
}
/* Watercolor art is already soft and full-color, so it needs neither
   the opacity knock-down (that muddies the washes) nor the dark-mode
   invert (which would turn the blues orange). */
.doodle--watercolor {
  opacity: 1;
}
:root[data-mode='professional'][data-theme='dark'] .doodle--watercolor {
  filter: none;
  opacity: 0.9;
}

/* ---- Professional interior pages: seamless hydrangea patterns ----
   One pattern per page so each carries the hydrangea theme with its
   own character, mirroring how fun mode gives each page its own
   paper texture. Painted on a ::before at low opacity rather than
   directly on the section, so the art never sits between the reader
   and the text — body copy keeps its full contrast against the page
   background. */
:root[data-mode='professional'] #top,
:root[data-mode='professional'] #about,
:root[data-mode='professional'] #work,
:root[data-mode='professional'] #resume {
  position: relative;
  isolation: isolate;
}
:root[data-mode='professional'] #top::before,
:root[data-mode='professional'] #about::before,
:root[data-mode='professional'] #work::before,
:root[data-mode='professional'] #resume::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-repeat: repeat;
  background-size: 460px auto;
  opacity: 0.16;
}
/* One pattern across the whole professional side — the hydrangea-and-
   rose print (pattern 2.png in the source pack — note its .png and
   .jpg files are different artworks, not two formats of one image),
   which is the boldest of the set and matches the nav mark. Deliberately NOT varied per page: professional mode's identity
   is consistency, which is what makes it read as the opposite of fun
   mode's page-by-page paper textures. */
:root[data-mode='professional'] #top::before,
:root[data-mode='professional'] #about::before,
:root[data-mode='professional'] #work::before,
:root[data-mode='professional'] #resume::before {
  background-image: url('./assets/florals/pattern-hydrangea.webp');
}

/* ---- Professional portrait: clean photo card ----
   Same "framed photo" idea as fun mode's taped polaroid, but the
   opposite execution — square corners, a thin rule, and a soft
   diffuse shadow instead of tape and a hard offset. */
.photo-card {
  /* Containing block for .photo-sticker, which overlays the card. */
  position: relative;
  width: min(100%, 340px);
  margin-inline: auto;
  /* The hero grid bottom-aligns its columns, which pushed the photo
     past the fold. Aligning to the top of the row brings it up beside
     the headline so it's visible without scrolling. */
  align-self: start;
  background: var(--color-surface);
  padding: 14px 14px 52px;
  border: 1px solid oklch(from var(--color-text) l c h / 0.14);
  box-shadow: 0 14px 38px oklch(from var(--color-text) l c h / 0.13);
}
.photo-card img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  object-position: center 20%;
}
@media (min-width: 900px) {
  .photo-card {
    margin-inline: 0 auto;
  }
}

/* Sprig standing in for the removed About portrait — sits above the
   stat cards as a light accent, not a focal point. */
.about-sprig {
  display: block;
  width: clamp(140px, 22vw, 210px);
  height: auto;
  margin: 0 auto var(--space-6);
}
@media (min-width: 860px) {
  .about-sprig {
    margin-inline: 0 auto;
  }
}

.handwritten {
  font-family: var(--font-hand);
  font-weight: 400;
}

/* Photo frame — polaroid in professional mode, taped scrapbook
   photo in fun mode; same markup, different skin */
.photo-frame {
  position: relative;
  aspect-ratio: 3/4;
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  padding: 14px 14px 40px;
  outline: 2px dashed var(--color-border);
  outline-offset: 8px;
  box-shadow: var(--shadow-lg);
  transform: rotate(-2deg);
}
.photo-frame img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 25%;
  border-radius: var(--radius-sm);
  filter: saturate(1.05);
}
.photo-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 10px;
  text-align: center;
  font-family: var(--font-hand);
  font-size: var(--text-base);
  color: var(--color-text-muted);
}

/* ============================================================
   HEADER / NAV
   ============================================================ */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: oklch(from var(--color-bg) l c h / 0.9);
  backdrop-filter: blur(12px);
  border-bottom: 2px dashed var(--color-primary);
  transition:
    transform 0.3s var(--ease-out),
    box-shadow 0.3s var(--ease-out);
}
.site-header.is-scrolled {
  box-shadow: var(--shadow-sm);
}
.site-header.is-hidden {
  transform: translateY(-100%);
}
.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block: var(--space-4);
}
.logo-link {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-text);
  text-decoration: none;
}
/* LOGO MARK — real hydrangea-with-stem line art (the same asset used
   as the professional hero doodle), shown directly instead of
   flattened through a CSS mask, so the actual linework stays
   readable at small size instead of collapsing into a blob. Dark
   line art reads fine on the light professional header as-is; fun
   mode inverts it to a light tint so it holds up on the dark wine
   header (see the fun-mode override further down). */
.logo-mark {
  display: inline-block;
  width: 34px;
  height: 34px;
  object-fit: contain;
}
/* The generic .mode-* utilities tie on specificity with .logo-mark and
   lose on source order, which showed BOTH marks in BOTH modes. These
   two-class selectors win outright. Professional gets the watercolor
   bloom; fun keeps the line art (which its filter inverts to cream). */
.logo-mark.mode-professional,
.logo-mark.mode-fun {
  display: none;
}
:root[data-mode='professional'] .logo-mark.mode-professional,
:root[data-mode='fun'] .logo-mark.mode-fun {
  display: inline-block;
}
.logo-word {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 400;
}
:root[data-mode='fun'] .logo-word {
  font-weight: 700;
}
:root[data-mode='professional'] .logo-word {
  font-family: var(--font-script);
  font-size: var(--text-xl);
}

.nav-desktop {
  display: none;
  align-items: center;
  gap: var(--space-8);
}
/* Fun mode drops the Work/About/Résumé header links outright — the
   zine filmstrip (in the page content below) already does that job,
   colorfully, on every viewport size. Beats the @media(min-width:860px)
   flex rule below on specificity regardless of source order, so this
   works at desktop widths too, not just mobile. The status ticker is
   a separate element right under the header and is untouched by this. */
:root[data-mode='fun'] .nav-desktop {
  display: none;
}
.nav-desktop a {
  font-family: var(--font-display);
  font-size: var(--text-base);
  font-weight: 400;
  color: var(--color-text-muted);
  text-decoration: none;
  position: relative;
  padding-block: var(--space-1);
}
:root[data-mode='fun'] .nav-desktop a {
  font-weight: 700;
}
:root[data-mode='professional'] .nav-desktop a {
  font-family: var(--font-nav);
}
.nav-desktop a:hover {
  color: var(--color-secondary-ink);
}
.nav-desktop a::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: -2px;
  width: 0;
  height: 2px;
  background: var(--color-secondary);
  transition: width var(--transition-interactive);
  border-radius: var(--radius-full);
}
.nav-desktop a:hover::after {
  width: 100%;
}

.header-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.icon-btn {
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full);
  color: var(--color-text-muted);
  transition:
    color var(--transition-interactive),
    background var(--transition-interactive);
}
.icon-btn:hover {
  color: var(--color-text);
  background: var(--color-surface-2);
}
.icon-btn svg {
  width: 20px;
  height: 20px;
}

/* Fun mode drops .site-header entirely — the .zine-navbar filmstrip
   (logo-less, but with home/page links and the pro-mode switch tile)
   replaces it as the actual nav bar, right above the ticker. The old
   wine-header styling that used to live here is no longer needed
   since the element never renders in this mode. */
:root[data-mode='fun'] .site-header {
  display: none;
}
:root[data-mode='fun'] .logo-word {
  font-family: var(--font-zine-nav);
  color: var(--zine-cream);
  font-size: var(--text-base);
  letter-spacing: 0.02em;
}
/* No filter needed here — the fun-mode mark is the same white hydrangea
   sprig used on the About page, already pale enough to read on the
   dark wine header as-is. */
:root[data-mode='fun'] .nav-desktop a {
  font-family: var(--font-zine-nav);
  font-size: 0.8rem;
  color: color-mix(in oklab, var(--zine-cream) 85%, transparent);
}
:root[data-mode='fun'] .nav-desktop a:hover {
  color: var(--zine-cream);
}
:root[data-mode='fun'] .nav-desktop a::after {
  background: var(--zine-rose);
}
:root[data-mode='fun'] .icon-btn {
  color: var(--zine-cream);
}
:root[data-mode='fun'] .icon-btn:hover {
  color: var(--zine-cream);
  background: color-mix(in oklab, var(--zine-cream) 18%, transparent);
}
:root[data-mode='fun'] .ticker {
  background: var(--zine-cream);
  border-bottom: 3px solid var(--zine-ink);
  padding-block: var(--space-1);
}
:root[data-mode='fun'] .ticker-track {
  /* To try another candidate, replace ONLY the font name in quotes
     below — leave everything else on the line alone, including the
     comma right after it. Registered options: 'Telefax TM' (the
     pick), 'Snaxware Black', 'Snaxware', 'BinaryFever', 'Caveat'. */
  font-family: 'Telefax TM', cursive;
  font-size: 0.85rem;
  color: var(--zine-ink);
  letter-spacing: 0.04em;
  align-items: center;
}
:root[data-mode='fun'] .ticker-star {
  /* The stars were sized with the surrounding text (~14px) and got
     lost in a bar with this much vertical room — real accent size
     now, not just punctuation between phrases. */
  font-size: clamp(24px, 4vw, 40px);
  line-height: 1;
  color: var(--zine-rose-deep);
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-6);
  border-radius: var(--radius-full);
  font-family: var(--font-subtitle);
  font-size: var(--text-base);
  font-weight: 400;
  text-decoration: none;
  white-space: nowrap;
  transition:
    background var(--transition-interactive),
    color var(--transition-interactive),
    box-shadow var(--transition-interactive),
    transform var(--transition-interactive);
}
:root[data-mode='fun'] .btn {
  font-weight: 700;
  position: relative;
  overflow: hidden;
}
/* Glossy Web-2.0 button sheen — the era's defining button treatment. */
:root[data-mode='fun'] .btn::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, oklch(1 0 0 / 0.5) 0%, oklch(1 0 0 / 0.15) 45%, oklch(1 0 0 / 0) 55%);
  pointer-events: none;
}
.btn:active {
  transform: translateY(1px) rotate(0deg);
}
.btn-primary {
  background: var(--color-primary);
  color: var(--color-primary-text-on);
  box-shadow: var(--shadow-sm);
}
.btn-primary:hover {
  background: var(--color-primary-hover);
  box-shadow: var(--shadow-md);
  transform: translateY(-2px) rotate(-1deg);
}
.btn-ghost {
  background: var(--color-surface);
  color: var(--color-text);
  border: 2px solid var(--color-border);
}
.btn-ghost:hover {
  background: var(--color-secondary-soft);
  border-color: var(--color-secondary);
  transform: translateY(-2px) rotate(1deg);
}

/* Buttons inside the zine card — same hard-edge, ink-bordered
   language as everything else in there, not the glossy Web-2.0
   pill treatment the rest of fun mode still uses. */
:root[data-mode='fun'] .about-zine .btn {
  font-family: var(--font-zine-display);
  font-size: 0.85rem;
}
:root[data-mode='fun'] .about-zine .btn::before {
  display: none;
}
:root[data-mode='fun'] .about-zine .btn-primary {
  background: var(--zine-rose);
  color: var(--zine-ink);
  border: 3px solid var(--zine-ink);
  box-shadow: var(--zine-shadow-sm);
}
:root[data-mode='fun'] .about-zine .btn-primary:hover {
  background: var(--zine-rose);
  transform: rotate(0deg) translate(-3px, -5px) scale(1.02);
  box-shadow: 10px 12px 0 var(--zine-ink);
}
:root[data-mode='fun'] .about-zine .btn-ghost {
  background: var(--zine-cream);
  color: var(--zine-ink);
  border: 3px solid var(--zine-ink);
  box-shadow: var(--zine-shadow-sm);
}
:root[data-mode='fun'] .about-zine .btn-ghost:hover {
  background: var(--zine-cream);
  border-color: var(--zine-ink);
  transform: rotate(0deg) translate(-3px, -5px) scale(1.02);
  box-shadow: 10px 12px 0 var(--zine-ink);
}
.btn svg {
  width: 16px;
  height: 16px;
}

.nav-toggle {
  display: inline-flex;
}

.mobile-menu {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: var(--color-bg);
  display: flex;
  flex-direction: column;
  padding: var(--space-6);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-8px);
  transition:
    opacity 0.25s var(--ease-out),
    transform 0.25s var(--ease-out),
    visibility 0.25s;
}
.mobile-menu.is-open {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
.mobile-menu-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--space-10);
}
.mobile-menu nav {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.mobile-menu nav a {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  color: var(--color-text);
  text-decoration: none;
  padding-block: var(--space-3);
  border-bottom: 2px dashed var(--color-divider);
}
:root[data-mode='professional'] .mobile-menu nav a {
  font-family: var(--font-nav);
}
.mobile-menu-footer {
  margin-top: auto;
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
}

/* Fun mode never got its own mobile-menu treatment — it was falling
   back to --font-display/--font-hand's old, never-loaded "Display
   Bubble"/"Diary Hand" placeholders (pre-dating the zine font system),
   which is why it read as a plain, generic menu instead of matching
   the rest of fun mode. Wired to the real zine tokens/palette here. */
:root[data-mode='fun'] .mobile-menu {
  background: var(--zine-wine);
  background-image: url('./assets/textures/paper-bg-glued-026.jpg');
  background-size: 900px auto;
  background-repeat: repeat;
  background-blend-mode: multiply;
}
:root[data-mode='fun'] .mobile-menu nav a {
  font-family: var(--font-zine-display);
  color: var(--zine-cream);
  border-bottom-color: color-mix(in oklab, var(--zine-cream) 30%, transparent);
}
:root[data-mode='fun'] .mobile-menu-footer .handwritten {
  font-family: var(--font-zine-hand);
  color: var(--zine-cream);
}

@media (min-width: 860px) {
  .nav-desktop {
    display: flex;
  }
  .nav-toggle {
    display: none;
  }
}

/* ============================================================
   HERO
   ============================================================ */
.hero {
  padding-block: clamp(var(--space-16), 12vh, var(--space-24)) 0;
  position: relative;
  overflow: hidden;
}
.hero-grid {
  display: grid;
  gap: var(--space-10);
  align-items: end;
  position: relative;
}
:root[data-mode='professional'] .hero-grid {
  padding-bottom: clamp(var(--space-16), 10vh, var(--space-24));
}
:root[data-mode='fun'] .hero-grid {
  padding-bottom: clamp(var(--space-16), 10vh, var(--space-24));
}
.hero-date {
  font-family: var(--font-hand);
  font-size: var(--text-lg);
  color: var(--color-secondary-ink);
  display: inline-block;
  transform: rotate(-1.5deg);
  margin-bottom: var(--space-4);
}
.hero h1 {
  max-width: 17ch;
  margin-bottom: var(--space-6);
}
:root[data-mode='fun'] .hero h1 {
  max-width: 15ch;
}
:root[data-mode='professional'] .hero h1 {
  font-family: var(--font-subtitle);
}
/* ---- Hero headline: one full-width line above the columns ----
   The global h1 rule caps width at ~414px and uses text-wrap: balance,
   which suits stacked headings but forced this one into a narrow
   column. Both are cleared so the line can run the full container.
   Pulled up toward the nav so it sits closer to the header. */
:root[data-mode='professional'] .hero-headline {
  max-width: none;
  text-wrap: nowrap;
  white-space: nowrap;
  margin: 0 0 var(--space-6);
  /* Scales with the viewport so the line keeps fitting as the
     container narrows, instead of being clipped. */
  font-size: clamp(1.35rem, 0.6rem + 2.6vw, 2.6rem);
  line-height: 1.15;
}
/* Below the width where one line still fits, allow a natural wrap
   rather than letting it overflow the page. */
@media (max-width: 780px) {
  :root[data-mode='professional'] .hero-headline {
    white-space: normal;
    text-wrap: balance;
  }
}
/* Tighten the gap between the header and the headline. */
:root[data-mode='professional'] .hero {
  padding-top: var(--space-6);
}
:root[data-mode='professional'] .hero-grid {
  padding-top: 0;
}

/* Wordmark set in Arches using its swash capitals (U+F00E = alternate
   S, U+F00D = alternate R). The regular caps paired badly in "Sa" and
   "Ra"; the swash forms are drawn for exactly that position, so the
   spacing resolves. Used consistently everywhere the wordmark appears
   (desktop header, mobile menu), not just one instance. */
:root[data-mode='professional'] .logo-word--pro {
  font-family: 'Arches', Georgia, serif;
  font-size: var(--text-xl);
  line-height: 1;
  letter-spacing: 0.01em;
}
/* The .mode-* utilities tie with .logo-word on specificity and lose on
   source order — the same trap the logo mark hit — so the two-class
   selectors are spelled out here. */
.logo-word.mode-professional,
.logo-word.mode-fun {
  display: none;
}
:root[data-mode='professional'] .logo-word.mode-professional,
:root[data-mode='fun'] .logo-word.mode-fun {
  display: inline-block;
}

/* The eyebrow moves off the text column and onto the photo, which
   rebalances a hero that was left-heavy and keeps a bit of the
   sticker playfulness on the professional side. */
.photo-sticker {
  position: absolute;
  left: 50%;
  bottom: -16px;
  transform: translateX(-50%) rotate(-1.6deg);
  z-index: 3;
  /* The label is longer than the card is wide, so it wraps rather than
     running off the card edge — nowrap overflowed by ~19px. */
  white-space: normal;
  width: max-content;
  max-width: min(94%, 300px);
  text-align: center;
  line-height: 1.3;
  font-size: var(--text-sm);
  box-shadow: 0 6px 18px oklch(from var(--color-text) l c h / 0.16);
}
.hero-lede {
  font-size: var(--text-lg);
  color: var(--color-text-muted);
  max-width: 46ch;
  margin-bottom: var(--space-8);
}
:root[data-mode='professional'] .hero-lede {
  /* Body font, NOT the display serif. Set in --font-subtitle it read as
     a second heading rather than a paragraph, which flattened the
     hierarchy — eyebrow, headline and lede all looked like headings.
     Matches how the About section already handles body copy. */
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: 1.65;
  max-width: 52ch;
}
:root[data-mode='fun'] .hero-lede {
  font-family: var(--font-hand);
  font-size: var(--text-xl);
  max-width: 30ch;
  transform: rotate(-0.5deg);
}
.hero-actions {
  display: flex;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-bottom: var(--space-10);
}
.hero-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  border-top: 2px dashed var(--color-border);
  padding-top: var(--space-5);
  margin-bottom: var(--space-10);
}
.hero-meta span {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
.hero-meta svg {
  width: 14px;
  height: 14px;
  color: var(--color-secondary-ink);
}

/* Cross-mode callout — points at the toggle from the other side.
   Currently used on the professional hero to pitch the fun side
   as a portfolio piece in its own right. */
.mode-cta {
  margin-top: var(--space-6);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.mode-cta strong {
  color: var(--color-primary-ink);
  font-weight: 700;
}
/* With several display faces in play the mode-switch prompt stopped
   standing out from the surrounding copy. Italic plus a dotted
   underline marks it as the actionable phrase without adding a fourth
   typeface to the page. */
:root[data-mode='professional'] .mode-cta strong {
  font-style: italic;
  font-weight: 500;
  text-decoration: underline;
  text-decoration-style: dotted;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}

/* Resume callout — a hand-drawn arrow doing the work a parenthetical
   used to do quietly. Meant to actually get noticed. */
.resume-note {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-base);
}
.resume-note .resume-arrow {
  flex-shrink: 0;
  color: var(--color-secondary-ink);
  transform: rotate(-4deg);
}
.resume-note a {
  color: var(--color-secondary-ink);
  text-decoration: underline;
  text-decoration-style: wavy;
  text-decoration-thickness: 1.5px;
  text-underline-offset: 4px;
}

@media (min-width: 900px) {
  .hero-grid {
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  }
  /* NOTE: the 1.15/0.85 split above is load-bearing. An even 1fr/1fr
     starves the text column (466px inside the 1020px container) and
     wraps the 76px headline onto five cramped lines at ~1440px. */
}

/* ============================================================
   SECTION HEADERS
   ============================================================ */
.section-head {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-12);
  max-width: 60ch;
}

/* ============================================================
   WORK / PROJECTS
   ============================================================ */
.work-grid {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: 1fr;
}
@media (min-width: 620px) {
  .work-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}
/* A third column once there's room, so a growing project list adds
   rows less often instead of just getting taller. */
@media (min-width: 1040px) {
  .work-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* project-card is professional-mode-only — fun mode uses its own
   .zine-project-card system. Deliberately no rotation/tilt here: that
   playfulness is fun mode's signature, professional stays a clean,
   uniform grid so it scales as more projects get added instead of
   growing more crowded-looking card by card. */
.project-card {
  position: relative;
  background: var(--color-surface);
  border: 2px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  transition:
    box-shadow var(--transition-interactive),
    transform var(--transition-interactive),
    border-color var(--transition-interactive);
}
.project-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-md);
  border-color: var(--color-secondary);
}

.project-status {
  font-family: var(--font-hand);
  font-size: var(--text-base);
  font-weight: 400;
  color: var(--color-olive);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
.project-status::before {
  content: '●';
  font-size: 0.6em;
}
.project-status.building {
  color: var(--color-secondary-ink);
}

.project-rating {
  color: #2b1a08;
  background: var(--color-gold, var(--color-primary));
  display: inline-flex;
  align-self: flex-start;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-full);
  font-size: var(--text-xs);
  font-weight: 800;
  letter-spacing: 0.03em;
}

.project-card h3 {
  font-size: var(--text-lg);
  margin-top: var(--space-1);
}
.project-card p {
  font-size: var(--text-sm);
}
.project-card.featured p {
  font-size: var(--text-base);
}

.tag-row {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-top: auto;
}
.tag {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--text-xs);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-full);
  background: var(--color-secondary-soft);
  color: var(--color-secondary-ink);
  border: 1.5px solid var(--color-secondary);
}
.tag:nth-child(3n + 2) {
  background: var(--color-primary-highlight);
  color: var(--color-primary-ink);
  border-color: var(--color-primary);
}
.tag:nth-child(3n) {
  background: oklch(from var(--color-olive, var(--color-gold)) l c h / 0.15);
  color: var(--color-olive, #8a6510);
  border-color: var(--color-olive, var(--color-gold));
}

.project-links {
  display: flex;
  gap: var(--space-4);
  margin-top: auto;
}
.project-links a {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-subtitle);
  font-size: var(--text-base);
  font-weight: 400;
  color: var(--color-text);
  text-decoration: none;
}
.project-links a svg {
  width: 15px;
  height: 15px;
  color: var(--color-secondary-ink);
  transition: transform var(--transition-interactive);
}
.project-links a:hover svg {
  transform: translate(2px, -2px);
}

/* ============================================================
   ABOUT
   ============================================================ */
.about-grid {
  display: grid;
  gap: var(--space-12);
  align-items: start;
}
.about-photo {
  position: relative;
  max-width: 210px;
  margin: 0 auto var(--space-8) var(--space-4);
  background: var(--color-surface);
  padding: 10px 10px 34px;
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  border: 1px solid var(--color-border);
  transform: rotate(2.5deg);
}
.about-photo .photo-caption {
  font-size: var(--text-sm);
  bottom: 8px;
  white-space: nowrap;
}
.about-photo img {
  display: block;
  width: 100%;
  aspect-ratio: 1/1;
  object-fit: cover;
  object-position: center 25%;
  border-radius: var(--radius-sm);
  filter: saturate(1.05);
}
@media (min-width: 540px) {
  .about-photo {
    margin: 0 0 var(--space-8) 0;
  }
}
@media (min-width: 860px) {
  .about-grid {
    grid-template-columns: 1fr 1fr;
  }
}
.about-copy p {
  font-size: var(--text-base);
  margin-bottom: var(--space-5);
}
.about-copy p:last-child {
  margin-bottom: 0;
}
.about-copy strong {
  color: var(--color-text);
  /* Bosca's heaviest real cut is 700; asking for 800 made the browser
     synthesise extra weight on top of an already-bold face, which
     smeared the letterforms in body copy. */
  font-weight: 700;
}

.stat-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-4);
}
.stat-card {
  background: var(--color-surface);
  border: 2px solid var(--color-border);
  border-radius: var(--radius-md);
  /* Matches the fun-side card padding (16px) rather than 20px, so both
     modes share one spacing rhythm. */
  padding: var(--space-4);
}
.stat-card:nth-child(1) {
  transform: rotate(-1deg);
}
.stat-card:nth-child(2) {
  transform: rotate(1deg);
}
.stat-card:nth-child(3) {
  transform: rotate(0.5deg);
}
.stat-card:nth-child(4) {
  transform: rotate(-0.5deg);
}
.stat-value {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 400;
  color: var(--color-primary-ink);
  font-variant-numeric: tabular-nums;
}
:root[data-mode='fun'] .stat-value {
  font-weight: 700;
}
.stat-label {
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--color-text-muted);
  margin-top: var(--space-1);
}

/* ============================================================
   FUN MODE "ZINE" TREATMENT
   Site-wide fun-mode tokens (header, ticker, About content, and
   whatever else adopts this look next). Independent of the
   site-wide --color and --font-hand tokens on purpose, since the
   rest of fun mode's palette and fonts are a separate pass still
   to come — this is its own self-contained system for now.
   ============================================================ */
:root[data-mode='fun'] {
  --zine-cream: #fbf5ea;
  --zine-wine: #6f2340;
  --zine-rose: #d5658a;
  --zine-rose-deep: #99396a;
  --zine-blush: #f3d9d6;
  --zine-hydrangea: #9d97c9;
  --zine-bronze: #a5824a;
  --zine-ink: #1c1015;
  --zine-shadow: 8px 8px 0 var(--zine-ink);
  --zine-shadow-sm: 4px 4px 0 var(--zine-ink);
  --zine-radius: 10px;
  /* Three display roles rather than one, so the Y2K faces stay a
     deliberate accent and don't swamp the handwritten voice:
       --font-zine-nav     navigation only (header links, filmstrip)
       --font-zine-header   the one true big headline (.zine-hero) —
                            plain ink text, not a colored chip
       --font-zine-display  small/plain text everywhere else: tabs,
                            facts titles, panel labels, project
                            titles, buttons, captions. Back to Titan
                            One — Dreamer TM's fine detail/glitter
                            mushes out at these sizes. Dreamer is used
                            explicitly on .zine-badge only (see there),
                            the one place it's big enough to read.
     Audition other faces from the bundle in font-test.html. */
  --font-zine-nav: 'Cyber Soulja', 'Titan One', var(--font-display), sans-serif;
  --font-zine-header: 'Cyber Angel', 'Titan One', var(--font-display), sans-serif;
  --font-zine-display: 'Titan One', 'Baloo 2', 'Bowlby One SC', var(--font-display), sans-serif;
  --font-zine-hand: 'Caveat', cursive;
  --font-zine-body: 'Work Sans', var(--font-body), sans-serif;
}

/* Motion, pulled from the actual Lovable source (circa-2000-profile
   repo) rather than reconstructed from a screenshot — sway/bob are
   idle wobble on accent elements, pop-in is a staggered entrance for
   card grids, lift is the hover pickup on bordered panels. */
@keyframes zine-sway {
  0%, 100% { transform: rotate(var(--zine-sway-from, -1.5deg)) translateY(0); }
  50% { transform: rotate(var(--zine-sway-to, 1.5deg)) translateY(-6px); }
}
@keyframes zine-bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-8px); }
}
@keyframes zine-pop-in {
  0% { opacity: 0; transform: translateY(14px) rotate(-3deg) scale(0.96); }
  100% { opacity: 1; transform: translateY(0) rotate(0) scale(1); }
}
.zine-sway {
  animation: zine-sway 6s ease-in-out infinite;
}
.zine-bob {
  animation: zine-bob 5s ease-in-out infinite;
}
.zine-lift {
  transition: transform 0.25s cubic-bezier(0.2, 0.9, 0.3, 1.4), box-shadow 0.25s ease;
}
.zine-lift:hover {
  transform: rotate(0deg) translate(-3px, -5px) scale(1.02);
  box-shadow: 10px 12px 0 var(--zine-ink);
}
@media (prefers-reduced-motion: reduce) {
  .zine-sway,
  .zine-bob,
  .zine-fact {
    animation: none !important;
  }
}

.about-zine {
  background: var(--zine-cream);
  color: var(--zine-ink);
  font-family: var(--font-zine-body);
  border: 3px solid var(--zine-ink);
  border-radius: var(--zine-radius);
  overflow: hidden;
  box-shadow: var(--zine-shadow);
}
/* The real contrast bug: a site-wide `p { color: var(--color-text-muted) }`
   rule (line ~463) targets the <p> tag directly, which beats inherited
   color from a parent panel no matter how specific the parent rule is.
   Every zine paragraph was silently landing on the muted plum/mauve
   text color instead of its panel's actual cream or ink. */
.about-zine p {
  color: inherit;
}

.zine-masthead {
  background: var(--zine-wine);
  border-bottom: 3px solid var(--zine-ink);
  padding: var(--space-3) var(--space-5);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.zine-issue {
  flex: none;
  border: 2px solid var(--zine-ink);
  background: var(--zine-rose);
  color: var(--zine-ink);
  font-family: var(--font-zine-display);
  font-size: var(--text-xs);
  padding: 2px var(--space-2);
}
.zine-wordmark {
  font-family: var(--font-zine-display);
  color: var(--zine-cream);
  font-size: var(--text-lg);
  letter-spacing: 0.02em;
}
.zine-tagline {
  font-family: var(--font-zine-hand);
  color: color-mix(in oklab, var(--zine-cream) 90%, transparent);
  font-size: var(--text-lg);
  margin-left: auto;
}

/* Same halftone extended to the page around the card, not just
   inside it — reads as one continuous paper surface, matching how
   Lovable ran it full-bleed behind the whole page rather than boxed
   into one panel. */
:root[data-mode='fun'] #about,
:root[data-mode='fun'] #top,
:root[data-mode='fun'] #work,
:root[data-mode='fun'] #resume,
:root[data-mode='fun'] .site-footer {
  /* Glued/creased paper for the background layer — visibly different
     texture from the card surface below, so the cards read as a
     separate material sitting on top, not the same sheet continuing.
     Applied to the footer too so the paper runs the full page, not
     just the main content section.

     This value is the About-page texture and the fallback for any
     new page; each other page overrides it below. See the per-page
     block for why they intentionally differ. */
  background-image: url('./assets/textures/paper-bg-glued-029.jpg');
  /* Real 300dpi source (resized to 1800px wide for web weight), so
     this tiles with actual detail instead of stretched thumbnail
     mush — 270deg-rotated per the earlier request. */
  background-size: 900px auto;
  background-repeat: repeat;
  background-color: var(--zine-cream, #fbf5ea);
}
:root[data-mode='fun'] .site-footer {
  /* Dotted seam marking where the footer "sheet" meets the section
     above — reads as a perforated tear-line between two pieces of
     paper, not just a divider rule. */
  border-top: 3px dotted var(--zine-ink, #1c1015);
}
/* Extra breathing room before that seam. The shared `section` rule's
   padding-block reads fine for professional mode's shorter sections,
   but fun mode's zine content runs long (project grid, fact grid,
   etc.) and butted straight up against the dotted line with barely
   any gap. Bottom-only, so top spacing is untouched. */
:root[data-mode='fun'] #top,
:root[data-mode='fun'] #about,
:root[data-mode='fun'] #work,
:root[data-mode='fun'] #resume {
  padding-bottom: clamp(var(--space-16), 8vw, var(--space-24));
}

/* Footer text printed INTO the paper rather than floating on it.
   multiply lets the sheet's creases and shadows darken the glyphs,
   so the ink follows the surface instead of sitting flat on top.
   The ink color is also a real contrast win: the old mauve sat at
   4.50:1, a rounding error above the 4.5:1 AA minimum. */
:root[data-mode='fun'] .site-footer .footer-inner p,
:root[data-mode='fun'] .site-footer .footer-links a {
  color: var(--zine-ink, #1c1015);
  mix-blend-mode: multiply;
  opacity: 0.82;
}
:root[data-mode='fun'] .site-footer .footer-links a:hover {
  opacity: 1;
}
/* Blending needs the paper as its backdrop, so the footer must not
   create its own isolated stacking context. */
:root[data-mode='fun'] .site-footer .footer-inner {
  isolation: auto;
}
@media (prefers-reduced-transparency: reduce) {
  :root[data-mode='fun'] .site-footer .footer-inner p,
  :root[data-mode='fun'] .site-footer .footer-links a {
    mix-blend-mode: normal;
    opacity: 1;
  }
}

/* Per-page background textures — deliberate, not leftover test code.
   Each page gets its own glued-paper sheet so it has a distinct feel,
   while the kraft card surface (.zine-body) stays identical across
   every page and carries the visual consistency. Keyed off each
   page's own section id, so a page's footer always matches its
   section. Adding a page? Either give it a variant here or let it
   fall through to the 029 default above.
     Home -> 026 (sweeping diagonal crease)
     About -> 029 (medium crease depth, the default)
     Work -> 028 (finer scattered wrinkles)
     Résumé -> 034 (softest, most subtle) */
:root[data-mode='fun'] body:has(#top) #top,
:root[data-mode='fun'] body:has(#top) .site-footer {
  background-image: url('./assets/textures/paper-bg-glued-026.jpg');
}
:root[data-mode='fun'] body:has(#work) #work,
:root[data-mode='fun'] body:has(#work) .site-footer {
  background-image: url('./assets/textures/paper-bg-glued-028.jpg');
}
:root[data-mode='fun'] body:has(#resume) #resume,
:root[data-mode='fun'] body:has(#resume) .site-footer {
  background-image: url('./assets/textures/paper-bg-glued-034.jpg');
}
.zine-body {
  position: relative;
  padding: var(--space-8) var(--space-5) var(--space-10);
  /* Drawing paper — deliberately a different, warmer/flatter texture
     than the glued background behind it (see the #about/#top/etc.
     rule above), so the card reads as its own separate sheet of
     paper sitting on top, not a continuation of the same surface. */
  /* Kraft paper now, not drawing paper — real 300dpi source (resized
     to 1600px wide for web weight), tan/organic tone as the card's
     own material, deliberately different from the glued background.
     A flat cream wash sits on top of the tile (first layer = topmost)
     to lighten it — the raw photo read as too dark once badges/text
     had to sit on it, and a flat opacity/filter on .zine-body itself
     would've washed out the child content too, not just the paper. */
  background-image: linear-gradient(oklch(from var(--zine-cream) l c h / 0.45), oklch(from var(--zine-cream) l c h / 0.45)), url('./assets/textures/paper-bg.jpg');
  background-size:
    800px auto,
    800px auto;
  background-repeat: repeat;
  background-color: var(--zine-cream);
}
.zine-body > * {
  position: relative;
}
@media (min-width: 540px) {
  .zine-body {
    padding: var(--space-10) var(--space-8) var(--space-12);
  }
}
.zine-kicker {
  font-family: var(--font-zine-hand);
  color: var(--zine-rose-deep);
  font-size: var(--text-xl);
  margin: 0;
}
/* The live-clock kicker only (Home page) — Telefax TM as the
   complementary accent to the Dreamer TM badge below it. Scoped to
   [data-live-datetime] specifically so the other pages' plain
   handwritten taglines ("so, who is she?" etc.) are untouched. */
.zine-kicker[data-live-datetime] {
  font-family: 'Telefax TM', var(--font-zine-hand);
}
.zine-hero {
  font-family: var(--font-zine-header);
  font-size: clamp(1.9rem, 5vw, 3.2rem);
  line-height: 0.98;
  margin: var(--space-1) 0 0;
  color: var(--zine-ink);
}
/* Her actual name, singled out of the plain headline as its own
   accent word — Dreamer TM, same treatment idea as the badge below
   it but inline rather than a boxed chip. */
.zine-name-accent {
  font-family: 'Dreamer TM', var(--font-zine-header);
  color: var(--zine-rose-deep);
}
.zine-badge {
  /* Block instead of inline-block: forces it onto its own line under
     the headline at every viewport width (not just narrow ones where
     it happened to wrap there). A slight overlap was tried here and
     covered the headline above it once the badge got this big — clear
     positive gap instead. */
  display: block;
  width: fit-content;
  margin-top: var(--space-3);
  transform: rotate(-1.5deg);
  border: 3px solid var(--zine-ink);
  background: var(--zine-hydrangea);
  color: var(--zine-ink);
  /* Explicit rather than inherited from the parent .zine-hero — the
     badge is a colored chip sitting on top of the big headline, not
     part of it, so it gets its own display font (Dreamer TM). This is
     the one spot Dreamer is used at — big enough here to actually read
     its detail instead of it turning to mush. */
  font-family: 'Dreamer TM', var(--font-zine-header);
  /* Was clamp(2.1rem, 5.5vw, 3.6rem) — computed to ~52px but the box
     itself was ~75px tall, all unused headroom. Sized up to actually
     fill that space instead of floating in the middle of it. */
  font-size: clamp(2.6rem, 7vw, 4.6rem);
  line-height: 1;
  padding: var(--space-2) var(--space-3);
  box-shadow: var(--zine-shadow);
}
/* Per-page accent on the badge/quote — same palette, so each page
   still feels part of one system, just not identical to the others. */
.zine-badge--rose,
.zine-quote-box--rose {
  background: var(--zine-rose);
  color: var(--zine-ink);
}
.zine-badge--wine,
.zine-quote-box--wine {
  background: var(--zine-wine);
  color: var(--zine-cream);
}
.zine-badge--bronze,
.zine-quote-box--bronze {
  background: var(--zine-bronze);
  color: var(--zine-ink);
}
.zine-badge--cream,
.zine-quote-box--cream {
  background: var(--zine-cream);
  color: var(--zine-ink);
}

.zine-columns {
  display: grid;
  gap: var(--space-8);
  margin-top: var(--space-10);
}
@media (min-width: 900px) {
  .zine-columns {
    grid-template-columns: 5fr 7fr;
    gap: var(--space-8);
  }
}
.zine-col {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

.zine-polaroid {
  position: relative;
  max-width: 300px;
  margin: 0 auto;
  background: var(--zine-cream);
  border: 3px solid var(--zine-ink);
  padding: var(--space-3) var(--space-3) var(--space-5);
  transform: rotate(-2deg);
  box-shadow: var(--zine-shadow-sm);
}
.zine-polaroid::before {
  content: '';
  position: absolute;
  top: -0.85rem;
  left: 50%;
  transform: translateX(-50%) rotate(-3deg);
  width: 5.5rem;
  height: 1.6rem;
  /* Solid cartoon tape, not see-through cellophane. */
  background: #fdfaf3;
  border: 1.5px solid var(--zine-ink);
  box-shadow: 1px 1px 0 rgba(28, 16, 21, 0.15);
}
.zine-polaroid img {
  display: block;
  width: 100%;
  aspect-ratio: 4/5;
  object-fit: cover;
  object-position: center 20%;
  border: 3px solid var(--zine-ink);
}
.zine-polaroid figcaption {
  margin-top: var(--space-3);
  text-align: center;
  font-family: var(--font-zine-hand);
  color: var(--zine-rose-deep);
  font-size: var(--text-xl);
}

.zine-panel {
  border: 3px solid var(--zine-ink);
  box-shadow: var(--zine-shadow-sm);
  padding: var(--space-4);
}
.zine-panel--wine {
  background: var(--zine-wine);
  color: var(--zine-cream);
  transform: rotate(1deg);
}
.zine-panel--dashed {
  background: var(--zine-cream);
  border-style: dashed;
  transform: rotate(-1deg);
}
.zine-panel--player {
  background: var(--zine-cream);
  transform: rotate(-1.5deg);
}
.zine-panel--player iframe {
  display: block;
  width: 100%;
  margin-top: var(--space-2);
  border-radius: var(--radius-sm);
}
.zine-role {
  font-family: var(--font-zine-display);
  font-size: var(--text-lg);
  margin: 0;
}
.zine-role-sub {
  margin: var(--space-1) 0 0;
  font-size: var(--text-sm);
  opacity: 0.85;
}
.zine-label {
  font-family: var(--font-zine-display);
  /* Was --text-xs (~12-14px) — fine for Titan One's chunky strokes,
     but Dreamer TM's finer detail goes illegible at that size. Fixed
     px instead of the fluid token so it doesn't shrink back down on
     narrow viewports either. */
  font-size: 0.9rem;
  color: var(--zine-rose-deep);
  margin: 0;
}
.zine-panel--wine .zine-label {
  color: var(--zine-cream);
}
.zine-panel p:not(.zine-label) {
  margin: var(--space-2) 0 0;
  line-height: 1.5;
}

.zine-tabpanel {
  position: relative;
  border: 3px solid var(--zine-ink);
  box-shadow: var(--zine-shadow);
  background: var(--zine-cream);
  padding: var(--space-6) var(--space-5) var(--space-5);
}
.zine-tab {
  position: absolute;
  top: -1rem;
  left: var(--space-5);
  border: 3px solid var(--zine-ink);
  background: var(--zine-rose);
  color: var(--zine-ink);
  font-family: var(--font-zine-display);
  /* Was --text-xs — same Dreamer-legibility fix as .zine-label. */
  font-size: 0.9rem;
  padding: 2px var(--space-3);
}
.zine-tabpanel p {
  margin: var(--space-2) 0 0;
  line-height: 1.6;
}
.zine-tab--hydrangea {
  background: var(--zine-hydrangea);
}

/* Filmstrip — Monster High-style thumbnail rail, used on Home as a
   second, playful way to jump to the other real pages (not decorative
   facets — each tile is a genuine link). */
/* The filmstrip's wrapper when it's promoted to be the actual fun-mode
   nav bar (see .site-header being hidden in fun mode, below) — full-
   bleed and sticky, sitting where the old wine header used to, with
   the ticker directly under it. Old-website toolbar-plus-marquee. */
.zine-navbar {
  display: none;
}
:root[data-mode='fun'] .zine-navbar {
  display: block;
  position: sticky;
  top: 0;
  z-index: 90;
}
.zine-filmstrip {
  background: var(--zine-ink);
  border-bottom: 3px solid var(--zine-ink);
  padding: var(--space-3) var(--space-4);
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.zine-filmstrip .arrow {
  flex: none;
  color: var(--zine-hydrangea);
  font-size: 1.3rem;
  font-weight: 800;
  line-height: 1;
  background: none;
  border: none;
  padding: var(--space-1);
  cursor: pointer;
  transition: opacity 0.2s ease;
}
.zine-filmstrip .arrow:hover {
  color: var(--zine-cream);
}
.zine-filmstrip .arrow:disabled {
  opacity: 0.35;
  cursor: default;
}
.zine-filmstrip-track {
  display: flex;
  gap: var(--space-3);
  overflow-x: auto;
  flex: 1;
  /* Now that the filmstrip is a full-bleed nav bar instead of sitting
     in a narrow card, the tiles need to center in the extra room
     rather than default flex-start, which stranded them at the left
     edge with empty dark space on the right.
     `safe center`, not plain `center`: on narrow screens the tiles
     overflow and the track scrolls — plain `center` computes a
     negative start offset in that case, which made "home base" (the
     first tile) permanently unreachable since scrollLeft can't go
     negative. `safe` falls back to start-alignment once content
     overflows, so scrolling to the very first tile always works. */
  justify-content: safe center;
}
.zine-thumb {
  flex: none;
  width: 104px;
  height: 96px;
  border-radius: 10px;
  border: 2px solid var(--zine-ink);
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--space-2);
  font-family: var(--font-zine-nav);
  /* Cyber Soulja's angular forms go mushy below ~13px, so the tile
     is sized to fit the label at a readable size rather than the
     label being shrunk to fit the tile. */
  font-size: 0.95rem;
  line-height: 1.15;
  text-decoration: none;
  color: var(--zine-ink);
  transition: transform 0.25s cubic-bezier(0.2, 0.9, 0.3, 1.4), box-shadow 0.25s ease;
  box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.4);
}
.zine-thumb:hover {
  transform: translate(-2px, -3px) scale(1.04);
  box-shadow: 6px 8px 0 rgba(0, 0, 0, 0.4);
}
.zine-thumb--rose { background: var(--zine-rose); }
.zine-thumb--hydrangea { background: var(--zine-hydrangea); }
.zine-thumb--cream { background: var(--zine-cream); }
.zine-thumb--bronze { background: var(--zine-bronze); }
/* The mode-switch tile living in the filmstrip alongside the page
   links — dark/inverted on purpose so it reads as a different KIND of
   action (a utility, not another page) rather than just a 5th color
   in the same rotation. */
.zine-thumb--toggle {
  background: var(--zine-ink);
  color: var(--zine-cream);
  flex-direction: column;
  gap: 4px;
  cursor: pointer;
}
.zine-thumb--toggle svg {
  width: 20px;
  height: 20px;
}
.zine-skill-grid {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.zine-skill-chip {
  display: inline-block;
  border: 2px solid var(--zine-ink);
  background: var(--zine-cream);
  color: var(--zine-ink);
  font-family: var(--font-zine-body);
  font-weight: 600;
  font-size: 0.78rem;
  padding: 5px 10px;
  border-radius: 999px;
  box-shadow: 2px 2px 0 var(--zine-ink);
  transition: transform 0.25s cubic-bezier(0.2, 0.9, 0.3, 1.4), box-shadow 0.25s ease;
}
.zine-skill-chip:hover {
  transform: translate(-2px, -3px) scale(1.03);
  box-shadow: 5px 6px 0 var(--zine-ink);
}
.zine-quote-panel {
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 3px dashed var(--zine-ink);
}
.zine-quote-box {
  background: var(--zine-bronze);
  border: 3px solid var(--zine-ink);
  box-shadow: var(--zine-shadow-sm);
  padding: var(--space-4);
  transform: rotate(1deg);
}
.zine-quote {
  font-family: var(--font-zine-hand);
  font-size: var(--text-xl);
  margin: var(--space-1) 0 0;
}
.zine-quote-box span {
  display: block;
  margin-top: var(--space-2);
  font-family: var(--font-zine-display);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  opacity: 0.85;
}
.zine-quote-def {
  margin: var(--space-1) 0 0;
  font-family: var(--font-zine-body);
  font-size: 0.8rem;
  font-style: italic;
  opacity: 0.85;
}

.zine-facts-title {
  display: inline-block;
  transform: rotate(-1deg);
  border: 3px solid var(--zine-ink);
  background: var(--zine-rose-deep);
  color: var(--zine-cream);
  font-family: var(--font-zine-display);
  font-size: var(--text-lg);
  padding: var(--space-1) var(--space-3);
  box-shadow: var(--zine-shadow-sm);
  margin: var(--space-8) 0 0;
}
.zine-fact-grid {
  list-style: none;
  margin: var(--space-6) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-5);
}
@media (min-width: 620px) {
  .zine-fact-grid {
    grid-template-columns: 1fr 1fr;
  }
}
.zine-fact {
  position: relative;
  border: 3px solid var(--zine-ink);
  box-shadow: var(--zine-shadow-sm);
  padding: var(--space-4) var(--space-4) var(--space-3);
  background: var(--zine-cream);
  transition: transform 0.25s cubic-bezier(0.2, 0.9, 0.3, 1.4), box-shadow 0.25s ease;
  animation: zine-pop-in 0.6s cubic-bezier(0.2, 0.9, 0.3, 1.4) both;
}
.zine-fact:nth-child(1) { animation-delay: 0ms; }
.zine-fact:nth-child(2) { animation-delay: 80ms; }
.zine-fact:nth-child(3) { animation-delay: 160ms; }
.zine-fact:nth-child(4) { animation-delay: 240ms; }
.zine-fact:nth-child(5) { animation-delay: 320ms; }
.zine-fact:nth-child(6) { animation-delay: 400ms; }
.zine-fact::before {
  content: '';
  position: absolute;
  top: -0.7rem;
  left: var(--space-4);
  width: 3.5rem;
  height: 1.2rem;
  transform: rotate(-6deg);
  /* Solid cartoon tape, not see-through cellophane. */
  background: #fdfaf3;
  border: 1.5px solid var(--zine-ink);
  box-shadow: 1px 1px 0 rgba(28, 16, 21, 0.15);
}
.zine-fact:hover {
  transform: rotate(0deg) translate(-3px, -5px) scale(1.02);
  box-shadow: 10px 12px 0 var(--zine-ink);
}
.zine-fact:nth-child(odd) {
  transform: rotate(-2deg);
}
.zine-fact:nth-child(even) {
  transform: rotate(2deg);
}
.zine-fact p {
  font-family: var(--font-zine-hand);
  font-size: var(--text-xl);
  line-height: 1.25;
  margin: 0;
}
.zine-fact span {
  display: block;
  margin-top: var(--space-2);
  font-family: var(--font-zine-display);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  opacity: 0.85;
}
.zine-fact--rose {
  background: var(--zine-rose);
  color: var(--zine-ink);
}
.zine-fact--wine {
  background: var(--zine-wine);
  color: var(--zine-cream);
}
.zine-fact--hydrangea {
  background: var(--zine-hydrangea);
  color: var(--zine-ink);
}

/* Project cards for the Work page — bigger sibling of .zine-fact,
   same tape/shadow/rotation/pop-in language, plus room for a rating,
   tags, and an optional live link. */
.zine-project-grid {
  list-style: none;
  margin: var(--space-6) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-6);
}
@media (min-width: 680px) {
  .zine-project-grid {
    grid-template-columns: 1fr 1fr;
  }
}
.zine-project-card {
  position: relative;
  border: 3px solid var(--zine-ink);
  box-shadow: var(--zine-shadow-sm);
  padding: var(--space-5) var(--space-5) var(--space-4);
  background: var(--zine-cream);
  color: var(--zine-ink);
  transition: transform 0.25s cubic-bezier(0.2, 0.9, 0.3, 1.4), box-shadow 0.25s ease;
  animation: zine-pop-in 0.6s cubic-bezier(0.2, 0.9, 0.3, 1.4) both;
}
.zine-project-card:nth-child(1) { animation-delay: 0ms; transform: rotate(-1.5deg); }
.zine-project-card:nth-child(2) { animation-delay: 80ms; transform: rotate(1.5deg); }
.zine-project-card:nth-child(3) { animation-delay: 160ms; transform: rotate(-1deg); }
.zine-project-card:nth-child(4) { animation-delay: 240ms; transform: rotate(1deg); }
.zine-project-card:nth-child(5) { animation-delay: 320ms; transform: rotate(-1.5deg); }
.zine-project-card:hover {
  transform: rotate(0deg) translate(-3px, -5px) scale(1.015);
  box-shadow: 10px 12px 0 var(--zine-ink);
}
.zine-project-card::before {
  content: '';
  position: absolute;
  top: -0.7rem;
  left: var(--space-5);
  width: 3.5rem;
  height: 1.2rem;
  transform: rotate(-6deg);
  background: #fdfaf3;
  border: 1.5px solid var(--zine-ink);
  box-shadow: 1px 1px 0 rgba(28, 16, 21, 0.15);
}
.zine-project-card .rating {
  font-size: 0.9rem;
  /* Was a fixed bronze — too low-contrast on the lighter card
     backgrounds. currentColor always matches whatever text color
     that card variant already uses (already contrast-checked). */
  color: currentColor;
  opacity: 0.75;
  letter-spacing: 0.1em;
}
.zine-project-card h3 {
  /* Deliberately NOT --font-zine-header. Cyber Angel has very tight
     counters, so mixed-case words ("PageKeeper", "Dataset Explorer")
     fill in and read as solid blocks at this size. It's kept for the
     all-caps headers, where that never happens. */
  font-family: var(--font-zine-display);
  font-size: var(--text-lg);
  margin: var(--space-2) 0 0;
}
.zine-project-card p {
  margin: var(--space-2) 0 0;
  font-size: 0.92rem;
  line-height: 1.55;
}
.zine-project-card .tag-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
}
.zine-project-card .tag-row .tag {
  border: 1.5px solid var(--zine-ink);
  background: color-mix(in oklab, currentColor 12%, transparent);
  color: inherit;
  font-family: var(--font-zine-body);
  font-weight: 600;
  font-size: 0.68rem;
  padding: 3px 8px;
  border-radius: 999px;
}
.zine-project-card .project-links {
  margin-top: var(--space-3);
}
.zine-project-card .project-links a {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-family: var(--font-zine-display);
  font-size: 0.8rem;
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.zine-project-card .project-links svg {
  width: 13px;
  height: 13px;
}
.zine-project-card--rose {
  background: var(--zine-rose);
  color: var(--zine-ink);
}
.zine-project-card--wine {
  background: var(--zine-wine);
  color: var(--zine-cream);
}
.zine-project-card--hydrangea {
  background: var(--zine-hydrangea);
  color: var(--zine-ink);
}
.zine-project-card--blush {
  background: var(--zine-blush);
  color: var(--zine-ink);
}
.zine-project-card--bronze {
  background: var(--zine-bronze);
  color: var(--zine-ink);
}
.zine-project-card--wine .tag-row .tag {
  border-color: var(--zine-cream);
}
.zine-project-card-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
}
.zine-project-card .card-thumb {
  width: 64px;
  height: 64px;
  object-fit: cover;
  border: 3px solid var(--zine-ink);
  box-shadow: 3px 3px 0 var(--zine-ink);
  transform: rotate(-4deg);
  flex-shrink: 0;
}

/* ============================================================
   SKILLS
   ============================================================ */
.skills-grid {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: 1fr;
}
@media (min-width: 700px) {
  .skills-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}
.skill-group {
  border-top: 2px dashed var(--color-border);
  padding-top: var(--space-5);
}
/* h4 on the Résumé (it sits under an h3 "Technical Skills", so h4 is
   the correct level there); h3 kept for any other use of this block. */
.skill-group h3,
.skill-group h4 {
  font-family: var(--font-hand);
  font-weight: 400;
  color: var(--color-secondary-ink);
  margin-bottom: var(--space-4);
  font-size: var(--text-lg);
}
.skill-chips,
.skill-sheet {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
:root[data-mode='fun'] .skill-sheet {
  gap: var(--space-4);
}
.skill-chip {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--text-sm);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-full);
  background: var(--color-surface);
  border: 2px solid var(--color-border);
  color: var(--color-text);
  transition:
    border-color var(--transition-interactive),
    transform var(--transition-interactive);
}
:root[data-mode='fun'] .skill-chip {
  font-weight: 800;
  padding: var(--space-2) var(--space-5);
}
:root[data-mode='fun'] .skill-chip:nth-child(4n + 1) {
  transform: rotate(-2deg);
}
:root[data-mode='fun'] .skill-chip:nth-child(4n + 2) {
  transform: rotate(1.5deg);
}
:root[data-mode='fun'] .skill-chip:nth-child(4n + 3) {
  transform: rotate(-1deg);
}
:root[data-mode='fun'] .skill-chip:nth-child(4n + 4) {
  transform: rotate(2deg);
}
.skill-chip:hover {
  border-color: var(--color-secondary);
  transform: translateY(-2px);
}
:root[data-mode='fun'] .skill-chip:hover {
  border-color: var(--color-primary);
  transform: translateY(-2px) rotate(0deg);
}

/* ============================================================
   EXPERIENCE / TIMELINE — professional mode only
   ============================================================ */
.timeline {
  display: flex;
  flex-direction: column;
}
.timeline-item {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
  padding-block: var(--space-8);
  border-top: 2px dashed var(--color-border);
}
.timeline-item:first-child {
  border-top: none;
}
@media (min-width: 760px) {
  .timeline-item {
    grid-template-columns: 220px 1fr;
    gap: var(--space-10);
  }
}
.timeline-period {
  font-family: var(--font-hand);
  font-size: var(--text-lg);
  font-weight: 400;
  color: var(--color-text-faint);
  padding-top: var(--space-1);
}
.timeline-role {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 400;
  margin-bottom: var(--space-1);
}
.timeline-org {
  font-size: var(--text-sm);
  color: var(--color-primary-ink);
  /* 700 is Bosca's real bold; 800 forced a synthesised weight. */
  font-weight: 700;
  margin-bottom: var(--space-3);
}
.timeline-item p {
  font-size: var(--text-sm);
  max-width: 62ch;
}

/* ============================================================
   CONTACT
   ============================================================ */
.contact-panel {
  background: var(--color-surface);
  border: 2px solid var(--color-border);
  border-radius: var(--radius-xl);
  padding: clamp(var(--space-10), 6vw, var(--space-16));
  display: grid;
  gap: var(--space-10);
  position: relative;
  overflow: hidden;
  box-shadow: var(--shadow-lg);
}
:root[data-mode='fun'] .contact-panel {
  border-width: 3px;
}
@media (min-width: 860px) {
  .contact-panel {
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr);
    align-items: center;
  }
}
.contact-panel h2 {
  font-size: var(--text-2xl);
  margin-bottom: var(--space-4);
}
.contact-links {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.contact-link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-4) var(--space-5);
  border: 2px solid var(--color-border);
  border-radius: var(--radius-md);
  text-decoration: none;
  color: var(--color-text);
  background: var(--color-bg);
  font-family: var(--font-subtitle);
  font-size: var(--text-base);
  font-weight: 400;
  transition:
    border-color var(--transition-interactive),
    background var(--transition-interactive),
    transform var(--transition-interactive);
}
:root[data-mode='fun'] .contact-link {
  font-weight: 700;
}
.contact-link:hover {
  border-color: var(--color-secondary);
  background: var(--color-secondary-soft);
  transform: translateX(3px);
}
:root[data-mode='fun'] .contact-link:hover {
  border-color: var(--color-primary);
  background: var(--color-primary-highlight);
}
.contact-link .link-left {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.contact-link svg {
  width: 18px;
  height: 18px;
  color: var(--color-secondary-ink);
}
.contact-link .arrow {
  width: 15px;
  height: 15px;
  color: var(--color-text-faint);
  transition: transform var(--transition-interactive);
}
.contact-link:hover .arrow {
  transform: translate(2px, -2px);
}

/* ============================================================
   FUN-MODE DENIM-SCRAPBOOK PRIMITIVES
   ============================================================ */

/* Bubble-outline title — the thick pink-stroke/white-fill heading
   style used for fun-mode section titles, background-independent
   (works whether it's sitting on denim or a card). */
.bubble-text {
  color: #fff;
  -webkit-text-stroke: 2px var(--color-primary);
  text-stroke: 2px var(--color-primary);
  text-shadow:
    0 2px 0 var(--color-primary),
    0 0 20px oklch(from var(--color-primary) l c h / 0.55);
  paint-order: stroke fill;
}

/* Status ticker — a rotating tagline marquee, the "Flash vibe"
   without Flash: a scrolling status line like an old away-message
   or a Bratz-site status bar. */
.ticker {
  overflow: hidden;
  /* Sits right under the sticky header, which already has its own
     dashed bottom border — only need a border below the ticker itself. */
  border-bottom: 2px dashed var(--color-border);
  padding-block: var(--space-2);
}
.ticker-track {
  display: inline-flex;
  gap: var(--space-10);
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--color-secondary-ink);
  letter-spacing: 0.02em;
  animation: ticker-scroll 22s linear infinite;
  padding-inline-end: var(--space-10);
}
@media (prefers-reduced-motion: reduce) {
  .ticker-track {
    animation: none;
  }
  .ticker {
    overflow-x: auto;
  }
}
@keyframes ticker-scroll {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}

/* ---- Charm stickers — gems, bows, pins scattered on the denim,
   like patches pinned to a jean jacket ---- */
.charm {
  position: absolute;
  font-size: 1.8rem;
  filter: drop-shadow(0 4px 6px oklch(0 0 0 / 0.35));
  pointer-events: none;
  z-index: 2;
}

/* ---- Note panel — a torn/hanging notebook page for body copy,
   echoing the pinned-up handwritten notes in the reference ---- */
.note-panel {
  position: relative;
  background: var(--color-surface);
  padding: calc(var(--space-6) + 16px) var(--space-8);
  /* Torn paper edge — a real rip, not pinking shears: a low, mostly-flat
     baseline where the paper separated cleanly along the grain, broken
     by occasional sharp, narrow snags where a fiber caught. No rhythm,
     no repeating scallop — the whole point is it reads as un-designed. */
  clip-path: polygon(
    0% 3px, 3% 5px, 7% 2px, 12% 9px, 14% 3px, 19% 13px, 22% 4px, 30% 5px, 35% 3px, 41% 14px, 44% 5px, 52% 6px, 60% 4px, 65% 12px, 68% 3px, 76% 5px,
    83% 9px, 86% 2px, 91% 11px, 94% 3px, 100% 5px,
    100% calc(100% - 4px), 96% calc(100% - 3px), 90% calc(100% - 11px), 87% calc(100% - 4px), 79% calc(100% - 5px), 72% calc(100% - 3px),
    66% calc(100% - 13px), 62% calc(100% - 5px), 55% calc(100% - 4px), 47% calc(100% - 6px), 42% calc(100% - 12px), 38% calc(100% - 3px),
    30% calc(100% - 4px), 23% calc(100% - 9px), 20% calc(100% - 3px), 13% calc(100% - 5px), 8% calc(100% - 2px), 4% calc(100% - 8px), 0% calc(100% - 4px)
  );
  filter: drop-shadow(0 14px 18px oklch(0 0 0 / 0.28));
}
.note-panel p {
  color: var(--color-text);
  margin-bottom: var(--space-3);
}
.note-panel p:last-child {
  margin-bottom: 0;
}

/* ============================================================
   FOOTER
   ============================================================ */
.site-footer {
  border-top: 2px dashed var(--color-border);
  padding-block: var(--space-10);
}
.footer-inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  align-items: center;
  text-align: center;
}
@media (min-width: 700px) {
  .footer-inner {
    flex-direction: row;
    justify-content: space-between;
    text-align: left;
  }
}
.footer-inner p {
  font-family: var(--font-hand);
  font-size: var(--text-base);
  color: var(--color-text-faint);
}
:root[data-mode='fun'] .footer-inner p {
  font-size: var(--text-lg);
}
.footer-links {
  display: flex;
  gap: var(--space-4);
}
.footer-links a {
  color: var(--color-text-faint);
}
.footer-links a:hover {
  color: var(--color-secondary-ink);
}

/* ============================================================
   SCROLL REVEAL (opacity / clip-path only — no CLS)
   ============================================================ */
.reveal {
  opacity: 1;
}
@supports (animation-timeline: scroll()) {
  .reveal {
    opacity: 0;
    animation: reveal-fade linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 55%;
  }
}
@keyframes reveal-fade {
  to {
    opacity: 1;
  }
}

/* ============================================================
   LOADING SCREEN — shown briefly on every page load, hidden by
   app.js once the page has settled. Framework only; tune the
   content/timing/animation to taste.
   ============================================================ */
.loading-screen {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-bg);
  transition:
    opacity 0.5s var(--ease-out),
    visibility 0.5s var(--ease-out);
}
.loading-screen.is-hidden {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}
.loading-content {
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
}
.loading-content .logo-mark {
  width: 56px;
  height: 56px;
}
.loading-label {
  font-family: var(--font-display);
  font-size: var(--text-xl);
}
:root[data-mode='fun'] .loading-label {
  font-family: 'Telefax TM', 'KonamiKiss', 'Titan One', sans-serif;
  font-size: var(--text-2xl);
  letter-spacing: 0.03em;
  color: var(--zine-wine);
}
.loading-sub {
  color: var(--color-text-muted);
}
:root[data-mode='fun'] .loading-sub {
  font-family: var(--font-zine-hand);
  color: var(--zine-rose-deep);
}
/* KND-file "typing" caret — only present while app.js is actively
   revealing that line's text, so it doesn't linger once done. */
.is-typing::after {
  content: '';
  display: inline-block;
  width: 0.5ch;
  height: 0.9em;
  margin-left: 3px;
  vertical-align: -0.1em;
  border-right: 3px solid currentColor;
  animation: loading-caret-blink 0.8s steps(1) infinite;
}
@keyframes loading-caret-blink {
  50% { border-color: transparent; }
}
.loading-dots {
  display: inline-flex;
  gap: 6px;
  margin-top: var(--space-2);
}
.loading-dots span {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--color-primary);
  animation: loading-bounce 1s ease-in-out infinite;
}
.loading-dots span:nth-child(2) {
  animation-delay: 0.15s;
}
.loading-dots span:nth-child(3) {
  animation-delay: 0.3s;
}
@keyframes loading-bounce {
  0%,
  80%,
  100% {
    transform: translateY(0);
    opacity: 0.5;
  }
  40% {
    transform: translateY(-6px);
    opacity: 1;
  }
}
@media (prefers-reduced-motion: reduce) {
  .loading-dots span {
    animation: none;
  }
}

/* ============================================================
   SPARKLE CURSOR TRAIL — fun mode only, spawned by app.js on
   pointermove. Framework only; tune colors/chars/spawn rate.
   ============================================================ */
#sparkle-layer {
  position: fixed;
  inset: 0;
  z-index: 90;
  pointer-events: none;
  overflow: hidden;
}
.sparkle-particle {
  position: absolute;
  font-size: 14px;
  line-height: 1;
  will-change: transform, opacity;
  animation: sparkle-pop 700ms ease-out forwards;
}
@keyframes sparkle-pop {
  0% {
    transform: translate(-50%, -50%) scale(0.4) rotate(0deg);
    opacity: 1;
  }
  60% {
    transform: translate(-50%, -70%) scale(1.1) rotate(40deg);
    opacity: 1;
  }
  100% {
    transform: translate(-50%, -110%) scale(0.6) rotate(80deg);
    opacity: 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  #sparkle-layer {
    display: none;
  }
}

/* ============================================================
   MISC
   ============================================================ */
.skip-link {
  position: absolute;
  left: -9999px;
  top: var(--space-4);
  background: var(--color-primary);
  color: var(--color-primary-text-on);
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-md);
  z-index: 100;
  font-weight: 600;
  font-size: var(--text-sm);
}
.skip-link:focus {
  left: var(--space-4);
}

[data-mode-toggle] svg {
  width: 20px;
  height: 20px;
}
