/* theme.css -- light mode (Linen) + Linen text in dark mode + the toggle
   + the tl;dr hover. Built 2026-10-01 from site/_proto/light-mode/ (Ivy's
   picks: Linen #E7DDCA light, linen-hued dark text at today's brightness,
   toggle symbol F3: the ✺ sets behind an unseen horizon). Home page only for now; loaded after
   style.css. Light mode = html[data-theme="linen"], set before paint by the
   inline script in <head> (system setting, or the visitor's last choice). */

/* ═══ palettes + light fixes ═══ */
/* light mode -- proto (2026-10-01). Loaded AFTER the real style.css.
   Everything is html[data-theme]-scoped, so with no theme set the page is
   exactly the live dark site.

   Two layers:
   1. TOKENS. Most of the site already paints with --void / --text /
      --text-dim / --text-mute / --rule, so re-pointing those does ~70%.
      Alphas are re-solved per palette so each tier keeps the contrast it
      has on dark (text ~10:1, dim ~4.5:1); mute is lifted to 3:1, because
      dim grey on light reads as washed-out rather than quiet.
   2. FIXES for the places that hard-code a dark color (nav pills, the
      tl;dr sheet, the cursor, shadows) or that assume "--void is dark"
      (text on a colored fill). */

/* ── palettes ───────────────────────────────────────────────────── */
html[data-theme="linen"] {
  --void: #E7DDCA; --void-2: #DDD2BD; --ink-rgb: 34 28 21;
  /* 10-02 (Ivy): dark type on a light page reads THINNER than light type on
     dark (irradiation: bright strokes bloom, dark strokes get eaten by the
     glow around them), so the quieter tiers go up a step: dim .67 -> .76
     (4.9 -> 6.5:1), mute .53 -> .64 (3.3 -> 4.5:1) */
  --text: rgb(34 28 21 / 0.94); --text-dim: rgb(34 28 21 / 0.76);
  --text-mute: rgb(34 28 21 / 0.64); --rule: rgb(34 28 21 / 0.13);
}

html[data-theme] {
  color-scheme: light;
  --stone: rgb(var(--ink-rgb));          /* the "bright" heading color on dark */
  --on-fill: #070709;                   /* text that sits ON a red/color fill: always dark */
  --shade: rgb(var(--ink-rgb) / 0.16);  /* shadows: warm ink, not black */
  --glass: color-mix(in srgb, var(--void) 94%, transparent);
}
html[data-theme], html[data-theme] body { background: var(--void); }
html[data-theme] ::selection { background: oklch(0.65 0.28 340 / 0.28); color: rgb(var(--ink-rgb)); }

/* grain: overlay noise darkens light paper more than it lifts black -- a touch less */
html[data-theme] .grain { opacity: 0.05; }

/* cursor: a light dot vanishes on paper */
html[data-theme] .cursor__dot { background: rgb(var(--ink-rgb)); box-shadow: 0 0 10px oklch(0.65 0.28 340 / 0.45); }
html[data-theme] .cursor__ring { border-color: rgb(var(--ink-rgb) / 0.5); box-shadow: none; }

/* nav + floating pills */
/* ⚠️ desktop only for .nav: on phones the nav has NO bar (7-06), just the
   two corner discs -- a glass fill there would draw a full-width strip */
@media (min-width: 761px) { html[data-theme] .nav { background: var(--glass); } }
html[data-theme] .nav__toggle,
html[data-theme] .to-top,
html[data-theme] .copy-hint,
html[data-theme] .card__soon-badge { background: var(--glass); }
html[data-theme] .nav.is-open .nav__links { background: var(--void); border-top-color: var(--rule); }
html[data-theme] .nav.is-open .nav__links li { border-bottom-color: var(--rule); }
/* ⚠️ not .nav__mark: it has NO fill on desktop (and none on phones since
   9-23). Giving it the glass stacked a second layer behind the name. */
html[data-theme] .nav__toggle { box-shadow: 0 8px 24px -12px var(--shade); }

/* hero: the BN Decos letters are hard-coded stone */
html[data-theme] .hero--decos .hero__title--decos .ink { fill: rgb(var(--ink-rgb)); }
html[data-theme] .hero--decos .hero__title--decos .line { stroke: rgb(var(--ink-rgb)); }
/* GRZYBOWSKI: on dark it's --text (92% alpha) at opacity .94 -- on linen
   that compounds to ~86% ink and reads lighter than the hero's solid fill */
html[data-theme] .fname__slot { color: rgb(var(--ink-rgb)); opacity: 1; }
html[data-theme] .hero--decos .hero__win { box-shadow: 0 30px 70px -20px var(--shade), 0 2px 8px var(--shade); }

/* cards: lift with a warm shadow, not a black one */
html[data-theme] .card { box-shadow: 0 10px 26px -12px var(--shade), 0 2px 6px -2px var(--shade); }
html[data-theme] .card:hover { box-shadow: 0 18px 40px -14px rgb(var(--ink-rgb) / 0.24), 0 4px 12px -2px var(--shade); }

/* tl;dr: the flood was stone with void text -- flipped, it floods INK and
   the text turns paper, so the button still inverts on hover */
html[data-theme] .quick-read::after { background-image: var(--sig, linear-gradient(90deg, rgb(var(--ink-rgb)), rgb(var(--ink-rgb)))); }
html[data-theme] [class*="sig-"] { --sig-ink: var(--on-fill); }
/* the tl;dr ring's hover-off now lives in tldr.css (both modes) */

/* the tl;dr sheet */
html[data-theme] .qr-scrim { background: color-mix(in srgb, var(--void) 72%, transparent); }
/* 10-02 (Ivy, after comparing both): the sheet is a LINEN card in light mode,
   and each project's title gets its true brand gradient as a highlighter band
   behind ink type -- the same mark as "Type unicorn" on the Unicorn page. */
html[data-theme] .qr-modal {
  background: linear-gradient(180deg, #F3ECDF 0%, var(--void) 100%);
  box-shadow: 0 2px 6px rgb(34 28 21 / 0.08), 0 18px 40px -12px rgb(34 28 21 / 0.22), 0 50px 110px -40px rgb(34 28 21 / 0.30);
}
html[data-theme] .qr__title b {
  -webkit-background-clip: border-box; background-clip: border-box;
  color: rgb(var(--ink-rgb)); -webkit-text-fill-color: rgb(var(--ink-rgb));
  background: linear-gradient(90deg, color-mix(in srgb, var(--sig-1, #E8956A) 58%, transparent), color-mix(in srgb, var(--sig-2, #C8A2FF) 58%, transparent)) 0 88% / 100% .62em no-repeat;
}
html[data-theme] .qr-out:hover { color: rgb(var(--ink-rgb)); border-color: currentColor; }
html[data-theme] .qr__x { background: rgb(var(--ink-rgb) / 0.06); }
html[data-theme] .qr__x:hover { background: rgb(var(--ink-rgb) / 0.12); }
html[data-theme] .qr__fade { background: linear-gradient(to top, var(--void-2) 30%, transparent); }

/* footer email: the hearts are red, so the type ON them stays dark */
html[data-theme] .foot--grz .foot__mail:hover,
html[data-theme] .foot--grz .foot__mail:active,
html[data-theme] .foot--grz .foot__mail.is-copied:hover { color: var(--on-fill); }

/* phones */
html[data-theme] .site-mobile-gate { background: var(--void); }

/* ── dark mode, linen text (Ivy 10-01: "so we switch this text to the
   linen color, so it's a clean switch between the modes") ─────────────
   A separate attribute, NOT data-theme, so none of the light fixes above
   apply. Same HUE as the light page, same BRIGHTNESS as today's stone:
   her 9-05 latte pass took the text darker on purpose to stop it blooming
   on the void, and full-strength linen is ~20% brighter. Every tier keeps
   today's contrast (text 10.6:1, dim 4.6:1); the title keeps 12.4:1. */
html:not([data-theme]) {
  --text: rgb(231 221 202 / 0.84);
  --text-dim: rgb(231 221 202 / 0.52);
  --text-mute: rgb(231 221 202 / 0.31);
  --rule: rgb(231 221 202 / 0.09);
  --stone: #D5CCBB;
}
html:not([data-theme]) .hero--decos .hero__title--decos .ink { fill: var(--stone); }
html:not([data-theme]) .hero--decos .hero__title--decos .line { stroke: var(--stone); }
html:not([data-theme]) .cursor__dot { background: #E7DDCA; }


/* ═══ tl;dr + every CTA hover (both modes) ═══ */
:root {
  --ease-ink: linear(0, 0.0035 3.57%, 0.0152 7.14%, 0.0368 10.71%, 0.0708 14.29%, 0.1203 17.86%,
    0.1894 21.43%, 0.2828 25%, 0.4029 28.57%, 0.5412 32.14%, 0.6706 35.71%, 0.7677 39.29%,
    0.8337 42.86%, 0.8783 46.43%, 0.9094 50%, 0.9318 53.57%, 0.9483 57.14%, 0.9608 60.71%,
    0.9705 64.29%, 0.9779 67.86%, 0.9838 71.43%, 0.9884 75%, 0.9919 78.57%, 0.9947 82.14%,
    0.9968 85.71%, 0.9983 89.29%, 0.9993 92.86%, 0.9998 96.43%, 1);
}
/* tl;dr hover, both modes -- proto (2026-10-01).

   Round 1 (measured on the live button): the drain ran on --ease-enter, so
   ~90% of the fill was gone in 100ms, the pill then sat with NO edge for
   ~250ms, then the ring faded in. Fast drop + gap + snap = abrupt.
   Round 2 (Ivy): one moving line fixed the gap, but the ink ring meeting the
   bright fill at that line was still harsh -- "maybe a gradient color, then
   fade to black".

   So: ONE value, --qr-cut (the fill's top edge, as a % of the button),
   drives the fill, both rings and the label. Two rings: the project's
   gradient underneath, the resting ink on top. --ring-a fades the ink ring:
   hover-in, the ring turns gradient first and the rising flood swallows it,
   color into color; hover-off, the ring redraws in gradient as the flood
   drains, then cools back to ink once the pill is empty. */
@property --qr-cut { syntax: "<percentage>"; inherits: true; initial-value: 100%; }
@property --ring-a { syntax: "<number>"; inherits: true; initial-value: 1; }

:is(.quick-read, .qr-cta) {
  --qr-cut: 100%;
  --ring-a: 1;
  /* the resting ring, made opaque (text over the page) so the gradient
     ring under it can't tint it at rest */
  --ring-solid: color-mix(in srgb, rgb(214 201 186) 92%, var(--void));
  /* round 3 (Ivy: "a little smoother from the gradient stroke to the
     black"): black fading in over a bright color changes LIGHTNESS fastest
     at the dark end, so an even opacity ramp still reads as a snap to black
     at the finish (the quint made it worse, all in the middle). This curve
     is even in how dark it LOOKS (CIE L*, gradient ~71 -> ink ~18) on sine
     timing -- the same idea as the footer's softer landing. 1.5s, and it
     starts while the last of the drain is still going so there's no wait. */
  /* round 4 (Ivy: "a tiny bit later... a little slower fade at the end"):
     starts .3s later, runs 2s, and the timing under the L* mapping is a
     gentle-start / long-settle curve, so the last steps into black take
     their time instead of arriving together. */
  transition:
    --qr-cut .9s cubic-bezier(.45, 0, .25, 1),
    --ring-a 2s var(--ease-ink) .75s;
}
html[data-theme] :is(.quick-read, .qr-cta) { --ring-solid: color-mix(in srgb, rgb(var(--ink-rgb)) 92%, var(--void)); }
html:not([data-theme]) :is(.quick-read, .qr-cta) { --ring-solid: color-mix(in srgb, rgb(231 221 202) 84%, var(--void)); }
:is(.quick-read, .qr-cta):hover {
  --qr-cut: 0%;
  --ring-a: 0;
  transition:
    --qr-cut .62s var(--ease-enter),
    --ring-a .22s var(--ease);
}

/* the fill */
:is(.quick-read, .qr-cta)::after,
:is(.quick-read, .qr-cta):hover::after { clip-path: inset(var(--qr-cut) 0 0 0); transition: none; }

/* the ink ring: on top, fades by --ring-a, cut by the same line */
:is(.quick-read, .qr-cta)::before,
:is(.quick-read, .qr-cta):hover::before {
  background: var(--ring-solid);
  opacity: var(--ring-a);
  clip-path: inset(0 0 calc(100% - var(--qr-cut)) 0);
  transition: none;
}

/* the label, and the gradient ring under the ink one. The span is padded
   out to the button's own box, so --qr-cut lines up with the fill. */
:is(.quick-read, .qr-cta) .qr-t {
  /* ⚙️ 2026-10-05: static, so the gradient ring (::before) sizes to the BUTTON,
     not to this span. Snapped to whole px, the button outgrew the span by a
     fraction and the ring's colour peeked out under the ink ring at rest. */
  position: static;
  display: block; padding: 11px 20px; margin: -11px -20px;   /* .quick-read; others below */
  background-image: linear-gradient(to bottom, var(--text) var(--qr-cut), var(--sig-ink, var(--void)) var(--qr-cut));
  -webkit-background-clip: text; background-clip: text;
  color: transparent; -webkit-text-fill-color: transparent;
}
:is(.quick-read, .qr-cta) .qr-t::before {
  content: ""; position: absolute; inset: 0; border-radius: var(--radius-pill); padding: 1.5px;
  background: var(--sig, linear-gradient(var(--ring-solid), var(--ring-solid)));
  clip-path: inset(0 0 calc(100% - var(--qr-cut)) 0);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
  pointer-events: none;
}

/* touch: no hover, so the finger-down state carries it */
@media (hover: none) {
  :is(.quick-read, .qr-cta):hover { --qr-cut: 100%; --ring-a: 1; }
  :is(.quick-read, .qr-cta):active { --qr-cut: 0%; --ring-a: 0; }
}
@media (prefers-reduced-motion: reduce) {
  :is(.quick-read, .qr-cta), :is(.quick-read, .qr-cta):hover { transition: --qr-cut .15s linear, --ring-a .15s linear; }
}

/* ── family B (10-02, Ivy: "the call-to-action buttons all need the same
   treatment"): Unicorn's critter + preview buttons and the case pages'
   "Up next" CTAs. Their ring is an inset box-shadow and their fill is
   ::before, so theme.js gives each one a .cta-rings span (gradient ring
   under, resting ring on top) and the label span. Same moving line, same
   2s cool-down to the resting ring. */
.qr-cta .qr-t { padding: 13px 24px; margin: -13px -24px; }
.uni-cta .qr-t { padding: 17px 32px; margin: -17px -32px; }
:is(.lost-next, .figma-next, .lost-reflect__cta) .qr-t { padding: 14px 22px; margin: -14px -22px; }
.tui-next .qr-t { padding: 14px 28px; margin: -14px -28px; }
:is(.uni-cta, .lost-next, .figma-next, .tui-next, .lost-reflect__cta) {
  --qr-cut: 100%;
  --ring-a: 1;
  --ring-solid: color-mix(in srgb, rgb(231 221 202) 84%, var(--void));
  --cta-ink: var(--void);
  --cta-ring-w: 1px;
  box-shadow: none !important;
  transition:
    --qr-cut .9s cubic-bezier(.45, 0, .25, 1),
    --ring-a 2s var(--ease-ink) .75s,
    color .3s var(--ease) .32s;
}
html[data-theme] :is(.uni-cta, .lost-next, .figma-next, .tui-next, .lost-reflect__cta) {
  --ring-solid: color-mix(in srgb, rgb(var(--ink-rgb)) 92%, var(--void));
  --cta-ink: var(--on-fill);
}
.uni-cta { --cta-fill: var(--uni-fill, var(--uni-glow)); --cta-ring-w: 1.5px; }
:is(.lost-next, .tui-next, .lost-reflect__cta) { --cta-fill: var(--neon); }
.figma-next { --cta-fill: var(--figma-glow); }
:is(.uni-cta, .lost-next, .figma-next, .tui-next, .lost-reflect__cta):hover {
  --qr-cut: 0%;
  --ring-a: 0;
  transition:
    --qr-cut .62s var(--ease-enter),
    --ring-a .22s var(--ease),
    color .3s var(--ease) .1s;
}
:is(.uni-cta, .lost-next, .figma-next, .tui-next, .lost-reflect__cta)::before,
:is(.uni-cta, .lost-next, .figma-next, .tui-next, .lost-reflect__cta):hover::before {
  clip-path: inset(var(--qr-cut) 0 0 0); transition: none;
}
.cta-rings { position: absolute; inset: 0; border-radius: inherit; pointer-events: none; z-index: 2; }
.cta-rings::before, .cta-rings::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit; padding: var(--cta-ring-w);
  clip-path: inset(0 0 calc(100% - var(--qr-cut)) 0);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
}
.cta-rings::before { background: var(--cta-fill, var(--ring-solid)); }
.cta-rings::after { background: var(--ring-solid); opacity: var(--ring-a); }
:is(.uni-cta, .lost-next, .figma-next, .tui-next) .qr-t {
  position: relative; display: block;
  background-image: linear-gradient(to bottom, var(--text) var(--qr-cut), var(--cta-ink) var(--qr-cut));
  -webkit-background-clip: text; background-clip: text;
  color: transparent; -webkit-text-fill-color: transparent;
}
@media (hover: none) {
  :is(.uni-cta, .lost-next, .figma-next, .tui-next, .lost-reflect__cta):hover { --qr-cut: 100%; --ring-a: 1; }
  :is(.uni-cta, .lost-next, .figma-next, .tui-next, .lost-reflect__cta):active { --qr-cut: 0%; --ring-a: 0; }
}
@media (prefers-reduced-motion: reduce) {
  :is(.uni-cta, .lost-next, .figma-next, .tui-next, .lost-reflect__cta),
  :is(.uni-cta, .lost-next, .figma-next, .tui-next, .lost-reflect__cta):hover { transition: --qr-cut .15s linear, --ring-a .15s linear; }
}

/* ═══ the toggle ═══ */
/* theme toggle -- proto (2026-10-01), round 4.
   Back in the top-right corner (Ivy: the nav "doesn't fit"; the ✺ and the
   nav stay exactly as they are). On the site's right RAIL -- the line the
   hero video's right edge sits on, the same --rail the hero uses, so it
   holds on every page -- and centered on the nav pill. Fixed, so it travels
   with the nav. Phones: beside the burger, as a 46px disc twinned with it.
   Full opacity, no hover style, 18px (her round-3 asks).
   The glyph is drawn by icons.js (the light <-> dark morphs). */
.theme-toggle {
  position: fixed;
  z-index: 10001;
  top: var(--tt-top, 26px);
  right: var(--tt-right, max(var(--edge), calc((100vw - min(1400px, 92vw)) / 2)));
  width: 18px; height: 18px;
  display: grid; place-items: center;
  padding: 0; border: 0; border-radius: 50%;
  background: transparent;
  color: var(--stone, #D6C9BA);
  cursor: pointer;
  /* ⚠️ buttons don't inherit the page font: without this the ✺ fell back to a
     chunky 12-ray glyph via Arial in her Chrome, not the site's thin ✺ */
  font-family: var(--f-mono);
}
.theme-toggle::before { content: ""; position: absolute; inset: -12px; border-radius: 50%; }  /* 42px hit area */
.theme-toggle:focus-visible { outline: 1px solid var(--text-mute); outline-offset: 5px; }
.theme-toggle svg { display: block; width: 18px; height: 18px; overflow: visible; }

/* round 5 (Ivy): the nav DROPS the ✺ -- the toggle is the symbol now.
   Desktop: the name opens the pill. Phones: the ✺'s old top-left corner is
   where the toggle lives (a 46px disc twinned with the burger). The nav is
   measured after load, so the pill re-centers and the contact reveal holds. */
.nav .nav__glyph { display: none; }
.nav .nav__mark { gap: 0; }
@media (max-width: 760px) {
  /* keep the slot (visibility, not display): the burger is pushed right by it */
  .nav .nav__mark { visibility: hidden; pointer-events: none; }
  .theme-toggle {
    right: auto; left: var(--tt-left, 24px);
    width: 46px; height: 46px;
    background: rgb(7 7 9 / 0.94);
    box-shadow: 0 8px 24px -12px rgba(0, 0, 0, 0.75);
  }
  .theme-toggle::before { inset: 0; }
  .theme-toggle svg { width: 21px; height: 21px; }
  html[data-theme] .theme-toggle { background: var(--glass); box-shadow: 0 8px 24px -12px var(--shade); }
}

/* round 8: the toggle is its own layer during the switch, above the flood,
   showing its live (morphing) self; no crossfade of its own */
.theme-toggle { view-transition-name: theme-toggle; }
::view-transition-old(theme-toggle) { display: none; }
::view-transition-new(theme-toggle) { animation: none; }
::view-transition-group(theme-toggle) { animation: none; }

/* the switch itself: the new theme floods out from the toggle on a
   feathered edge (View Transitions), never a hard wipe */
@property --vt-r { syntax: "<length>"; inherits: false; initial-value: 0px; }
::view-transition-old(root),
::view-transition-new(root) { animation: none; mix-blend-mode: normal; }
::view-transition-new(root) {
  -webkit-mask-image: radial-gradient(circle at var(--vt-x, 100%) var(--vt-y, 0), #000 calc(var(--vt-r) - 140px), transparent var(--vt-r));
          mask-image: radial-gradient(circle at var(--vt-x, 100%) var(--vt-y, 0), #000 calc(var(--vt-r) - 140px), transparent var(--vt-r));
}

/* ═══ shared helpers for every page (2026-10-02, site-wide rollout) ═══ */

/* DARK ISLANDS -- objects that stay dark in light mode on purpose (media
   heroes, video captions, dark cards). Inside one, point the text tokens
   back at their dark-mode values:
     html[data-theme="linen"] .x { --text: var(--dk-text); --text-dim: var(--dk-text-dim);
       --text-mute: var(--dk-text-mute); --rule: var(--dk-rule); --stone: var(--dk-stone); } */
:root {
  --dk-void: #070709; --dk-void-2: #0e0e14;
  --dk-text: rgb(231 221 202 / 0.84); --dk-text-dim: rgb(231 221 202 / 0.52);
  --dk-text-mute: rgb(231 221 202 / 0.31); --dk-rule: rgb(231 221 202 / 0.09);
  --dk-stone: #D5CCBB;
  --on-fill: #070709;   /* text ON a bright fill stays dark in both modes */
}

/* dark mode, linen text: the stone literals style.css hard-codes */
html:not([data-theme]) ::selection { color: #E7DDCA; }
html:not([data-theme]) .cursor__dot { box-shadow: 0 0 14px rgba(231, 221, 202, 0.9), 0 0 28px var(--pink-hex); }
html:not([data-theme]) .cursor__ring { border-color: rgba(231, 221, 202, 0.7); }
html:not([data-theme]) .quick-read::after,
html:not([data-theme]) .foot--grz .foot__mail::after { background-image: var(--sig, linear-gradient(90deg, #D5CCBB, #D5CCBB)); }
/* GRZYBOWSKI matches PRODUCT DESIGNER in dark too: --text (.84) x opacity .94
   read grey next to the solid title (the same fix light mode got) */
html:not([data-theme]) .fname__slot { color: var(--stone); opacity: 1; }

/* phones: the gate's accent drifts through bright neons (lime, mint, ...) that
   vanish on linen; in light mode it keeps the drift, a shade deeper */
html[data-theme="linen"] .site-mobile-gate__glyph,
html[data-theme="linen"] .site-mobile-gate__title em { filter: brightness(0.55) saturate(1.25); }

/* 10-02: color by the surface behind it (theme-toggle.js reads it), so a fixed
   toggle never vanishes over a dark hero in light mode, or a pale photo in dark */
@media (min-width: 761px) {
  .theme-toggle { transition: color .45s ease; }
  .theme-toggle.on-dark { color: #D5CCBB; }
  .theme-toggle.on-light { color: rgb(34 28 21); }
}

/* the Unicorn hover hearts: warm ink shadow on linen, not black */
html[data-theme="linen"] .uni-heart { filter: drop-shadow(0 1px 4px rgb(var(--ink-rgb) / 0.22)); }

/* the tl;dr sheet's title + "out" links paint in the project's signature
   gradient (lime, lilac, pink...) -- bright on black, near-invisible on linen.
   In light mode the same gradient, a shade deeper. */


/* 10-02 (Ivy): the linen tl;dr sheet read as "blocks of text" -- the section
   labels were pale. Solid ink + bold, so each row starts with a clear title. */
html[data-theme] .qr-row .lbl { color: rgb(var(--ink-rgb)); font-weight: 700; }

/* 10-02 (Ivy: the home project cards read "very dark" in light mode). Their
   art rests at 0.62-0.78 over a near-black card, tuned to sit back on the
   dark page; on linen that's a dark hole. Light mode lifts the art to near
   full and drops the scrim to a breath. Figma CL (card--04) was right as is. */
html[data-theme="linen"] .work-row .card:not(.card--05) { --art-opacity: 0.96; }
html[data-theme="linen"] .work-row .card:not(.card--05)::before { opacity: 0.05; }   /* Figma CL too (Ivy: its sky read too dark after all) */

/* ═══ phones: bare corner glyphs (2026-10-04, Ivy) ═══
   The toggle, burger and to-top drop their 46px discs -- "sticking out too
   much". Each keeps its 46px tap target and takes ink or linen from the
   surface behind it (theme.js reads it), so it holds over dark heroes and
   pale photos alike. */
@media (max-width: 760px) {
  .theme-toggle,
  html[data-theme] .theme-toggle,
  .nav__toggle,
  html[data-theme] .nav__toggle,
  .to-top,
  html[data-theme] .to-top {
    background: transparent;
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .theme-toggle, .nav__toggle, .to-top { transition: color .45s ease, opacity 0.4s var(--ease), transform 0.5s var(--ease), bottom 0.6s var(--ease); }
  .nav__toggle span { height: 1.5px; background: currentColor; }
  .nav__toggle { color: var(--text); }
  .theme-toggle.on-dark, .nav__toggle.on-dark, .to-top.on-dark { color: #D5CCBB; }
  .theme-toggle.on-light, .nav__toggle.on-light, .to-top.on-light { color: rgb(34 28 21); }
}

/* ═══ phones: the open menu (2026-10-04, Ivy) ═══
   Light mode: the sheet was the page's own linen, so it vanished into it.
   It lifts onto paper with the warm three-layer shadow the light cards use.
   The "email copied ✓" row's pink -> lilac was pastel on paper; light mode
   takes the deeper coral -> orchid -> violet run from the Unicorn page.
   Experiments leaves the phone menu: phones only reach the desktop gate there. */
@media (max-width: 760px) {
  html[data-theme="linen"] .nav.is-open .nav__links {
    background: #EFE6D6;   /* a hair under paper: lifts off the linen without glaring */
    box-shadow:
      0 1px 2px rgb(var(--ink-rgb) / 0.06),
      0 8px 20px -8px rgb(var(--ink-rgb) / 0.14),
      0 28px 56px -24px rgb(var(--ink-rgb) / 0.22);
  }
  html[data-theme="linen"] .nav__links li.nav__contact-wrap::after {
    background: linear-gradient(90deg, #CD4F3E, #AE4C9E 50%, #8550DC);
    -webkit-background-clip: text; background-clip: text;
  }
  .nav__links li:has(> a[href$="experiments/"]) { display: none; }
}

/* ═══ phones: a TAP plays the fill (2026-10-05, Ivy) ═══
   Nobody holds a button on a phone. theme.js adds .is-tapfill on tap, the
   fill floods at the desktop hover's own pace, then the button does its job. */
@media (hover: none) {
  :is(.quick-read, .qr-cta, .uni-cta, .lost-next, .figma-next, .tui-next, .lost-reflect__cta).is-tapfill {
    --qr-cut: 0%; --ring-a: 0;
    transition: --qr-cut .62s var(--ease-enter), --ring-a .22s var(--ease);
  }
}

/* ═══ phones: the corner glyphs sit closer to the edges (2026-10-05, Ivy) ═══
   max(24px, 6vw) -> max(16px, 4vw): toggle left, burger + to-top right. The
   toggle follows the nav's left (theme.js reads it). */
@media (max-width: 760px) {
  .nav { left: max(16px, 4vw); right: max(16px, 4vw); }
  .landing-body .to-top, .to-top,
  .landing-body .to-top.is-lifted, .to-top.is-lifted { right: calc(max(16px, 4vw) - 3px); }
}

/* the tl;dr sheet takes focus when it opens (keyboard users land inside it).
   On phones the tap replays a click, and Safari sometimes drew its blue ring
   round the whole card (Ivy, 10-05). The sheet itself never needs a ring;
   its buttons keep theirs. */
.qr-modal:focus, .qr-modal:focus-visible { outline: none; }

/* ⚙️ 2026-10-05 (Ivy): desktop nav -- name, Work, Experiments, About, Contact
   on ONE even rhythm. The gap after the name was 42px (it used to hold the ✺,
   which left the nav); the links run 28px. Contact stays collapsed at rest and
   opens on hover exactly as before -- only the name's gap changes. */
@media (min-width: 761px) {
  .nav { gap: 28px; }
}

/* ⚙️ 2026-10-05 (Ivy): desktop back-to-top loses its filled disc too -- a
   bare arrow like phones, taking ink or linen from the surface behind it */
@media (min-width: 761px) {
  .to-top, html[data-theme] .to-top, .landing-body .to-top {
    background: transparent; box-shadow: none;
    backdrop-filter: none; -webkit-backdrop-filter: none;
    transition: color .45s ease, opacity 0.4s var(--ease), transform 0.5s var(--ease), bottom 0.6s var(--ease);
  }
  .to-top.on-dark { color: #D5CCBB; }
  .to-top.on-light { color: rgb(34 28 21); }
}

/* ⚙️ 2026-10-05 (Ivy): desktop back-to-top, the same on every page. It rests
   LOW (home's spot: every other page parked it ~23px higher, on top of the I)
   and centres on the I of GRZYBOWSKI, under its heart -- theme.js measures
   the I and sets --totop-right. */
@media (min-width: 761px) {
  .to-top, .landing-body .to-top {
    right: var(--totop-right, calc(var(--edge) - 14px));
    bottom: clamp(12px, 2vh, 22px);
  }
}


/* ═══ ONE light-mode shadow (2026-10-07, Ivy: option E "Depth", corrected) ═══
   Light mode had ~5 shadow systems (flat cards, a bare hat, a black board
   shadow, a heavy events card, a bare Config photo). One token now: a tight
   darker contact line that lifts the card off the linen + two soft layers.
   Correction from the proto: almost no sideways offset and negative spread on
   the soft layers, so nothing hangs off the bottom-right corner (the
   diagonal offsets stacked there and read as a wrong radius). Warm ink only.
   Static (hover = rest), light mode only; dark mode is untouched. */
html[data-theme="linen"] {
  --shadow-card:
    0 1px 1.5px rgb(34 28 21 / .20),
    1px 6px 12px -4px rgb(34 28 21 / .14),
    3px 22px 40px -16px rgb(34 28 21 / .12);
}
/* home */
html[data-theme="linen"] .card,
html[data-theme="linen"] .card:hover,
html[data-theme="linen"] .hero__win,
html[data-theme="linen"] .hero__hat { box-shadow: var(--shadow-card); }
/* About */
html[data-theme="linen"] .print figure,
html[data-theme="linen"] .about-flipboard .flipboard,
html[data-theme="linen"] .about-config .about-events--card,
html[data-theme="linen"] .about-config__fig,
html[data-theme="linen"] .about-config--events .about-config__fig,
html[data-theme="linen"] .s2--about .s2__orchid::before { box-shadow: var(--shadow-card); }
@media (min-width: 761px) {
  html[data-theme="linen"] .about-hero { box-shadow: var(--shadow-card); }
}
/* the orchid tile's figure ends its reveal on clip-path: inset(0), which cut
   the shadow at the old padding. More room (the margins already cancel it,
   so the layout doesn't move). */
html[data-theme="linen"] .s2--about .s2__orchid { --pt: 28px; --px: 72px; --pb: 104px; }

/* ═══ squircle corners (2026-10-07, Ivy: "Squircle 12px is where it's at") ═══
   A circular corner meets the straight edge with a visible kink ("a little
   knob" on the big cards). `corner-shape: squircle` is the continuous Apple
   curve; Chrome / Edge / Dia draw it, and box-shadow + overflow clipping
   follow it. A squircle reads tighter than a round corner of the same
   number, so 12px squircle ~ 7px round -- which is the round radius every
   other browser (Safari, Firefox) gets from --radius-sm. Same softness for
   everyone; the smoother curve where it's supported. Media-like surfaces
   only: pills, discs and the nav keep their own shapes. */
@supports (corner-shape: squircle) {
  :root { --radius-sq: 12px; }
  /* :root :is() lifts these over the page-specific radius rules (e.g. the
     home hero's) without !important; pseudo-elements can't sit in :is() */
  :root :is(.card, .card__img, .card__img img, .card__video,
    .hero__win, .hero__win video, .hero__hat, .hero__hat img,
    .print figure, .about-config__fig, .about-config .about-events--card,
    .about-flipboard .flipboard, .exp-card__media),
  :root .card::before, :root .card::after,
  :root .s2--about .s2__orchid::before {
    border-radius: var(--radius-sq);
    corner-shape: squircle;
  }
  /* the About hero is a card on desktop only (full-bleed on phones) */
  @media (min-width: 761px) {
    :root :is(.about-hero, .about-hero__canvas, .about-hero__canvas canvas) { border-radius: var(--radius-sq); corner-shape: squircle; }
  }
  /* this clip can only draw a circular corner; the squircle'd box clips instead */
  :root .card__img { clip-path: none; }
}
/* elements theme.js finds on the 7px corner (media-sized) -> the same squircle */
@supports (corner-shape: squircle) {
  .sq-auto { border-radius: var(--radius-sq) !important; corner-shape: squircle; }
}

/* ⚙️ 2026-10-07 (Ivy): the desktop nav pill on the same family as the CTA
   pills -- a full capsule (--radius-pill), not an 18px rounded box -- and in
   light mode it sits on the page with the cards' Depth shadow. Phones have
   no nav bar; dark mode keeps no shadow. */
@media (min-width: 761px) {
  .nav { border-radius: var(--radius-pill); }
  html[data-theme="linen"] .nav { box-shadow: var(--shadow-card); }
}

/* ═══ ONE shape for the nav + every button (2026-10-07, Ivy: option 6 of
   _proto/light-shadows/buttons.html) ═══
   Not pills ("standard AI slop") -- a squircle at ~36% of each element's own
   height, so the family reads as one shape at different sizes (the taller
   nav a touch rounder than the small tl;dr). Browsers without squircle get a
   ROUND corner at 58% of that (squircle 12 ~ round 7), so it looks the same
   size. Every layer that draws the outline or the fill follows: the button,
   its ::before / ::after, the gradient ring (.qr-t::before), the family-B
   rings (.cta-rings). The footer email's ::after is its round flood circle --
   left alone; the button's own clip already shapes it. */
.nav { --btn-r: 18px; }                                   /* 50px tall */
.quick-read { --btn-r: 13px; }                            /* 37px */
.qr-cta { --btn-r: 16px; }                                /* 44px */
:is(.uni-cta, .lost-next, .figma-next, .tui-next, .lost-reflect__cta) { --btn-r: 19px; }   /* ~52px */
.foot--grz .foot__mail { --btn-r: 21px; }                 /* ~58px */
@media (min-width: 761px) {
  :root .nav { border-radius: calc(var(--btn-r) * .58); }
}
:root :is(.quick-read, .qr-cta, .uni-cta, .lost-next, .figma-next, .tui-next, .lost-reflect__cta, .foot--grz .foot__mail),
:root :is(.quick-read, .qr-cta, .uni-cta, .lost-next, .figma-next, .tui-next, .lost-reflect__cta)::before,
:root :is(.quick-read, .qr-cta, .uni-cta, .lost-next, .figma-next, .tui-next, .lost-reflect__cta)::after,
:root .foot--grz .foot__mail::before,
:root :is(.quick-read, .qr-cta) .qr-t::before,
:root .cta-rings, :root .cta-rings::before, :root .cta-rings::after {
  border-radius: calc(var(--btn-r) * .58) !important;
}
@supports (corner-shape: squircle) {
  @media (min-width: 761px) {
    :root .nav { border-radius: var(--btn-r); corner-shape: squircle; }
  }
  :root :is(.quick-read, .qr-cta, .uni-cta, .lost-next, .figma-next, .tui-next, .lost-reflect__cta, .foot--grz .foot__mail),
  :root :is(.quick-read, .qr-cta, .uni-cta, .lost-next, .figma-next, .tui-next, .lost-reflect__cta)::before,
  :root :is(.quick-read, .qr-cta, .uni-cta, .lost-next, .figma-next, .tui-next, .lost-reflect__cta)::after,
  :root .foot--grz .foot__mail::before,
  :root :is(.quick-read, .qr-cta) .qr-t::before,
  :root .cta-rings, :root .cta-rings::before, :root .cta-rings::after {
    border-radius: var(--btn-r) !important;
    corner-shape: squircle;
  }
}

/* the nav + buttons get their own, gentler depth (light mode): the same soft
   top as the cards, much less underneath -- they're flat surfaces in the
   page's own colour, where the cards' long shadow read too heavy */
html[data-theme="linen"] {
  /* (Ivy, 2nd pass) balanced: a little on TOP, less underneath */
  --shadow-ui: 0 -1px 3px rgb(34 28 21 / .07), 0 1px 1.5px rgb(34 28 21 / .10), 0 2px 4px -3px rgb(34 28 21 / .06);
}
/* (Ivy, 10-08) the light nav wears NOTHING around it: any shadow read as a
   blurry stroke. The buttons keep --shadow-ui. */
@media (min-width: 761px) {
  html[data-theme="linen"] .nav { box-shadow: none; }
}
/* (Ivy, 10-08) not the footer email: like the nav, a shadow read as a blurry stroke on it */
html[data-theme="linen"] :is(.quick-read, .qr-cta, .uni-cta, .lost-next, .figma-next, .tui-next, .lost-reflect__cta) {
  box-shadow: var(--shadow-ui) !important;
}

@media (min-width: 761px) {
  /* the nav's words sat ~1px above centre (19.4px over the caps, 21.3 under
     the baseline): down 1px. The symbol (no lift now) centres with them. */
  .nav__mark, .nav__links { position: relative; top: 1px; }
}


/* ⚙️ 2026-10-08 (Ivy): the small pills join the shape family -- the board's
   "next" (About + the Experiments card), the commit heatmap's sound toggle,
   and the sound buttons on the project pages. Same rule as the nav + CTAs:
   a squircle at ~36% of the button's height, round at 58% of that without. */
.fb-rail__next { --btn-r: 13px; }                         /* About board: 37px */
.exp-flipboard-card__mount .fb-rail__next { --btn-r: 8px; }   /* Experiments card: 22px */
.sound-toggle { --btn-r: 14px; }                          /* heatmap: 38px */
.uni-sound { --btn-r: 10px; }                             /* 29px */
.lost-thinking__classroom-sound { --btn-r: 10px; }        /* 28px */
.figma-beats__sound { --btn-r: 12px; }                    /* 32px */
.uni-cliptag { --btn-r: 10px; }                           /* the "clip" tags: 27.5px */
:root :is(.fb-rail__next, .sound-toggle, .uni-sound, .lost-thinking__classroom-sound, .figma-beats__sound, .uni-cliptag),
:root :is(.fb-rail__next, .sound-toggle, .figma-beats__sound)::before,
:root .figma-beats__sound::after {
  border-radius: calc(var(--btn-r) * .58) !important;
}
@supports (corner-shape: squircle) {
  :root :is(.fb-rail__next, .sound-toggle, .uni-sound, .lost-thinking__classroom-sound, .figma-beats__sound, .uni-cliptag),
  :root :is(.fb-rail__next, .sound-toggle, .figma-beats__sound)::before,
  :root .figma-beats__sound::after {
    border-radius: var(--btn-r) !important;
    corner-shape: squircle;
  }
}
