/* ═══════════════════════════════════════════════════════════════
   Fred site v2 — palettes

   Three themes, the same three the app ships: forest (default),
   purple, mono. Switching is one attribute write on <html>
   (data-theme) and the whole page retints on the next frame —
   nothing re-renders, the browser just re-resolves var().

   THE ACCENTS ARE THE APP'S, NOT THE LAB'S. bg-lab.html has
   "violet" and "mono" presets and they are the wrong colours:
   its purple is #7c5cfc / #a855f7 and its mono is a COOL grey
   #a1a1aa, where the app uses #ae9dff and a WARM grey #d6d3ce.
   Site and app have to agree or the switcher is a lie, so these
   are lifted from fred-box/desktop/src/styles/themes.css:

     app --f-bg        -> --base-1
     app --f-signal    -> --accent
     app --f-fg        -> --text
     app --f-on-signal -> --btn-text

   Keep in sync with:
     fred-box/desktop/src/styles/themes.css   (desktop renderer)
     fred-box/src/styles.css                  (legacy renderer)
     fred-box/fred-app/src/design/palettes.ts (mobile)

   The site needs tokens the app has no equivalent for — the mesh
   tints (--m1..--m4), --accent2, and --warmth — so those are
   derived per palette. See the notes on each.
   ═══════════════════════════════════════════════════════════════ */

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

:root,
:root[data-theme='forest']{
  /* ── palette ── */
  --base-1:#060d0b;
  --base-2:#0d1a15;
  --base-3:#060d0b;
  --m1:rgba(16,185,129,0.14);
  --m2:rgba(0,212,170,0.10);
  --m3:rgba(5,150,105,0.10);
  --m4:rgba(20,83,45,0.12);
  --accent:#10b981;
  --accent2:#6ee7b7;
  --text:#e7f2ec;
  --text-dim:#8ca79b;
  --border:#16291f;
  --btn-text:#04231b;

  /* Status colours. Deliberately NOT part of the accent: a failed
     form or an "in development" badge has to read as failed or
     unfinished in every palette, so they stay warm/red throughout
     and only mono desaturates them (see below). */
  --warn:#ff9f43;
  --warn-rgb:255,159,67;
  --err:#f87171;

  /* ── background stack ── */
  --warmth:0.16;
  --grain-op:0.024;       /* 0.065 -> 0.042 -> 0.034 -> 0.024, kept getting toned down.
                             Opacity is the ONLY grain knob to touch. The alpha
                             exponent in v2.js changes what the grain looks like,
                             not how much of it there is, and moving it made the
                             texture wrong rather than quieter. */
  --grain-scale:168px;
  --grain-blend:normal;   /* NOT overlay — overlay renders zero grain on a
                             near-black base. Measured identical to grain:off. */
  --grain-speed:0.75s;    /* Unused on the site now: the grain is static, see
                             #bgGrain. Kept because bg-lab.html emits this in its
                             Copy CSS output and still animates its own preview. */
  --drift-speed:24s;
  --mesh-op:0.48;
  --cast-op:0.55;
  --cast-angle:150deg;
  --spot-op:0.5;
  --spot-x:22%;
  --spot-y:8%;
  --vig-op:0.75;

  /* ── layout ── */
  --nav-h:64px;
  --maxw:1080px;
}

/* ── purple ──────────────────────────────────────────────────────
   The look Fred originally shipped with, kept as an opt-in. Only
   the palette and the two background values that are hue-dependent
   are overridden; grain, cast, spot and vignette are shared, so
   there is exactly one place to tune the background stack.

   --warmth drops from 0.16 to 0.05. It is a warm brown soft-light
   wash tuned against green, and at full strength over violet it
   turns the mesh muddy brown rather than warming it. */
:root[data-theme='purple']{
  --base-1:#07080b;
  --base-2:#0e1018;
  --base-3:#07080b;
  --m1:rgba(174,157,255,0.16);
  --m2:rgba(139,120,240,0.10);
  --m3:rgba(124,92,252,0.10);
  --m4:rgba(91,33,182,0.10);
  --accent:#ae9dff;
  --accent2:#d0c6ff;
  --text:#f2f3f5;
  --text-dim:#8a8b90;
  --border:#1c1f27;
  --btn-text:#0b0b12;

  --warmth:0.05;
}

/* ── mono ────────────────────────────────────────────────────────
   The quiet one. Its accent is a WARM grey (#d6d3ce, the app's),
   which is why --warmth stays fairly high here: the brown wash is
   what keeps a greyscale page from reading as cold and dead.

   Two consequences worth knowing before judging it:
   - --btn-text is dark, because the accent is light enough that
     white-on-accent would be unreadable.
   - The .grad headline flattens out. accent -> accent2 is a very
     short distance in greyscale, so the gradient reads as solid
     text. That is the theme working, not a bug to fix. */
:root[data-theme='mono']{
  --base-1:#0b0b0c;
  --base-2:#141416;
  --base-3:#0b0b0c;
  --m1:rgba(255,255,255,0.05);
  --m2:rgba(255,255,255,0.035);
  --m3:rgba(255,255,255,0.04);
  --m4:rgba(255,255,255,0.025);
  --accent:#d6d3ce;
  --accent2:#f0ede8;
  --text:#f4f4f5;
  --text-dim:#8c8c8d;
  --border:#232326;
  --btn-text:#171614;

  /* Mono desaturates status colour, it does not remove it: a failed
     submit still has to read as failed without reading the label.
     Same reasoning as --f-ok/warn/err in the app's themes.css. */
  --warn:#cdb88a;
  --warn-rgb:205,184,138;
  --err:#d69292;

  --warmth:0.12;
}

/* The clip has to be on the ROOT too. `overflow` on html applies to the
   viewport, so this is what actually stops sideways scrolling from the fixed
   background layers, whose negative insets (-8%, -10%) deliberately extend past
   the edges. A clip on body alone never touches fixed descendants, which is why
   phones could still be dragged sideways. */
html{scroll-behavior:smooth;overflow-x:clip;}
@supports not (overflow-x: clip){
  html{overflow-x:hidden;}
}
body{
  font-family:'Inter',-apple-system,sans-serif;
  background:var(--base-1);
  color:var(--text);
  line-height:1.7;
  min-height:100vh;
  /* `clip`, NOT `hidden`. overflow-x:hidden turns body into a scroll container,
     which silently kills `position:sticky` on everything inside it — the nav
     was declared sticky for weeks and never actually stuck. `clip` contains the
     background layers' negative insets without creating a scroll container. */
  overflow-x:clip;
  -webkit-font-smoothing:antialiased;
}
/* Safari < 16 has no `clip`; keep the old guard there and accept a nav that
   scrolls away rather than a page that scrolls sideways. */
@supports not (overflow-x: clip){
  body{overflow-x:hidden;}
}
::selection{background:color-mix(in srgb, var(--accent) 35%, transparent);color:var(--text);}

/* ═══ BACKGROUND STACK ═══ */
.bg-layer{position:fixed;inset:0;pointer-events:none;}
#bgBase{z-index:-6;background:linear-gradient(165deg,var(--base-2) 0%,var(--base-1) 45%,var(--base-3) 100%);}
#bgWarm{
  z-index:-5;opacity:var(--warmth);mix-blend-mode:soft-light;
  background:linear-gradient(150deg,rgba(190,160,130,0.22) 0%,rgba(140,115,95,0.10) 40%,rgba(60,50,45,0.05) 100%);
}
#bgMesh{
  z-index:-4;inset:-8%;opacity:var(--mesh-op);filter:blur(6px);will-change:transform;
  background:
    radial-gradient(48rem 38rem at 12% 8%,  var(--m1), transparent 62%),
    radial-gradient(42rem 34rem at 88% 22%, var(--m2), transparent 60%),
    radial-gradient(52rem 40rem at 68% 92%, var(--m3), transparent 62%),
    radial-gradient(34rem 30rem at 30% 68%, var(--m4), transparent 60%);
  animation:meshDrift var(--drift-speed) ease-in-out infinite;
}
#bgCast{
  z-index:-3;opacity:var(--cast-op);filter:blur(40px);
  background:linear-gradient(var(--cast-angle),
    rgba(0,0,0,0) 0%,rgba(0,0,0,0) 28%,rgba(0,0,0,0.35) 46%,
    rgba(0,0,0,0.55) 62%,rgba(0,0,0,0.20) 78%,rgba(0,0,0,0) 100%);
}
#bgSpot{
  z-index:-3;opacity:var(--spot-op);
  background:radial-gradient(60rem 46rem at var(--spot-x) var(--spot-y),
    rgba(255,255,255,0.055),rgba(255,255,255,0.02) 38%,transparent 68%);
}
#bgVig{
  z-index:-2;opacity:var(--vig-op);
  background:radial-gradient(130% 110% at 50% 32%,transparent 42%,rgba(0,0,0,0.30) 74%,rgba(0,0,0,0.62) 100%);
}
/* STATIC. The tile used to jitter on a steps(1) cycle (1.75% -> 0.9% -> 0.45%,
   0.28s -> 0.5s -> 0.75s, toned down every pass and never quite settled). It is
   the part that reads as fuzz, so it is gone rather than slowed. no will-change
   either, since nothing animates. */
#bgGrain{
  z-index:-1;inset:-10%;opacity:var(--grain-op);
  background-repeat:repeat;background-size:var(--grain-scale) var(--grain-scale);
  mix-blend-mode:var(--grain-blend);
}
@keyframes meshDrift{
  0%{transform:translate(0,0) scale(1);}
  33%{transform:translate(3%,-1.8%) scale(1.03);}
  66%{transform:translate(-2.1%,1.5%) scale(1.015);}
  100%{transform:translate(0,0) scale(1);}
}
@media (prefers-reduced-motion:reduce){
  #bgGrain,#bgMesh{animation:none;}
  html{scroll-behavior:auto;}
}

/* ═══ NAV ═══ */
/* The mount div must not generate a box. A sticky element can only travel
   inside its containing block, and #fred-nav is exactly as tall as the header,
   so the sticky range was zero pixels — the second reason the nav never stuck.
   `display:contents` dissolves the wrapper so body becomes the containing block. */
#fred-nav{display:contents;}

/* ── tee strip ── */
/* A slim line above the header, because the shirt is buried under "who's behind
   Fred" on the homepage and most people never scroll that far. Deliberately NOT
   sticky: the nav is what stays, so this scrolls away after the first screen and
   never takes space again. Dismissal is remembered, see site-nav.js. */
.tee-strip{
  position:relative;display:flex;align-items:center;justify-content:center;
  min-height:34px;padding:6px 42px;
  background:color-mix(in srgb, var(--accent) 10%, var(--base-1));
  border-bottom:1px solid color-mix(in srgb, var(--accent) 20%, transparent);
}
.ts-link{
  display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:5px 9px;
  text-decoration:none;font-size:0.78rem;font-weight:600;color:var(--text);
  line-height:1.35;text-align:center;
}
.ts-tag{
  font-size:0.58rem;font-weight:800;letter-spacing:1.1px;text-transform:uppercase;
  color:var(--btn-text);background:var(--accent2);padding:2px 8px;border-radius:999px;
}
.ts-sub{color:var(--text-dim);font-weight:500;}
.ts-go{color:var(--accent2);white-space:nowrap;transition:transform .2s;}
.ts-link:hover .ts-go{transform:translateX(3px);}
.ts-x{
  position:absolute;right:7px;top:50%;transform:translateY(-50%);
  width:26px;height:26px;display:flex;align-items:center;justify-content:center;
  background:none;border:none;border-radius:7px;cursor:pointer;
  color:var(--text-dim);font-family:inherit;font-size:1.05rem;line-height:1;
  transition:color .18s,background .18s;
}
.ts-x:hover{color:var(--text);background:color-mix(in srgb, var(--text) 9%, transparent);}
/* the second clause is the nice-to-have half of the sentence — it is the first
   thing to go when the strip would otherwise wrap to two lines on a phone */
@media (max-width:640px){
  .ts-sub{display:none;}
  .ts-link{font-size:0.74rem;}
}
.nav{
  position:sticky;top:0;z-index:100;height:var(--nav-h);
  display:flex;align-items:center;gap:28px;
  padding:0 28px;
  background:color-mix(in srgb, var(--base-1) 72%, transparent);
  backdrop-filter:blur(14px);
  border-bottom:1px solid color-mix(in srgb, var(--border) 80%, transparent);
}
.nav-logo{
  font-size:1.12rem;font-weight:800;letter-spacing:-0.5px;color:var(--text);
  text-decoration:none;flex-shrink:0;
}
.nav-logo span{color:var(--accent2);}
.nav-links{display:flex;gap:22px;margin-left:auto;}
.nav-links a{
  color:var(--text-dim);text-decoration:none;font-size:0.85rem;font-weight:600;
  transition:color .18s;
}
.nav-links a:hover{color:var(--text);}

.nav-lang,.nav-theme{position:relative;flex-shrink:0;}
/* One rule for both triggers. They sit next to each other, so any difference in
   padding, height or caret colour reads as a mistake — which is exactly what
   happened when the theme button had its own box: 5px/9px padding against
   6px/11px, and a caret at 0.5 opacity inheriting a different colour. */
.nav-lang-btn,.nav-theme-btn{
  display:flex;align-items:center;gap:5px;cursor:pointer;
  padding:6px 11px;border-radius:8px;font-family:inherit;
  font-size:0.76rem;font-weight:700;letter-spacing:0.4px;
  color:var(--text-dim);background:transparent;
  border:1px solid var(--border);transition:color .18s,border-color .18s;
  /* Equal height, not merely equal padding. These are flex containers, so each
     one's height comes from its tallest child: the language button gets a ~15px
     text line box, the theme button a 13px dot, and the pair sat 2px apart. The
     floor is in em of their shared font-size, so they stay equal if the type
     changes. */
  min-height:2.4em;
}
.nav-lang-btn:hover,.nav-theme-btn:hover{
  color:var(--text);border-color:color-mix(in srgb, var(--accent) 40%, transparent);
}
.nav-lang-menu,.nav-theme-menu{
  position:absolute;top:calc(100% + 7px);right:0;min-width:132px;
  background:color-mix(in srgb, var(--base-2) 96%, transparent);
  backdrop-filter:blur(16px);
  border:1px solid var(--border);border-radius:10px;padding:5px;
  box-shadow:0 18px 44px rgba(0,0,0,0.6);
  opacity:0;visibility:hidden;transform:translateY(-5px);
  transition:opacity .16s,transform .16s,visibility .16s;
}
.nav-lang-menu.open,.nav-theme-menu.open{opacity:1;visibility:visible;transform:translateY(0);}
.nav-lang-menu button,.nav-theme-menu button{
  display:block;width:100%;text-align:left;cursor:pointer;
  padding:7px 11px;border:none;border-radius:7px;background:transparent;
  color:var(--text-dim);font-family:inherit;font-size:0.8rem;font-weight:600;
}
.nav-lang-menu button:hover,.nav-theme-menu button:hover{
  background:color-mix(in srgb, var(--accent) 14%, transparent);color:var(--text);
}
.nav-lang-menu button.on,.nav-theme-menu button.on{color:var(--accent2);}

/* ── theme picker ──
   Sits next to the language picker and works the same way. The trigger is a
   swatch rather than a word because the thing being chosen IS a colour, and
   because "Theme" needed a fourth translation nobody had written.

   Every swatch paints itself from its own palette's literal accent, NOT from
   var(--accent) — the whole point is previewing a colour you are not currently
   using, so these three are the one place in the file where hardcoded hex is
   correct. Keep them equal to the --accent of each block at the top. */
.nav-theme-menu button{display:flex;align-items:center;gap:9px;}
.nt-dot{
  width:12px;height:12px;border-radius:50%;flex:0 0 auto;
  box-shadow:inset 0 0 0 1px rgba(0,0,0,0.35);
}
.nt-dot[data-sw='forest']{background:#10b981;}
.nt-dot[data-sw='purple']{background:#ae9dff;}
.nt-dot[data-sw='mono']{background:#d6d3ce;}
/* The trigger's own dot follows the ACTIVE theme, so it is the one that may
   use the live token. */
.nav-theme-btn .nt-dot{background:var(--accent);width:13px;height:13px;}
/* Kept at every width. It survives the 560px burger breakpoint because a bare
   swatch is ~31px next to the logo, burger and language button, and hiding it
   would leave phone visitors with no way to change theme at all — there is no
   other picker anywhere on the site. */
/* No opacity here: the language caret has none, and these two sit side by side. */
.nav-theme-btn .nt-caret{flex:0 0 auto;}

/* ═══ LAYOUT ═══ */
/* Now that the header genuinely stays put, anchor targets have to clear it or
   "Get Fred" and "Screenshots" land with their heading hidden behind the bar. */
.sec[id],#fred-tail,.ft-cta[id]{scroll-margin-top:calc(var(--nav-h) + 14px);}
.wrap{max-width:var(--maxw);margin:0 auto;padding:0 24px;}
.sec{padding:96px 0;}
.sec h2{
  font-size:clamp(1.4rem,3vw,1.9rem);font-weight:800;letter-spacing:-0.7px;
  margin-bottom:10px;text-align:center;
  background:linear-gradient(135deg,var(--accent),var(--accent2));
  -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;
}
.sec .sub{text-align:center;color:var(--text-dim);font-size:0.92rem;margin-bottom:40px;}
.placeholder-note{font-style:italic;opacity:0.55;}

/* ═══ HERO ═══ */
.hero{min-height:calc(92vh - var(--nav-h));display:flex;align-items:center;}
.hero-inner{display:flex;flex-direction:column;align-items:center;text-align:center;gap:22px;}
.eyebrow{
  display:inline-block;padding:5px 14px;border-radius:999px;
  font-size:0.66rem;font-weight:700;letter-spacing:1.4px;text-transform:uppercase;
  background:color-mix(in srgb, var(--accent2) 11%, transparent);
  color:var(--accent2);
  border:1px solid color-mix(in srgb, var(--accent2) 30%, transparent);
}
h1{font-size:clamp(2.6rem,7vw,5rem);font-weight:800;letter-spacing:-2.5px;line-height:1.02;}
h1 .grad{
  background:linear-gradient(135deg,var(--accent),var(--accent2));
  -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;
}

/* ═══ HERO KINETICS ═══════════════════════════════════════════════════════
   DESIGN_SYSTEM.md principle 03: "The product is alive. Never fully still."
   Three things, all opacity/transform/background-position only, so nothing
   here can shift layout or cost a reflow.

     1. staggered entrance, top to bottom
     2. a specular band that crosses the headline every ~11s — kinetic
        pattern 2 (scanline) applied to gradient text instead of a QR frame
     3. the pulse dot from kinetic pattern 1 on the EARLY ACCESS badge,
        which is a live-state label, exactly what that pattern is for
   ═══════════════════════════════════════════════════════════════════════ */
@keyframes heroIn{from{opacity:0;transform:translateY(15px);}to{opacity:1;transform:none;}}

.hero .eyebrow,
.hero h1 span,
.hero .claim,
.hero .lead,
.hero .platforms,
.hero .btns{
  animation:heroIn .85s cubic-bezier(.2,.8,.2,1) both;
}
/* the h1 lines are inline spans; transform is a no-op on inline boxes */
.hero h1 span{display:inline-block;}
.hero .eyebrow      {animation-delay:.06s;}
.hero h1 span:nth-of-type(1){animation-delay:.16s;}
.hero h1 span:nth-of-type(2){animation-delay:.29s;}
.hero .claim        {animation-delay:.46s;}
.hero .lead         {animation-delay:.58s;}
.hero .platforms    {animation-delay:.68s;}
.hero .btns         {animation-delay:.78s;}

/* Sweep: TWO layers. A narrow white streak rides above the untouched base
   gradient and crosses the words, which is a scanline in the sense the design
   system means it. Widening a band inside the base gradient instead just
   brightens the whole line at once, because the region around the band is
   already mint — tried it, it read as a flicker rather than a pass.
   The streak parks off-element at both ends, so the loop reset is invisible
   and the resting state is exactly the original gradient. */
.hero h1 .grad{
  background-image:
    linear-gradient(100deg, transparent 0%, rgba(255,255,255,0.88) 50%, transparent 100%),
    linear-gradient(135deg, var(--accent), var(--accent2));
  background-size:40% 100%, 100% 100%;
  background-repeat:no-repeat, no-repeat;
  background-position:-150% 0, 0 0;
  /* MUST be restated whenever `background`/`background-image` is set here:
     the shorthand resets background-clip to border-box, and with
     text-fill-color transparent that paints the box and hides the words. */
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;
  animation:
    heroIn .85s cubic-bezier(.2,.8,.2,1) .29s both,
    headlineSheen 9s 1.8s ease-in-out infinite;
}
@keyframes headlineSheen{
  0%,  14%  {background-position:-150% 0, 0 0;}
  62%, 100% {background-position: 250% 0, 0 0;}
}

/* pulse dot — 1.8s, scale 1 -> 1.15, opacity .6 -> 1, per the design system.
   A pseudo-element on purpose: lang.js rewrites this badge's text on every
   language switch, which would wipe out a real child node. */
.hero .eyebrow::before{
  content:'';display:inline-block;width:6px;height:6px;border-radius:50%;
  background:var(--accent2);margin-right:8px;vertical-align:1.5px;
  animation:pulseDot 1.8s ease-in-out infinite;
}
@keyframes pulseDot{
  0%,100%{opacity:.6;transform:scale(1);}
  50%    {opacity:1;transform:scale(1.15);}
}
.claim{
  font-size:clamp(1.1rem,2.4vw,1.4rem);font-weight:700;letter-spacing:-0.5px;
  color:var(--text);margin-top:-6px;
}
.claim b{
  font-weight:700;
  background:linear-gradient(135deg,var(--accent),var(--accent2));
  -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;
}
.lead{font-size:1.06rem;color:var(--text-dim);max-width:560px;line-height:1.7;}
.platforms{font-size:0.82rem;color:var(--text-dim);opacity:0.78;max-width:480px;line-height:1.55;}
.btns{display:flex;gap:12px;flex-wrap:wrap;justify-content:center;margin-top:6px;}
.btn{
  display:inline-block;padding:13px 26px;border-radius:11px;border:none;cursor:pointer;
  font-family:inherit;font-size:0.92rem;font-weight:700;text-decoration:none;
  background:linear-gradient(135deg,var(--accent),var(--accent2));
  color:var(--btn-text);
  box-shadow:0 8px 30px color-mix(in srgb, var(--accent) 30%, transparent);
  transition:transform .18s,box-shadow .18s;
}
.btn:hover{transform:translateY(-2px);box-shadow:0 12px 38px color-mix(in srgb, var(--accent) 40%, transparent);}
.btn.ghost{
  background:transparent;border:1px solid var(--border);color:var(--text);box-shadow:none;
}
.btn.ghost:hover{border-color:color-mix(in srgb, var(--accent) 50%, transparent);box-shadow:none;}

/* ═══ SHOWCASE ═══ */
/* No frame. These are composed mockups that already carry their own
   perspective, lighting and shadow — wrapping them in a bordered card reads
   as "screenshot pasted into a box", which is exactly what the phone shots
   avoid. drop-shadow (not box-shadow) follows the image's alpha silhouette,
   so a transparent PNG appears to float. On an opaque image it falls back to
   shadowing the rectangle, which is harmless. */
/* The desktop mockups are tight crops: the device bleeds off all four canvas
   edges, so only a corner or two is actually transparent (measured: 10.4% and
   0.3%). With no containment that renders as a hard rectangle with one
   chamfered corner. Rounded corners + shadow, but NO border stroke, so it
   reads as a deliberate crop rather than a bordered card.

   If a shot is ever re-exported with transparent margin all the way around
   the device, add class "floating" below to get the true cut-out look. */
/* Feathered into the page instead of framed.

   These mockups are tight crops — the device bleeds off all four canvas
   edges, so there is no silhouette to float (measured: only 10.4% and 0.3%
   of each canvas is transparent, none of it along the bottom). A border made
   it a card; no border made it a hard rectangle. Both read as "image pasted
   onto page".

   So: mask the edges to transparent. Two gradients intersected feather all
   four sides at once, and because the mockups are near-black they dissolve
   into the near-black background with no visible boundary at all. No shadow —
   a rectangular shadow behind a faded image gives the edge away. */
.wide-shot{
  position:relative;width:100%;
  --feather-x:7%;
  --feather-y:6%;
  -webkit-mask-image:
    linear-gradient(to right,  transparent 0, #000 var(--feather-x), #000 calc(100% - var(--feather-x)), transparent 100%),
    linear-gradient(to bottom, transparent 0, #000 var(--feather-y), #000 calc(100% - var(--feather-y)), transparent 100%);
  -webkit-mask-composite:source-in;
          mask-image:
    linear-gradient(to right,  transparent 0, #000 var(--feather-x), #000 calc(100% - var(--feather-x)), transparent 100%),
    linear-gradient(to bottom, transparent 0, #000 var(--feather-y), #000 calc(100% - var(--feather-y)), transparent 100%);
          mask-composite:intersect;
}
.wide-shot img{width:100%;height:auto;display:block;}

/* ── rotation: frames stacked in one slot, cross-fading ── */
.rotator{position:relative;}
.rotator .wide-shot{position:absolute;inset:0;transition:opacity 1.9s ease;}
.rotator .wide-shot img{width:100%;height:100%;object-fit:contain;}
.wide-shot.is-off{opacity:0;pointer-events:none;}

.rot-dots{display:flex;justify-content:center;gap:9px;margin-top:26px;}
.rot-dot{
  width:7px;height:7px;padding:0;border-radius:50%;cursor:pointer;
  border:none;background:color-mix(in srgb, var(--text) 22%, transparent);
  transition:background .3s, transform .3s;
}
.rot-dot:hover{background:color-mix(in srgb, var(--accent2) 55%, transparent);}
.rot-dot.on{background:var(--accent2);transform:scale(1.35);}

/* phones: each slot stacks its own images and cross-fades in place */
.shot img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  transition:opacity 1.9s ease;}
.shot img.is-off{opacity:0;}

/* Opt-outs, kept for shots that suit a different treatment. */
.wide-shot.framed-card{
  -webkit-mask-image:none;mask-image:none;
  border-radius:14px;overflow:hidden;
  box-shadow:0 16px 34px rgba(0,0,0,0.42), 0 46px 74px -12px rgba(0,0,0,0.40);
}
/* For a shot re-exported WITH transparent margin around the whole device. */
.wide-shot.floating{
  -webkit-mask-image:none;mask-image:none;
  filter:drop-shadow(0 16px 34px rgba(0,0,0,0.42)) drop-shadow(0 46px 74px rgba(0,0,0,0.34));
}

/* Opt-in card treatment, for plain rectangular screenshots that DO need a
   containing edge. Add class "framed". */
.wide-shot.framed{
  border-radius:16px;overflow:hidden;filter:none;
  border:1px solid color-mix(in srgb, var(--text) 9%, transparent);
  background:linear-gradient(165deg,var(--base-2),var(--base-1));
  box-shadow:
    0 2px 6px rgba(0,0,0,0.35),
    0 16px 40px rgba(0,0,0,0.45),
    0 50px 90px -24px rgba(0,0,0,0.65),
    0 0 90px -20px color-mix(in srgb, var(--accent) 15%, transparent);
}

.shots{display:flex;align-items:flex-end;justify-content:center;gap:clamp(20px,5vw,64px);
  padding:112px 0 24px;flex-wrap:wrap;}
.shot{
  position:relative;width:clamp(185px,23vw,272px);aspect-ratio:9/19.5;
  border-radius:34px;overflow:hidden;
  border:1px solid color-mix(in srgb, var(--text) 9%, transparent);
  background:linear-gradient(165deg,var(--base-2),var(--base-1));
  box-shadow:
    0 2px 4px rgba(0,0,0,0.30),
    0 8px 18px rgba(0,0,0,0.32),
    0 28px 50px rgba(0,0,0,0.42),
    0 60px 90px -20px rgba(0,0,0,0.55),
    0 0 60px -10px color-mix(in srgb, var(--accent) 12%, transparent);
  transition:transform .5s cubic-bezier(.2,.8,.2,1);
}
.shot img{width:100%;height:100%;object-fit:cover;display:block;}
.shot.tilt{transform:translateY(-14px) rotate(-3deg);}
.shot.tilt2{transform:translateY(10px) rotate(2.5deg);}
.shots:hover .shot{transform:translateY(-6px) rotate(0deg);}
.shot-empty{
  display:flex;align-items:center;justify-content:center;text-align:center;height:100%;
  color:var(--text-dim);opacity:0.45;font-size:0.72rem;letter-spacing:0.4px;padding:20px;
  font-family:'JetBrains Mono',monospace;
}

/* ═══ TIERS ═══ */
.tier-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(290px,1fr));gap:20px;align-items:stretch;}
.tier-card{
  position:relative;display:flex;flex-direction:column;gap:14px;
  padding:30px 28px;border-radius:18px;
  border:1px solid var(--border);
  background:color-mix(in srgb, var(--accent) 3%, transparent);
  backdrop-filter:blur(6px);
  transition:border-color .25s, transform .25s;
}
.tier-card:hover{transform:translateY(-3px);}
.tier-card-free{
  border-color:color-mix(in srgb, var(--accent2) 32%, transparent);
  background:color-mix(in srgb, var(--accent2) 5%, transparent);
}
.tier-card-free:hover{border-color:color-mix(in srgb, var(--accent2) 55%, transparent);}
.tier-card-box:hover{border-color:color-mix(in srgb, var(--accent) 40%, transparent);}
.tier-badge{
  align-self:flex-start;padding:4px 12px;border-radius:999px;
  font-size:0.64rem;font-weight:700;letter-spacing:1.2px;text-transform:uppercase;
}
.tier-badge-free{
  background:color-mix(in srgb, var(--accent2) 13%, transparent);
  color:var(--accent2);
  border:1px solid color-mix(in srgb, var(--accent2) 32%, transparent);
}
.tier-badge-dev{
  background:rgba(var(--warn-rgb),0.12);color:var(--warn);border:1px solid rgba(var(--warn-rgb),0.3);
}
.tier-name{font-size:1.32rem;font-weight:800;letter-spacing:-0.6px;line-height:1.2;}
.tier-price{font-size:0.9rem;color:var(--accent2);font-weight:600;}
.tier-price-dev{color:var(--warn);}
.tier-list{list-style:none;display:flex;flex-direction:column;gap:9px;margin:4px 0 auto;}
.tier-list li{
  position:relative;padding-left:24px;font-size:0.87rem;color:var(--text-dim);line-height:1.55;
}
.tier-list li::before{
  content:'';position:absolute;left:4px;top:0.58em;
  width:7px;height:7px;border-radius:50%;
  background:var(--accent2);opacity:0.75;
}
.tier-btn{
  display:block;text-align:center;text-decoration:none;margin-top:18px;
  padding:13px 20px;border-radius:11px;font-size:0.9rem;font-weight:700;
  transition:transform .18s,box-shadow .18s;
}
.tier-btn-free{
  background:linear-gradient(135deg,var(--accent),var(--accent2));color:var(--btn-text);
  box-shadow:0 8px 26px color-mix(in srgb, var(--accent) 26%, transparent);
}
.tier-btn-free:hover{transform:translateY(-2px);box-shadow:0 12px 34px color-mix(in srgb, var(--accent) 36%, transparent);}
.tier-btn-box{
  background:transparent;color:var(--text);
  border:1px solid color-mix(in srgb, var(--accent) 45%, transparent);
}
.tier-btn-box:hover{background:color-mix(in srgb, var(--accent) 12%, transparent);transform:translateY(-2px);}
.tier-note{font-size:0.78rem;color:var(--text-dim);line-height:1.55;margin-top:12px;opacity:0.85;}
.tier-note a{color:var(--accent2);text-decoration:none;}
.tier-note a:hover{text-decoration:underline;}

/* ═══ WHAT IS FRED — constellation ═══ */
.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;
}
.what-head{display:flex;flex-direction:column;align-items:center;text-align:center;gap:12px;margin-bottom:14px;}
.what-head h2{margin-bottom:0;}
.what-head .sub{margin-bottom:0;max-width:660px;}

.constellation{
  position:relative;
  width:100%;
  height:clamp(360px,44vw,520px);
  margin:26px auto 8px;
}
.cn-lines{position:absolute;inset:0;width:100%;height:100%;overflow:visible;}
.cn-lines line{
  stroke:color-mix(in srgb, var(--accent2) 26%, transparent);
  stroke-width:1;
  stroke-dasharray:3 5;
}

.cn-brain,.cn-node{
  position:absolute;left:50%;top:50%;
  transform:translate(-50%,-50%);
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  text-align:center;
}
.cn-brain{
  width:126px;height:126px;border-radius:50%;gap:2px;
  background:radial-gradient(circle at 50% 40%,
    color-mix(in srgb, var(--accent) 26%, transparent),
    color-mix(in srgb, var(--accent) 8%, transparent) 65%, transparent 72%);
  border:1px solid color-mix(in srgb, var(--accent2) 34%, transparent);
  box-shadow:0 0 44px -6px color-mix(in srgb, var(--accent) 42%, transparent);
  z-index:2;
}
.cn-brain .cn-emoji{font-size:1.9rem;line-height:1;}
.cn-brain b{
  font-size:0.6rem;font-weight:800;letter-spacing:1.6px;color:var(--accent2);
}
/* slow breathing pulse — the brain is the only thing that moves at rest */
.cn-brain::after{
  content:'';position:absolute;inset:-9px;border-radius:50%;
  border:1px solid color-mix(in srgb, var(--accent2) 26%, transparent);
  animation:cnPulse 3.6s ease-in-out infinite;
}
@keyframes cnPulse{
  0%,100%{transform:scale(1);opacity:0.55;}
  50%{transform:scale(1.09);opacity:0.12;}
}

.cn-node{
  gap:5px;padding:9px 13px;border-radius:12px;white-space:nowrap;
  background:color-mix(in srgb, var(--base-2) 88%, transparent);
  border:1px solid var(--border);
  backdrop-filter:blur(6px);
  transition:border-color .25s, transform .25s, box-shadow .25s;
  opacity:0;
}
.constellation.in .cn-node{opacity:1;transition:opacity .55s ease, border-color .25s, transform .25s;}
.cn-node .cn-emoji{font-size:1.15rem;line-height:1;}
.cn-node span.cn-label{
  font-size:0.58rem;font-weight:700;letter-spacing:1.1px;color:var(--text-dim);
}
.cn-node:hover{
  border-color:color-mix(in srgb, var(--accent2) 55%, transparent);
  transform:translate(-50%,-50%) scale(1.07);
  box-shadow:0 0 26px -6px color-mix(in srgb, var(--accent) 45%, transparent);
}
.cn-node:hover span.cn-label{color:var(--accent2);}

/* below the breakpoint the ring becomes an ordinary wrapped row */
.constellation.stacked{height:auto;display:flex;flex-direction:column;align-items:center;gap:22px;}
.constellation.stacked .cn-lines{display:none;}
.constellation.stacked .cn-brain{position:relative;left:auto;top:auto;transform:none;}
.constellation.stacked .cn-nodes{display:flex;flex-wrap:wrap;justify-content:center;gap:9px;}
.constellation.stacked .cn-node{position:relative;left:auto;top:auto;transform:none;}
.constellation.stacked .cn-node:hover{transform:scale(1.05);}

/* ═══ PILLARS ═══ */
.pillars{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(238px,1fr));
  gap:16px;margin-top:46px;
}
.pillar{
  padding:26px 24px;border-radius:16px;border:1px solid var(--border);
  background:color-mix(in srgb, var(--accent) 3%, transparent);
  backdrop-filter:blur(6px);
  transition:border-color .25s, transform .25s;
}
.pillar:hover{
  border-color:color-mix(in srgb, var(--accent2) 34%, transparent);
  transform:translateY(-3px);
}
.pl-icon{font-size:1.5rem;line-height:1;display:block;margin-bottom:12px;}
.pillar h3{font-size:0.99rem;font-weight:800;letter-spacing:-0.3px;margin-bottom:8px;}
.pillar p{font-size:0.85rem;color:var(--text-dim);line-height:1.68;}

/* ═══ PRINCIPLES ═══ */
.principles{
  margin-top:34px;padding:24px 26px;border-radius:16px;
  border:1px solid color-mix(in srgb, var(--accent2) 22%, transparent);
  background:color-mix(in srgb, var(--accent2) 4%, transparent);
}
.principles h4{
  font-size:0.66rem;font-weight:800;letter-spacing:1.5px;text-transform:uppercase;
  color:var(--accent2);margin-bottom:14px;text-align:center;
}
.principles ul{
  list-style:none;display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));
  gap:15px 30px;   /* row gap raised with the list at 8 items: some now wrap */
}
.principles li{
  position:relative;padding-left:23px;font-size:0.85rem;color:var(--text-dim);line-height:1.6;
}
.principles li::before{
  content:'';position:absolute;left:3px;top:0.52em;width:8px;height:8px;
  border-radius:50%;background:var(--accent2);opacity:0.8;
}

/* ═══ COMPARISON ═══ */
.cmp-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;}
.cmp{width:100%;min-width:560px;border-collapse:collapse;font-size:0.87rem;}
.cmp th,.cmp td{padding:13px 14px;text-align:center;}
.cmp thead th{
  font-weight:600;font-size:0.8rem;color:var(--text-dim);
  border-bottom:1px solid var(--border);
}
.cmp thead th.cmp-us{color:var(--accent2);font-weight:800;font-size:0.92rem;}
.cmp tbody td:first-child{text-align:left;color:var(--text-dim);}
.cmp tbody tr{border-bottom:1px solid color-mix(in srgb, var(--border) 70%, transparent);}
.cmp tbody tr:last-child{border-bottom:none;}
.cmp tbody tr:hover{background:color-mix(in srgb, var(--accent) 5%, transparent);}
.cmp td.y{color:var(--accent2);font-weight:700;font-size:1rem;}
.cmp td.n{color:var(--err);font-weight:700;font-size:1rem;}
.cmp td.m{color:var(--text-dim);font-size:0.78rem;opacity:0.8;}
/* the Fred column gets a subtle highlight down the table */
.cmp th:nth-child(2),.cmp td:nth-child(2){
  background:color-mix(in srgb, var(--accent) 6%, transparent);
}

/* ═══ ABOUT ═══ */
.about-card{
  display:flex;gap:28px;align-items:flex-start;
  padding:30px;border-radius:18px;border:1px solid var(--border);
  background:color-mix(in srgb, var(--accent) 3%, transparent);
  backdrop-filter:blur(6px);
}
.about-photo{
  width:148px;height:196px;object-fit:cover;border-radius:14px;flex-shrink:0;
  border:1px solid color-mix(in srgb, var(--text) 9%, transparent);
}
.about-body h3{font-size:1.12rem;font-weight:800;letter-spacing:-0.4px;}
.about-role{
  font-size:0.79rem;color:var(--accent2);margin:3px 0 14px;
  letter-spacing:0.2px;
}
.about-body p{font-size:0.88rem;color:var(--text-dim);line-height:1.7;margin-bottom:11px;}
.about-contact a{color:var(--accent2);text-decoration:none;font-weight:600;}
.about-contact a:hover{text-decoration:underline;}
@media (max-width:640px){
  .about-card{flex-direction:column;align-items:center;text-align:center;}
  .about-photo{width:132px;height:176px;}
}

/* ═══ FAQ ═══ */
.faq{max-width:760px;margin:0 auto;}
.faq details{
  border-bottom:1px solid var(--border);
}
.faq details:first-child{border-top:1px solid var(--border);}
.faq summary{
  list-style:none;cursor:pointer;padding:17px 34px 17px 2px;position:relative;
  font-size:0.93rem;font-weight:600;color:var(--text);
  transition:color .18s;
}
.faq summary::-webkit-details-marker{display:none;}
.faq summary:hover{color:var(--accent2);}
/* chevron */
.faq summary::after{
  content:'';position:absolute;right:8px;top:50%;
  width:8px;height:8px;margin-top:-5px;
  border-right:2px solid var(--text-dim);border-bottom:2px solid var(--text-dim);
  transform:rotate(45deg);transition:transform .22s, border-color .22s;
}
.faq details[open] summary::after{transform:rotate(-135deg);margin-top:-2px;border-color:var(--accent2);}
.faq details p{
  padding:0 34px 19px 2px;font-size:0.87rem;color:var(--text-dim);line-height:1.72;
}

/* ═══ SIMPLE CONTENT PAGES (legal, contact) ═══ */
.page-head{padding:72px 0 12px;text-align:center;}
/* the eyebrow is an inline-block, so without this it sits on the h1's cap line */
.page-head .eyebrow{margin-bottom:20px;}
.page-head h1{
  font-size:clamp(1.9rem,4.5vw,2.7rem);letter-spacing:-1.2px;margin-bottom:10px;
  background:linear-gradient(135deg,var(--accent),var(--accent2));
  -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;
}
.page-head p{color:var(--text-dim);font-size:0.92rem;}
.prose{max-width:760px;margin:0 auto;padding:34px 24px 90px;}
.prose h2{
  font-size:1.06rem;font-weight:700;letter-spacing:-0.2px;margin:34px 0 10px;color:var(--text);
  background:none;-webkit-text-fill-color:currentColor;text-align:left;
}
.prose h2:first-child{margin-top:0;}
.prose p,.prose li{color:var(--text-dim);font-size:0.9rem;line-height:1.75;margin-bottom:11px;}
.prose ul{padding-left:20px;margin-bottom:14px;}
.prose a{color:var(--accent2);text-decoration:none;}
.prose a:hover{text-decoration:underline;}
/* `.prose a` (0,1,1) outranks `.btn` (0,1,0), which made buttons inside prose
   take the link colour — mint text on a mint gradient, effectively invisible.
   Restate the button colours at matching specificity. */
.prose a.btn,.prose a.tier-btn-free{color:var(--btn-text);}
.prose a.btn.ghost,.prose a.tier-btn-box{color:var(--text);}
.prose a.btn:hover,.prose a.tier-btn:hover{text-decoration:none;}
.prose strong{color:var(--text);font-weight:600;}
.prose .meta{
  font-size:0.78rem;color:var(--text-dim);opacity:0.7;
  padding-bottom:18px;margin-bottom:26px;border-bottom:1px solid var(--border);
}

/* statically-multilingual legal pages: one block per language */
.lang-content{display:none;}
.lang-content.active{display:block;}
.prose h1{
  font-size:clamp(1.8rem,4vw,2.4rem);letter-spacing:-1.1px;margin-bottom:6px;
  background:linear-gradient(135deg,var(--accent),var(--accent2));
  -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;
}

/* ═══ FORM ═══ */
.form-card{
  max-width:560px;margin:0 auto;padding:30px 28px;border-radius:18px;
  border:1px solid var(--border);
  background:color-mix(in srgb, var(--accent) 3%, transparent);
  backdrop-filter:blur(6px);
}
.field{margin-bottom:16px;}
.field label{display:block;font-size:0.78rem;font-weight:600;color:var(--text-dim);margin-bottom:6px;}
.field input,.field textarea{
  width:100%;padding:12px 15px;border-radius:10px;
  border:1px solid var(--border);
  background:color-mix(in srgb, var(--text) 4%, transparent);
  color:var(--text);font-family:inherit;font-size:0.9rem;outline:none;
  transition:border-color .18s;
}
.field input:focus,.field textarea:focus{border-color:color-mix(in srgb, var(--accent) 55%, transparent);}
.field textarea{resize:vertical;min-height:130px;line-height:1.6;}
.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden;}
.form-msg{margin-top:14px;font-size:0.86rem;font-weight:600;display:none;}
.form-msg.ok{color:var(--accent2);display:block;}
.form-msg.err{color:var(--err);display:block;}

/* ═══ DONATE ═══ */
.chips{display:flex;flex-wrap:wrap;gap:10px;margin:18px 0 14px;}
.chip{
  flex:1;min-width:92px;text-align:center;text-decoration:none;
  padding:15px 12px;border-radius:12px;font-size:1.05rem;font-weight:800;
  color:var(--text);border:1px solid var(--border);
  background:color-mix(in srgb, var(--accent) 5%, transparent);
  transition:border-color .18s, background .18s, transform .18s;
}
.chip:hover{
  border-color:color-mix(in srgb, var(--accent2) 55%, transparent);
  background:color-mix(in srgb, var(--accent) 13%, transparent);
  transform:translateY(-2px);
}
.chip .cur{font-size:0.68rem;font-weight:700;color:var(--text-dim);margin-right:3px;vertical-align:2px;}
.methods,.hint{font-size:0.78rem;color:var(--text-dim);opacity:0.8;line-height:1.6;}
.hint{margin-top:12px;}
/* hidden by default — the donate script only reveals it for a section
   whose Stripe links are missing */
.coming-soon{display:none;font-size:0.82rem;color:var(--warn);margin-top:10px;}
.trust{font-size:0.76rem;color:var(--text-dim);opacity:0.7;line-height:1.7;margin-top:30px;}
.trust a{color:var(--accent2);text-decoration:none;}
.prose h2{margin-top:0;}

/* tappable rotators — subtle affordance, no obvious "button" chrome */
.tappable{cursor:pointer;-webkit-tap-highlight-color:transparent;}
.tappable:focus-visible{
  outline:2px solid var(--accent2);outline-offset:6px;border-radius:6px;
}
.tappable:active{transform:scale(0.995);}
.shot.tappable:active{filter:brightness(1.06);}

/* ═══ MERCH ═══ */
.merch-wrap{padding-bottom:96px;max-width:1000px;}
.tees{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:22px;margin-top:34px;}
.tee{
  padding:22px;border-radius:18px;border:1px solid var(--border);
  background:color-mix(in srgb, var(--accent) 3%, transparent);
  backdrop-filter:blur(6px);transition:border-color .25s;
}
.tee:hover{border-color:color-mix(in srgb, var(--accent2) 30%, transparent);}
.tee-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:14px;}
.tee-name{font-size:0.95rem;font-weight:700;letter-spacing:-0.2px;}
.cw-badge{
  font-size:0.62rem;font-weight:700;letter-spacing:1.1px;text-transform:uppercase;
  padding:4px 11px;border-radius:999px;color:var(--accent2);
  background:color-mix(in srgb, var(--accent2) 11%, transparent);
  border:1px solid color-mix(in srgb, var(--accent2) 28%, transparent);
}
/* The tee plate is `.tee-shot`, NOT `.shot`. `.shot` is the phone frame in the
   showcase section above; because these rules come later in the file they were
   winning, which squashed the phones to a 1:1 plate with object-fit:contain and
   in return gave the tee plates the phone frame's width clamp. Two unrelated
   components must not share a class name. */
/* The shots are cut-out PNGs with a real alpha channel, so they sit straight on
   the page background — no plate. `overflow:hidden` is gone with it, because the
   drop-shadows below spill past the box on purpose and were being clipped. */
.tee-shot{
  aspect-ratio:1/1;display:flex;width:100%;
}
.tee-shot img{width:100%;height:100%;object-fit:contain;display:block;}
/* A near-black tee on a near-black page is a silhouette, so it gets a faint cool
   halo to peel it off the background; the white one only needs grounding. Both
   shadows follow the alpha cut-out, which is why they read as light and not as a
   box. Keep the halo under ~12% or it turns into a glow. */
.tee-shot[data-cw="black"] img{
  filter:drop-shadow(0 0 24px rgba(231,242,236,0.11)) drop-shadow(0 16px 30px rgba(0,0,0,0.5));
}
.tee-shot[data-cw="white"] img{
  filter:drop-shadow(0 18px 32px rgba(0,0,0,0.55));
}
/* thumbs are a view switcher, not a second gallery — `flex:1` made each one half
   the card wide, i.e. nearly the size of the shot it switches */
.thumbs{display:flex;gap:10px;margin-top:14px;align-items:flex-start;justify-content:center;}
.thumb{
  flex:0 0 auto;width:72px;display:flex;flex-direction:column;align-items:center;gap:6px;
  background:none;border:none;cursor:pointer;font-family:inherit;padding:0;
}
/* The thumbs keep a surface, unlike the shot above: they are buttons, and at
   72px a cut-out black tee at 55% opacity is invisible. It is the same 4% wash
   every other card on the site uses, not a photo backdrop. */
.thumb-img{
  display:block;width:100%;border-radius:9px;overflow:hidden;aspect-ratio:1/1;
  background:color-mix(in srgb, var(--text) 4%, transparent);
  border:1px solid var(--border);transition:border-color .2s, opacity .2s;opacity:0.62;
}
.thumb-img img{width:100%;height:100%;object-fit:contain;display:block;}
.thumb.active .thumb-img,.thumb:hover .thumb-img{
  opacity:1;border-color:color-mix(in srgb, var(--accent2) 45%, transparent);
}
.thumb-cap{font-size:0.66rem;font-weight:700;letter-spacing:0.9px;text-transform:uppercase;color:var(--text-dim);}
.thumb.active .thumb-cap{color:var(--accent2);}

.tee-details{
  margin-top:26px;padding:22px 26px;border-radius:16px;
  border:1px solid var(--border);background:color-mix(in srgb, var(--accent) 3%, transparent);
}
/* The grid used to sit on .tee-details, whose only child is .specs — so it was
   always one column and the three specs stacked down the left edge, leaving the
   rest of the card empty. The grid belongs on the element that holds them. */
.specs{
  display:grid;grid-template-columns:minmax(200px,1fr) minmax(240px,1.05fr);
  gap:22px 40px;align-items:start;
}
.spec-col{display:flex;flex-direction:column;gap:20px;}
@media (max-width:640px){.specs{grid-template-columns:1fr;}}
.spec .k{font-size:0.63rem;font-weight:700;letter-spacing:1.1px;text-transform:uppercase;color:var(--text-dim);margin-bottom:5px;}
.spec .v{font-size:0.9rem;color:var(--text);}
.spec-note{font-size:0.78rem;color:var(--text-dim);opacity:0.8;}
.label-tag{
  margin-top:8px;padding:10px 13px;border-radius:9px;font-size:0.74rem;line-height:1.65;
  color:var(--text-dim);border:1px dashed color-mix(in srgb, var(--accent2) 22%, transparent);
  background:color-mix(in srgb, var(--accent) 4%, transparent);
}
.label-tag .cross{color:var(--accent2);opacity:0.6;letter-spacing:2px;}

/* ── order ── */
.order-card{
  margin-top:24px;padding:24px 26px;border-radius:18px;
  border:1px solid color-mix(in srgb, var(--accent2) 24%, transparent);
  background:color-mix(in srgb, var(--accent) 6%, transparent);
  backdrop-filter:blur(6px);
  display:flex;flex-wrap:wrap;align-items:center;gap:18px 28px;
}
.price{display:flex;align-items:baseline;gap:6px;flex-shrink:0;}
.price .cur{font-size:0.82rem;font-weight:700;letter-spacing:0.6px;color:var(--text-dim);}
.price .amt{
  font-size:2.2rem;font-weight:800;letter-spacing:-1.4px;line-height:1;
  background:linear-gradient(135deg,var(--accent),var(--accent2));
  -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;
}
.price .per{font-size:0.8rem;color:var(--text-dim);}
.order-copy{flex:1;min-width:230px;font-size:0.84rem;color:var(--text-dim);line-height:1.6;}
.order-card .btn{flex-shrink:0;}
.order-buy{display:flex;gap:10px;flex-wrap:wrap;}
/* Equal weight, both filled: a primary/ghost pair here reads as "buy the black
   one", and there is no recommendation to make. A swatch carries the colourway
   instead of the button style doing it. */
.order-buy .btn{display:inline-flex;align-items:center;gap:9px;}
.order-buy .btn::before{
  content:'';width:12px;height:12px;border-radius:50%;flex-shrink:0;
  box-shadow:0 0 0 1.5px rgba(4,35,27,0.32);
}
.order-buy .btn-cw-black::before{background:#111114;}
.order-buy .btn-cw-white::before{background:#fff;}
/* Side by side they'd be ~140px each here, which wraps "Buy the black one"
   onto three lines. Stack them full width instead, still equal. */
@media (max-width:560px){
  .order-buy{width:100%;flex-direction:column;}
  .order-buy .btn{width:100%;justify-content:center;}
}
/* The size field is required at checkout and easy to miss, and a wrong size
   cannot be exchanged, so this gets its own line rather than a clause buried
   in the shipping copy. */
.size-note{
  margin-top:16px;padding:14px 20px;border-radius:12px;text-align:center;
  font-size:0.86rem;line-height:1.6;color:var(--text-dim);
  border:1px dashed color-mix(in srgb, var(--accent2) 30%, transparent);
  background:color-mix(in srgb, var(--accent2) 6%, transparent);
}
.size-note strong{color:var(--accent2);font-weight:700;}
.order-help{
  margin-top:14px;text-align:center;font-size:0.83rem;color:var(--text-dim);opacity:0.85;
}
.order-help a{color:var(--accent2);text-decoration:none;font-weight:600;}
.order-help a:hover{text-decoration:underline;}
.facts{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(196px,1fr));
  gap:18px 26px;margin-top:24px;
}
.fact{padding-left:15px;border-left:2px solid color-mix(in srgb, var(--accent2) 28%, transparent);}
.fact .fk{font-size:0.8rem;font-weight:700;color:var(--text);margin-bottom:4px;}
.fact .fv{font-size:0.8rem;color:var(--text-dim);line-height:1.6;}
/* The cards used to carry a per-colourway "Buy this one" link. It was the same
   two Stripe links the order card below already offers, twice on one screen, so
   the cards are now purely the picture and the order card is the only place to
   buy. Bring `.tee-order` back from git if that ever reverses. */
.print-note{
  margin-top:24px;padding:20px 24px;border-radius:16px;
  border:1px solid var(--border);border-left:3px solid color-mix(in srgb, var(--accent) 55%, transparent);
  background:color-mix(in srgb, var(--accent) 3%, transparent);
}
.print-note h3{font-size:0.94rem;font-weight:700;letter-spacing:-0.2px;margin-bottom:7px;}
.print-note p{font-size:0.85rem;color:var(--text-dim);line-height:1.72;}

/* ── homepage tee plug ── */
.merch-teaser{
  display:flex;align-items:center;gap:26px;max-width:760px;margin:44px auto 0;
  padding:20px 24px;border-radius:18px;text-decoration:none;color:inherit;
  border:1px solid var(--border);background:color-mix(in srgb, var(--accent) 3%, transparent);
  backdrop-filter:blur(6px);transition:border-color .25s, transform .25s;
}
.merch-teaser:hover{
  border-color:color-mix(in srgb, var(--accent2) 34%, transparent);transform:translateY(-3px);
}
/* No plate, same as the merch page: the PNG is a cut-out and the card behind it
   is already a surface. A second, brighter rectangle inside it read as a hole. */
.mt-shot{
  flex:0 0 auto;width:112px;height:112px;
}
.mt-shot img{
  width:100%;height:100%;object-fit:contain;display:block;
  filter:drop-shadow(0 8px 16px rgba(0,0,0,0.5));
}
.mt-body{flex:1;min-width:0;}
/* every child here is a <span> (the whole card is one <a>), so they need
   blockifying by hand — flex only does that for direct flex items */
.mt-kicker{
  display:block;font-size:0.63rem;font-weight:700;letter-spacing:1.3px;text-transform:uppercase;
  color:var(--accent2);margin-bottom:6px;
}
.mt-title{display:block;font-size:1.08rem;font-weight:800;letter-spacing:-0.4px;margin-bottom:5px;}
.mt-sub{display:block;font-size:0.84rem;color:var(--text-dim);line-height:1.6;}
.mt-price{
  display:inline-block;margin-top:11px;padding:4px 11px;border-radius:999px;
  font-size:0.73rem;font-weight:700;color:var(--accent2);
  background:color-mix(in srgb, var(--accent2) 11%, transparent);
  border:1px solid color-mix(in srgb, var(--accent2) 26%, transparent);
}
.mt-go{flex-shrink:0;font-size:1.3rem;color:var(--accent2);opacity:0.6;transition:opacity .25s, transform .25s;}
.merch-teaser:hover .mt-go{opacity:1;transform:translateX(4px);}
@media (max-width:600px){
  .merch-teaser{flex-direction:column;text-align:center;gap:18px;}
  .mt-go{display:none;}
}
.tee-story{
  margin-top:26px;font-size:0.88rem;color:var(--text-dim);line-height:1.75;text-align:center;
  max-width:720px;margin-left:auto;margin-right:auto;
}
.notify{margin-top:44px;text-align:center;}
.notify h2{font-size:1.15rem;font-weight:800;letter-spacing:-0.3px;margin-bottom:6px;}
.notify p{font-size:0.9rem;color:var(--text-dim);margin-bottom:16px;}
.notify form{display:flex;gap:10px;max-width:520px;margin:0 auto;}
.notify input{
  flex:1;min-width:0;padding:12px 16px;border-radius:10px;
  border:1px solid var(--border);background:color-mix(in srgb, var(--text) 4%, transparent);
  color:var(--text);font-family:inherit;font-size:0.9rem;outline:none;
}
.notify input:focus{border-color:color-mix(in srgb, var(--accent) 55%, transparent);}
.notify button{
  flex-shrink:0;padding:12px 22px;border:none;border-radius:10px;cursor:pointer;
  background:linear-gradient(135deg,var(--accent),var(--accent2));color:var(--btn-text);
  font-family:inherit;font-size:0.9rem;font-weight:700;
}
.notify button:disabled{opacity:0.5;cursor:default;}
.notify .fine{font-size:0.78rem;color:var(--text-dim);margin-top:12px;}
.notify .fine a{color:var(--accent2);}
.notify .ok{display:none;font-size:0.92rem;color:var(--accent2);margin-top:6px;font-weight:600;}
.notify .ok.show{display:block;}
@media (max-width:480px){.notify form{flex-direction:column;}}

/* ═══ REVEAL ═══ */
.reveal{opacity:0;transform:translateY(22px);transition:opacity .7s ease,transform .7s cubic-bezier(.2,.8,.2,1);}
.reveal.in{opacity:1;transform:none;}
.shot.tilt.reveal.in{transform:translateY(-14px) rotate(-3deg);}
.shot.tilt2.reveal.in{transform:translateY(10px) rotate(2.5deg);}
@media (prefers-reduced-motion:reduce){
  .reveal{opacity:1;transform:none;transition:none;}
  /* hero kinetics off: everything visible at rest, no sweep, no pulse.
     The headline keeps a plain gradient rather than the sweep's wide one,
     which at 290% background-size would otherwise sit mid-ramp. */
  .hero .eyebrow,.hero h1 span,.hero .claim,.hero .lead,
  .hero .platforms,.hero .btns{animation:none;opacity:1;transform:none;}
  .hero h1 .grad{
    background-image:linear-gradient(135deg,var(--accent),var(--accent2));
    background-size:100% 100%;background-position:0 0;
    -webkit-background-clip:text;background-clip:text;
    -webkit-text-fill-color:transparent;
    animation:none;
  }
  .hero .eyebrow::before{animation:none;opacity:.85;}
}

/* ═══ RESPONSIVE ═══ */
@media (max-width:760px){
  .nav{padding:0 16px;gap:14px;}
  .nav-links{gap:14px;}
  .nav-links a{font-size:0.78rem;}
  .sec{padding:68px 0;}
  h1{letter-spacing:-1.4px;}
  .shots{gap:18px;padding-top:40px;}
  .shot.tilt,.shot.tilt2,.shot.tilt.reveal.in,.shot.tilt2.reveal.in{transform:none;}
}
/* ═══ MOBILE NAV ═══
   Below 560px the links used to be `display:none` with nothing in their place,
   so the whole menu was unreachable on a phone. They become a dropdown behind
   a burger instead. */
.nav-burger{display:none;}
@media (max-width:560px){
  .nav{gap:12px;}
  /* Row order on a phone: logo … theme, language, burger.
     The markup order is logo, links, burger, theme, lang — the burger sits next
     to the links it controls, which is right for the DOM but puts it in the
     middle of the row on mobile. Reordered here with flex `order` rather than by
     moving it in site-nav.js, because the markup order is also the tab order and
     a burger that opens the link panel belongs beside that panel.
     `margin-left:auto` moves to the first item of the right-hand group, which is
     now the theme picker, so the three controls stay flush right as one cluster. */
  .nav-theme{order:1;margin-left:auto;}
  .nav-lang{order:2;}
  .nav-burger{order:3;}

  .nav-burger{
    display:flex;flex-direction:column;justify-content:center;gap:4px;
    width:40px;height:38px;padding:0 10px;
    background:none;border:1px solid var(--border);border-radius:9px;cursor:pointer;
    transition:border-color .18s;
  }
  .nav-burger:hover{border-color:color-mix(in srgb, var(--accent) 40%, transparent);}
  .nav-burger span{
    display:block;height:1.5px;background:var(--text-dim);border-radius:2px;
    transition:transform .22s, opacity .18s, background .18s;
  }
  .nav-burger[aria-expanded="true"] span{background:var(--accent2);}
  .nav-burger[aria-expanded="true"] span:nth-child(1){transform:translateY(5.5px) rotate(45deg);}
  .nav-burger[aria-expanded="true"] span:nth-child(2){opacity:0;}
  .nav-burger[aria-expanded="true"] span:nth-child(3){transform:translateY(-5.5px) rotate(-45deg);}

  .nav-links{
    display:flex;flex-direction:column;gap:0;margin-left:0;
    position:absolute;top:var(--nav-h);left:0;right:0;
    padding:4px 20px 14px;
    /* Fully opaque, and no backdrop-filter: at 0.97 alpha inside a parent that
       already has backdrop-filter, page text behind stayed legible through the
       panel and fought with the links. A menu needs to be a solid surface. */
    background:var(--base-1);
    border-bottom:1px solid var(--border);
    box-shadow:0 22px 40px rgba(0,0,0,0.5);
    opacity:0;visibility:hidden;transform:translateY(-10px);
    transition:opacity .2s, transform .2s, visibility .2s;
  }
  .nav-links.open{opacity:1;visibility:visible;transform:none;}
  .nav-links a{
    padding:14px 2px;font-size:0.95rem;
    border-bottom:1px solid color-mix(in srgb, var(--border) 70%, transparent);
  }
  .nav-links a:last-child{border-bottom:none;}
}
