/* site.css — shared chrome for every Lyvorian Studio page.
   Loaded before each page's own <style>, so page rules still win. */

:root{
  --bg:#f2e8d5; --bg2:#ecdfc4; --panel:#faf3e3;
  --line:rgba(var(--shadow-rgb),.18); --line-bright:rgba(var(--shadow-rgb),.42);
  --text:#2a2013; --muted:#6b5a3e; --muted2:#8c7a5c;
  --gold:#a97a1f; --gold-bright:#c98f22; --gold-dim:#8a6a2e;
  --maroon:#8a3838; --ok:#3b7a4f; --ok-dot:#5da271;
  /* The focus ring is a control, not decoration: WCAG wants 3:1 for a
     non-text indicator and --gold-bright manages 2.3:1 on this page. */
  --focus:#8a6a2e;
  --hdr:rgba(242,232,213,.75);
  --r:16px;

  /* Decorative tints. Every gradient, glow and ember reads these rather than
     a literal, so a theme can retune the whole decorative layer at once.
     Stored as bare channels so they can be used inside rgba(). */
  --tint-rgb:201,143,34;      /* the gold */
  --tint-hi-rgb:240,203,126;  /* its highlight */
  --tint-2-rgb:138,56,56;     /* the maroon counterpoint */
  --shadow-rgb:42,32,19;

  /* ── motion ──────────────────────────────────────────────
     One scale, so nothing on the site invents its own timing. Values follow
     the durations that read as responsive rather than decorative: UI motion
     stays under 300ms, entrances ease out hard, and nothing uses ease-in,
     which drags at the start of a movement. Only transform and opacity are
     animated — anything touching layout costs a reflow per frame. */
  --e-out:cubic-bezier(.23,1,.32,1);       /* entrances, feedback */
  --e-inout:cubic-bezier(.77,0,.175,1);    /* movement across the screen */
  --t-press:120ms;    /* button press */
  --t-quick:180ms;    /* tooltips, chips */
  --t-ui:240ms;       /* dropdowns, cards */
  --t-panel:320ms;    /* panels and overlays */
  --stagger:55ms;     /* between items in a list */

  /* ── glass ─────────────────────────────────────────────────────────
     Frosted surfaces only work when there is something behind them worth
     refracting, so body::before lays down a fixed colour field (see
     home.css). Four parts to each surface:
       --glass      the translucent fill
       --glass-brd  the edge, catching light
       --glass-hi   a 1px inner highlight along the top — this is the
                    detail that reads as "pane of glass" rather than
                    "semi-transparent box"
       --glass-shd  elevation, so panes sit above the field
     saturate() in the blur keeps the gold underneath from going grey. */
  --glass:rgba(255,252,245,.62);
  --glass-brd:rgba(255,255,255,.75);
  --glass-hi:rgba(255,255,255,.9);
  --glass-shd:0 18px 44px -20px rgba(var(--shadow-rgb),.3);
  --glass-blur:blur(18px) saturate(150%);
  --glass-strong:rgba(255,253,248,.78);

  /* ── typography ─────────────────────────────────────────
     Two families. Cormorant Garamond carries headlines and display text at
     Regular and Medium; Manrope carries body, buttons and labels at Regular
     through Bold. It is the same pairing the Finance Tracker app itself uses,
     so the site and the product now read as one thing.

     Cormorant is a Garamond: small x-height (.386 against Newsreader's .426)
     and a delicate, high-contrast stroke. Two consequences are baked into the
     tokens below — display sizes are ~10% larger than the sans-serif era
     needed to hold the same optical weight, and the tight negative tracking
     that suited a grotesque is relaxed almost to zero, because cramping a
     Garamond closes its counters and muddies it.

     Tokens use the `font` shorthand (weight size/line-height family), so any
     font-style must be declared AFTER the token or the shorthand resets it. */
  --font-display:'Cormorant Garamond','Georgia',serif;
  --font-interface:'Manrope','Manrope Fallback','Segoe UI',Arial,sans-serif;

  /* display — Cormorant Garamond, Regular 400 / Medium 500 */
  --t-hero:500 clamp(2.25rem,4.7vw,3.6rem)/1.06 var(--font-display);
  --ls-hero:-.012em;
  --t-h2:500 clamp(1.65rem,2.8vw,2.2rem)/1.16 var(--font-display);
  --ls-h2:-.008em;
  --t-h3:500 1.375rem/1.28 var(--font-display);
  --ls-h3:-.006em;
  --t-quote:400 clamp(1.25rem,2vw,1.5rem)/1.5 var(--font-display);
  --ls-quote:0em;

  /* interface — Manrope, Regular 400 / Medium 500 / SemiBold 600 / Bold 700 */
  --t-card-title:600 1.1875rem/1.32 var(--font-interface);
  --ls-card-title:-.018em;
  --t-body-lg:400 1.0625rem/1.62 var(--font-interface);
  --ls-body-lg:-.006em;
  --t-body:400 .9375rem/1.65 var(--font-interface);
  --ls-body:-.002em;
  --t-body-sm:400 .875rem/1.6 var(--font-interface);
  --ls-body-sm:0em;
  --t-eyebrow:600 .6875rem/1.5 var(--font-interface);
  --ls-eyebrow:.12em;
  /* 550 and 450 were valid on a 100-900 axis; Manrope is pinned 400-700 here,
     so these snap to the nearest real weight rather than silently rounding */
  --t-button:500 .875rem/1 var(--font-interface);
  --ls-button:-.006em;
  --t-form-label:500 .8125rem/1.4 var(--font-interface);
  --ls-form-label:.01em;
  --t-status:600 .6875rem/1 var(--font-interface);
  --ls-status:.05em;
  --t-caption:400 .8125rem/1.5 var(--font-interface);
  --ls-caption:0em;

  /* measure — long text must never run the width of the screen */
  --measure:66ch;
  --measure-tight:52ch;

  /* ── spacing ─────────────────────────────────────────────
     One scale. The audit found 38 distinct padding/margin values including
     1, 3, 5, 7, 9, 11, 13, 15, 34, 38, 46, 52 and 58px — arbitrary numbers
     that make rhythm impossible to hold across pages. */
  --sp-1:4px;   --sp-2:8px;   --sp-3:12px;  --sp-4:16px;
  --sp-5:24px;  --sp-6:32px;  --sp-7:48px;  --sp-8:64px;
  --sp-9:96px;  --sp-10:128px; --sp-11:160px;

  /* ── radius ──────────────────────────────────────────────
     A hierarchy, not noise: the audit found 23 distinct values. Editorial
     surfaces get 0 on purpose — the contrast against rounded controls is
     what stops everything reading as the same soft card. */
  --r-none:0;    --r-xs:6px;   --r-sm:8px;
  --r-md:14px;   --r-lg:20px;  --r-xl:28px;  --r-pill:100px;

  /* ── elevation ───────────────────────────────────────────
     Depth from surface difference and a tight shadow, not a large blur. */
  --sh-1:0 1px 2px rgba(var(--shadow-rgb),.18);
  --sh-2:0 6px 18px -8px rgba(var(--shadow-rgb),.30);
  --sh-3:0 18px 44px -20px rgba(var(--shadow-rgb),.42);
  --sh-4:0 42px 92px -34px rgba(var(--shadow-rgb),.55);

  /* ── layering ────────────────────────────────────────────
     Named, so nothing has to guess a z-index and win by accident. */
  --z-base:0; --z-raised:2; --z-sticky:20; --z-header:60;
  --z-overlay:200; --z-modal:300; --z-toast:400;

  /* ── grid ────────────────────────────────────────────────
     12 columns on desktop, 8 on tablet, 4 on mobile, one gutter scale.
     Breakpoints are listed for reference only: a media query cannot read a
     custom property, so the numbers below are duplicated literally in the
     @media rules and must be changed in both places. */
  --grid-cols:12;
  --gutter:clamp(20px,3.4vw,40px);
  --maxw:1220px;
  --maxw-narrow:760px;
  /* breakpoints (literal in @media): 480 / 768 / 940 / 1100 / 1440 */
}
@media(prefers-color-scheme:dark){
  :root:not([data-theme]){
    --bg:#212327; --bg2:#2b2d31; --panel:#2b2d31;
    --line:rgba(255,255,255,.09); --line-bright:rgba(255,255,255,.20);
    --text:#f4f4f5; --muted:#a9adb5; --muted2:#989ea9;
    /* no gold in dark: the accent is light, not warm. --gold* are kept as
       names so every existing rule keeps working, remapped to neutrals. */
    --gold:#e8e9ec; --gold-bright:#ffffff; --gold-dim:rgba(255,255,255,.34);
    --ok:#8fe09f; --ok-dot:#8fe09f;
    /* --maroon was never remapped for dark: #8a3838 on #2b2d31 is 1.78:1,
       which made the form error message and every "we never do this" ✕
       marker effectively invisible. */
    --maroon:#ef9a9a;
    --focus:#ffffff;
    --hdr:rgba(33,35,39,.82);
    /* charcoal has no warmth to reinforce: the decorative layer goes neutral,
       with a faint cool counterpoint instead of the maroon */
    --tint-rgb:255,255,255;
    --tint-hi-rgb:255,255,255;
    --tint-2-rgb:150,160,180;
    --shadow-rgb:0,0,0;
    /* glass on charcoal is a cool light tint, not a warm one */
    --glass:rgba(255,255,255,.045);
    --glass-brd:rgba(255,255,255,.10);
    --glass-hi:rgba(255,255,255,.14);
    --glass-shd:0 20px 50px -24px rgba(0,0,0,.8);
    --glass-blur:blur(18px) saturate(120%);
    --glass-strong:rgba(255,255,255,.07);
  }
}
:root[data-theme="light"]{
  --bg:#f2e8d5; --bg2:#ecdfc4; --panel:#faf3e3;
  --line:rgba(var(--shadow-rgb),.18); --line-bright:rgba(var(--shadow-rgb),.42);
  --text:#2a2013; --muted:#6b5a3e; --muted2:#8c7a5c;
  --gold:#a97a1f; --gold-bright:#c98f22; --gold-dim:#8a6a2e;
  --ok:#3b7a4f; --ok-dot:#5da271;
  --focus:#8a6a2e;
  --hdr:rgba(242,232,213,.75);
  --glass:rgba(255,252,245,.62);
  --glass-brd:rgba(255,255,255,.75);
  --glass-hi:rgba(255,255,255,.9);
  --glass-shd:0 18px 44px -20px rgba(var(--shadow-rgb),.3);
  --glass-strong:rgba(255,253,248,.78);
}
:root[data-theme="dark"]{
    --bg:#212327; --bg2:#2b2d31; --panel:#2b2d31;
    --line:rgba(255,255,255,.09); --line-bright:rgba(255,255,255,.20);
    --text:#f4f4f5; --muted:#a9adb5; --muted2:#989ea9;
    /* no gold in dark: the accent is light, not warm. --gold* are kept as
       names so every existing rule keeps working, remapped to neutrals. */
    --gold:#e8e9ec; --gold-bright:#ffffff; --gold-dim:rgba(255,255,255,.34);
    --ok:#8fe09f; --ok-dot:#8fe09f;
    /* --maroon was never remapped for dark: #8a3838 on #2b2d31 is 1.78:1,
       which made the form error message and every "we never do this" ✕
       marker effectively invisible. */
    --maroon:#ef9a9a;
    --focus:#ffffff;
    --hdr:rgba(33,35,39,.82);
    /* charcoal has no warmth to reinforce: the decorative layer goes neutral,
       with a faint cool counterpoint instead of the maroon */
    --tint-rgb:255,255,255;
    --tint-hi-rgb:255,255,255;
    --tint-2-rgb:150,160,180;
    --shadow-rgb:0,0,0;
    /* glass on charcoal is a cool light tint, not a warm one */
    --glass:rgba(255,255,255,.045);
    --glass-brd:rgba(255,255,255,.10);
    --glass-hi:rgba(255,255,255,.14);
    --glass-shd:0 20px 50px -24px rgba(0,0,0,.8);
    --glass-blur:blur(18px) saturate(120%);
    --glass-strong:rgba(255,255,255,.07);
}


/* ── base typography, all pages ────────────── */
/* The hidden attribute, made to mean what it says.
   The UA stylesheet already says [hidden]{display:none}, and it loses to any
   author rule that sets display on the same element — not on specificity, on
   cascade ORIGIN, a category the author sheet always wins. products.html's
   filter set the attribute on exactly the right cards while .pcard{display:flex}
   kept all five on screen: the page read "Showing 1 product in research" over
   five visible cards, with thirteen focusable controls inside the four that
   were supposedly gone.
   !important is load-bearing here, not a shortcut. Without it this rule is
   (0,1,0) and every page's own <style> loads AFTER this file, so a plain
   .pcard{display:flex} would beat it on source order alone and the guard would
   read as protection while providing none.
   Safe because nothing on this site tries to SHOW a hidden element: every
   [hidden] rule in the tree sets display:none, and this simply stops each one
   from having to. The class-scoped guards (.lb, .mm, .pcard) are left in place
   as belt and braces — redundant now, but they cost nothing and they keep
   working if this line is ever moved. */
[hidden]{display:none!important}

html{-webkit-text-size-adjust:100%}
body{font:var(--t-body);letter-spacing:var(--ls-body);text-rendering:optimizeLegibility}
/* text-wrap:pretty is inherited, so pages that set it on a wrapper would
   otherwise hand it down to their headings; balance is the right mode there. */
h1,h2,h3{text-wrap:balance}

/* ── skip link ─────────────────────────────── */
.skip-link{position:absolute;left:-9999px;top:0;z-index:400;
  background:var(--panel);color:var(--gold);border:1px solid var(--gold-dim);
  border-radius:10px;padding:11px 18px;text-decoration:none;
  font:var(--t-button);letter-spacing:var(--ls-button)}
.skip-link:focus{left:14px;top:14px}

/* ── keyboard focus, site-wide ─────────────── */
a:focus-visible,button:focus-visible,input:focus-visible,textarea:focus-visible,
select:focus-visible,[tabindex]:focus-visible{
  outline:2px solid var(--focus);outline-offset:3px;border-radius:4px}
.card:focus-visible,.btn-preview:focus-visible,.hero-btn:focus-visible,
.cta:focus-visible,.shot:focus-visible{outline:2px solid var(--focus);outline-offset:4px}

/* ── shared header (secondary pages) ───────── */
/* header.site-hdr beats a page's bare `header{}` rule on specificity,
   whatever the source order */
header.site-hdr{position:sticky;top:0;z-index:60;backdrop-filter:blur(14px);
  background:var(--hdr);border-bottom:1px solid var(--line);
  padding:0;text-align:left}
.site-hdr .hdr-inner{max-width:1120px;margin:0 auto;padding:15px 24px;
  display:flex;align-items:center;justify-content:space-between;gap:16px}
.site-hdr .word{font:500 1.1875rem/1.2 var(--font-display);letter-spacing:-.01em;
  color:var(--text);text-decoration:none;display:flex;align-items:center;gap:11px}
.site-hdr .mark-img{width:32px;height:32px;border-radius:9px;flex-shrink:0;object-fit:cover;
  box-shadow:0 0 0 1px var(--line),0 6px 18px -8px rgba(var(--shadow-rgb),.4);
  transition:transform .4s cubic-bezier(.34,1.56,.64,1)}
.site-hdr .word:hover .mark-img{transform:scale(1.08) rotate(-6deg)}
/* not scoped to .site-hdr — the homepage header uses this too */
.hdr-right{display:flex;align-items:center;gap:8px;flex-shrink:0}
.site-hdr nav{display:flex;gap:26px;align-items:center}
.site-hdr nav a{position:relative;color:var(--muted);text-decoration:none;
  font:500 .875rem/1 var(--font-interface);letter-spacing:-.004em;
  transition:color .2s;padding-bottom:2px}
.site-hdr nav a::after{content:'';position:absolute;left:0;bottom:0;width:0;height:1px;
  background:var(--gold-bright);transition:width .25s cubic-bezier(.16,.8,.28,1)}
.site-hdr nav a:hover{color:var(--gold-bright)}
.site-hdr nav a:hover::after{width:100%}
/* [aria-current] unqualified: "page" marks the exact page, "true" marks the
   section a product page sits inside. Both should read as current. */
.site-hdr nav a[aria-current]{color:var(--gold)}
.site-hdr nav a[aria-current]::after{width:100%;background:var(--gold-dim)}

/* ── nav + mobile menu (all pages) ─────────── */
.nav-toggle{display:none;width:38px;height:38px;border-radius:50%;border:1px solid var(--line);
  background:transparent;color:var(--muted);cursor:pointer;align-items:center;justify-content:center;
  transition:color .25s,border-color .25s}
.nav-toggle:hover{color:var(--gold);border-color:var(--gold-dim)}
.nav-toggle .bars{display:block}.nav-toggle .ex{display:none}
.nav-toggle[aria-expanded="true"] .bars{display:none}
.nav-toggle[aria-expanded="true"] .ex{display:block}

/* visibility, not just opacity — the same fix .contact-pop carries further down,
   for the same reason, and it should have been applied to both at once. Neither
   opacity:0 nor pointer-events:none takes an element out of the tab order, so
   the CLOSED menu kept nine focusable links live (4 nav + 4 chips + the mail
   link) on all 15 pages that carry it, at every width up to 820px. It sits
   after </footer> and has no `top` until JS opens it, so those nine tab stops
   landed on a fixed-position box far below the fold that the page cannot scroll
   into view — and being role="dialog" aria-modal="true", a screen reader
   entering it can treat the rest of the page as inert while the visitor sees
   nothing happen at all.

   max-height and overflow because a fixed box is excluded from the viewport's
   scrollable overflow: on a landscape phone the sheet renders 566px tall from
   top:66px on a 375px-tall screen, leaving the fourth nav item, all four product
   chips and the support address permanently off-screen with no way to reach
   them. site.js sets the exact height from the header rect it already measures. */
.mobile-menu{position:fixed;left:12px;right:12px;z-index:250;
  background:var(--panel);border:1px solid var(--line-bright);border-radius:16px;
  box-shadow:0 26px 60px -22px rgba(var(--shadow-rgb),.5);padding:10px;
  display:flex;flex-direction:column;
  overflow-y:auto;overscroll-behavior:contain;
  opacity:0;transform:translateY(-10px) scale(.98);pointer-events:none;visibility:hidden;
  transition:opacity .24s cubic-bezier(.16,.8,.28,1),transform .24s cubic-bezier(.16,.8,.28,1),
             visibility 0s linear .24s}
.mobile-menu.show{opacity:1;transform:none;pointer-events:auto;visibility:visible;
  transition-delay:0s}
.mobile-menu a{color:var(--text);text-decoration:none;
  font:550 clamp(1.25rem,4.6vw,1.4375rem)/1.3 var(--font-interface);letter-spacing:-.024em;
  padding:11px 15px;border-radius:11px;transition:background .2s,color .2s}
.mobile-menu a:hover,.mobile-menu a:focus-visible{background:rgba(var(--tint-rgb),.12);color:var(--gold-bright)}
.mobile-menu a[aria-current]{color:var(--gold)}
.mobile-menu .mm-label{font:var(--t-eyebrow);letter-spacing:var(--ls-eyebrow);
  text-transform:uppercase;color:var(--muted2);padding:14px 15px 5px}

/* ── touch targets ─────────────────────────────
   The 38px ring and the 32px wordmark are deliberate at this scale, so the
   hit area is grown to 44px with a pseudo-element instead of the box itself —
   nothing moves, the finger just gets more room. Coarse pointers only. */
@media(pointer:coarse){
  .nav-toggle,.theme-toggle{position:relative}
  /* an img is a replaced element and grows no ::after, so the wordmark link
     takes the height directly — it is flex-centred, so nothing shifts */
  .nav-toggle::after,.theme-toggle::after{
    content:'';position:absolute;top:50%;left:50%;translate:-50% -50%;
    width:44px;height:44px}
  /* not .site-hdr .word — the homepage's header carries no class, so scoping
     it that way left the wordmark at 34px on the one page most people land on */
  .word{min-height:44px}
}

@media(max-width:820px){
  .nav-toggle{display:flex}
  .site-hdr nav{display:none}
}
@media(min-width:821px){
  .mobile-menu{display:none}
}

/* ── small gold text, light theme ─────────────
   Status chips are 11px/600 and the current nav link 14px/500, so 4.5:1
   applies to all of them. On the light page --gold (#a97a1f) measures 3.15:1
   and --ok (#3b7a4f) 4.23:1. Only the TEXT is darkened here: the dashed ring,
   the status dot, the nav underline, and every other use of --gold and --ok
   are left exactly as they were, and no token is redefined. The dark theme
   already passes at 6.47:1, so it keeps the plain tokens below.
   Chips use two class names (0,2,0) to beat each page's own .plat-wip; the
   nav rules match the originals' specificity and win on source order. */
.skip-link{color:#7d5a0e}                      /* 4.76:1 on --bg2, the darkest light ground — the
   first control a keyboard user meets, and var(--gold) put it at 3.5:1 */
.plat.plat-wip{color:#7d5a0e}                  /* 4.76:1 on --bg2  */
.plat.plat-live{color:#33693f}                 /* 4.92:1 on --bg2  */
.site-hdr nav a[aria-current]{color:#7d5a0e}   /* 4.76:1 on --bg2  */
.mobile-menu a[aria-current]{color:#7d5a0e}    /* --panel is lighter than --bg */
@media(prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .skip-link{color:var(--gold)}
  :root:not([data-theme="light"]) .plat.plat-wip{color:var(--gold)}
  :root:not([data-theme="light"]) .plat.plat-live{color:var(--ok)}
  :root:not([data-theme="light"]) .site-hdr nav a[aria-current]{color:var(--gold)}
  :root:not([data-theme="light"]) .mobile-menu a[aria-current]{color:var(--gold)}
}
:root[data-theme="dark"] .skip-link{color:var(--gold)}
:root[data-theme="dark"] .plat.plat-wip{color:var(--gold)}
:root[data-theme="dark"] .plat.plat-live{color:var(--ok)}
:root[data-theme="dark"] .site-hdr nav a[aria-current]{color:var(--gold)}
:root[data-theme="dark"] .mobile-menu a[aria-current]{color:var(--gold)}

/* Same defect, same treatment, three more places. Each page's own <style>
   loads after this file, so these selectors are one step more specific than
   the originals rather than relying on order: a.inline:any-link beats
   a.inline, a.home-link beats .home-link, .log .entry time beats .entry time.
   --muted2 misses in BOTH themes (3.42 light / 4.40 dark), so the back link
   moves to --muted outright and needs no dark override. */
a.inline:any-link{color:#7d5a0e}   /* 4.76:1 on --bg2, was --gold at 2.90 */
a.home-link{color:var(--muted)}    /* 5.47:1 light, 7.47:1 dark */
.log .entry time{color:#7d5a0e}
@media(prefers-color-scheme:dark){
  :root:not([data-theme="light"]) a.inline:any-link{color:var(--gold)}
  :root:not([data-theme="light"]) .log .entry time{color:var(--gold)}
}
:root[data-theme="dark"] a.inline:any-link{color:var(--gold)}
:root[data-theme="dark"] .log .entry time{color:var(--gold)}

/* The last of the small-gold/--muted2 text. Same rule as above: one step more
   specific than each page's own <style>, which loads after this file.
   .shots .shot span is deliberately NOT plain .shot span — the homepage
   gallery's captions are also .shot span, but they sit on a dark overlay
   where --muted2 is correct and --muted would be unreadable. */
.wrap .kicker,header .kicker{color:#7d5a0e}      /* 4.76:1 on --bg2, was 3.15:1 */
.shots .shot span{color:var(--muted)}            /* strip captions, not the overlay's */
.log .log-year{color:var(--muted)}               /* --muted2: 3.42 light, 4.40 dark */
.wrap footer{color:var(--muted)}                 /* every page's copyright line */
.wrap .sub{color:var(--muted)}                   /* legal + story page subtitles */
@media(prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .wrap .kicker,
  :root:not([data-theme="light"]) header .kicker{color:var(--gold)}
}
:root[data-theme="dark"] .wrap .kicker,
:root[data-theme="dark"] header .kicker{color:var(--gold)}

/* ── the product grid, and the footer pair the earlier passes walked past ──
   The rule above was right; the LIST was incomplete, twice. Both earlier passes
   enumerated selectors by hand, so they fixed what they could name and left
   everything they could not. These were found the other way — by measuring
   every rendered element's computed colour against its fully composited
   background and reading the failures back out — which is the only method that
   does not depend on already knowing where to look.

   .foot-mail and .foot-live are NOT products-only: they are in the shared
   footer on fifteen pages. a.inline was already fixed above and passes at
   4.70:1, which is exactly why they were missed — the footer's own mail link
   never carried that class. The rest is the product grid, which only
   products.html has.

   --muted2 items move to --muted outright and need no dark override: --muted
   passes in both themes, and --muted2 misses in both (the note above records
   3.42 light / 4.40 dark). The --gold and --ok items are darkened for light and
   handed their tokens back in dark, where --gold is #e8e9ec and --ok #8fe09f.
   Every selector carries one extra type or class because products.html's own
   <style> loads after this file — specificity, not source order.

   Ratios are quoted against --bg2 (#ecdfc4), because that and not --bg is the
   darkest light ground on the site: index.html's own .foot uses it. Quoting
   against --bg is exactly how the first attempt shipped #856010 and #357045,
   which measure 4.70 and 4.86 there but only 4.33 and 4.48 on --bg2 — both
   under AA, in a footer that is on every page. They are now #7d5a0e and
   #33693f, which clear --bg2 at 4.76 and 4.92; anything clearing --bg2 clears
   --bg and --panel as well, so one number covers every light surface. */
a.foot-mail{color:#7d5a0e}               /* 4.76:1 on --bg2  — was 2.90 */
span.foot-live{color:#33693f}            /* 4.92:1 on --bg2  — was 4.23 */
button.pfilter{color:var(--muted)}       /* 5.47:1           — was 3.81 */
p.pcount{color:var(--muted)}             /* 5.47:1           — was 3.42 */
a.pcard-go{color:#7d5a0e}                /* 4.76:1 or better — was 3.62 */
a.pcard-2nd{color:var(--muted)}          /* 5.47:1           — was 3.93 */
/* opacity, not just colour: products.html's own <style> fades .stamp to .75,
   which the colour above does not survive. #7d5a0e measures 4.76 on --bg2 at
   full strength and 3.03 once faded, so the fix that set the colour was being
   undone by a line in another file that nothing connected to contrast. Dark
   keeps the fade below — it clears AA there either way. */
span.stamp{color:#7d5a0e;opacity:1}      /* 4.76:1 on --bg2  — was 3.03 faded */
span.pchip{color:var(--muted)}           /* 5.47:1           — was 3.93 */
span.pchip.pchip-wip{color:#7d5a0e}      /* beats span.pchip — was 3.62 */
p.pcard-plat > span{color:var(--muted)}  /* 5.47:1           — was 4.01 */
@media(prefers-color-scheme:dark){
  :root:not([data-theme="light"]) a.foot-mail{color:var(--gold)}
  :root:not([data-theme="light"]) span.foot-live{color:var(--ok)}
  :root:not([data-theme="light"]) a.pcard-go{color:var(--gold)}
  :root:not([data-theme="light"]) span.stamp{color:var(--gold);opacity:.75}
  :root:not([data-theme="light"]) span.pchip.pchip-wip{color:var(--gold)}
}
/* ── the fourth sweep, and the first one done by measurement alone ──
   Every page scanned in light, every rendered element compared against its
   composited background, nothing enumerated. That turned up four more classes
   no previous pass had named, on eight pages:

     .wrap a:not([class])  --gold  3.46:1  privacy(5) terms(6) account-deletion(2)
                                           and every other privacy-*, which all
                                           set a{color:var(--gold)} inline
     p.qualifier           --muted2 3.42:1 habit-kramedha, medhakeli(2), fylunor
     p.fb-eyebrow          --gold  3.62:1  share-an-idea
     .short span.lbl       --gold  3.22:1  terms, and the privacy-* short forms

   The link one is the whole lesson in miniature: those pages declare
   `a{color:var(--gold)}` in their own <style>, a bare type selector at (0,0,1),
   so nothing class-based in this file could ever have been the problem — it was
   simply never written, because no one grepped for an unclassed <a>.
   `:not([class])` is deliberate: it takes exactly the prose links that have no
   rule of their own and leaves .foot-mail, a.inline and every other classed
   link to the rules above, which already pass. */
.wrap a:not([class]){color:#7d5a0e}   /* 4.76:1 on --bg2 — was 3.46 */
p.qualifier{color:var(--muted)}       /* 5.04:1 on --bg2 — was 3.42 */
p.fb-eyebrow{color:#7d5a0e}           /* 4.76:1 on --bg2 — was 3.62 */
.short span.lbl{color:#7d5a0e}        /* 4.76:1 on --bg2 — was 3.22 */
@media(prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .wrap a:not([class]){color:var(--gold)}
  :root:not([data-theme="light"]) p.fb-eyebrow{color:var(--gold)}
  :root:not([data-theme="light"]) .short span.lbl{color:var(--gold)}
}
:root[data-theme="dark"] .wrap a:not([class]){color:var(--gold)}
:root[data-theme="dark"] p.fb-eyebrow{color:var(--gold)}
:root[data-theme="dark"] .short span.lbl{color:var(--gold)}

:root[data-theme="dark"] a.foot-mail{color:var(--gold)}
:root[data-theme="dark"] span.foot-live{color:var(--ok)}
:root[data-theme="dark"] a.pcard-go{color:var(--gold)}
:root[data-theme="dark"] span.stamp{color:var(--gold);opacity:.75}
:root[data-theme="dark"] span.pchip.pchip-wip{color:var(--gold)}

/* ── the fifth sweep, run on the phone against the deployed site ──
   Backgrounds came from CDP `CSS.getBackgroundColors` — the same resolution the
   DevTools contrast readout uses — instead of a hand-rolled composite, and the
   page list was taken from tools/check-site.py so nothing could be left out.

   That last part is the finding. 404.html is named in none of the four passes
   above and carried three of these four failures: it is the one page nobody
   navigates to on purpose, so every enumeration by hand had missed it.

     div.code              --gold   2.80:1  404
     div.where-label       --muted2 3.04:1  404
     div.where a span      --muted2 3.76:1  404, the three status captions
     ol.fb-steps-list span --gold   3.52:1  share-an-idea

   Type selectors, because unlike the passes above these rules live in the
   pages' own <style> block and in home.css, both of which load AFTER this file
   — a bare class here would tie on specificity and lose. `.where a:hover span`
   stays (0,2,2) and still outranks `div.where a span` at (0,1,3), so the hover
   state is untouched.

   Ratios are against the background each element actually sits on: on 404 that
   is --bg under its warm radial wash, #e9dbbf, slightly darker than --bg2, so
   these read a little lower than the 4.76 quoted above for the same ink.

   Deliberately NOT changed, both verified rather than assumed: products' ◇ is
   aria-hidden decoration, and share-an-idea's .fb-pnum "on" numeral is dark ink
   on a gold fill, which already passes. */
div.code{color:#7d5a0e}                /* 4.59:1 — was 2.80 */
div.where-label{color:var(--muted)}    /* 4.86:1 — was 3.04 */
div.where a span{color:var(--muted)}   /* 6.02:1 — was 3.76 */
ol.fb-steps-list span{color:#7d5a0e}   /* 5.79:1 — was 3.52 */
@media(prefers-color-scheme:dark){
  :root:not([data-theme="light"]) div.code{color:var(--gold)}
  :root:not([data-theme="light"]) div.where-label{color:var(--muted2)}
  :root:not([data-theme="light"]) div.where a span{color:var(--muted2)}
  :root:not([data-theme="light"]) ol.fb-steps-list span{color:var(--gold)}
  /* The li text, not just the numeral. --muted is #a9adb5 in dark and
     measures 4.26 against the feedback card, which is lighter than the page
     behind it -- the one place on the site where --muted lands under AA.
     #b4b8bf clears it at 4.76. Eleven points brighter: not --text, which
     would make a deliberately quiet list as loud as the body copy. */
  :root:not([data-theme="light"]) ol.fb-steps-list li{color:#b4b8bf}
}
:root[data-theme="dark"] div.code{color:var(--gold)}
:root[data-theme="dark"] div.where-label{color:var(--muted2)}
:root[data-theme="dark"] div.where a span{color:var(--muted2)}
:root[data-theme="dark"] ol.fb-steps-list span{color:var(--gold)}
:root[data-theme="dark"] ol.fb-steps-list li{color:#b4b8bf}   /* 4.76 — was 4.26 */

/* ── "More from the studio" (product pages) ──
   Lives here rather than in each page's <style> so adding a fifth product
   means editing markup only, and every product page stays in step. */
.more-products{margin:64px 0 0;padding:26px 0 0;border-top:1px solid var(--line)}
/* --muted, not --muted2: at this size --muted2 measures 3.4:1 and misses AA */
.more-products .mp-label{font:var(--t-eyebrow);letter-spacing:var(--ls-eyebrow);
  text-transform:uppercase;color:var(--muted);margin:0 0 16px}
.mp-grid{list-style:none;padding:0;margin:0;
  display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
@media(max-width:620px){.mp-grid{grid-template-columns:1fr}}
.mp-grid a{display:flex;gap:13px;align-items:flex-start;height:100%;
  text-decoration:none;color:inherit;background:var(--panel);
  border:1px solid var(--line);border-radius:14px;padding:14px;
  transition:border-color .22s,transform .22s cubic-bezier(.16,.8,.28,1),box-shadow .22s}
.mp-grid a:hover{border-color:var(--gold-dim);transform:translateY(-2px);
  box-shadow:0 14px 30px -18px rgba(var(--shadow-rgb),.55)}
.mp-grid img{width:38px;height:38px;border-radius:10px;flex-shrink:0}
.mp-grid b{display:block;font-weight:600;color:var(--text);letter-spacing:-.012em}
.mp-grid .mp-note{display:block;margin-top:3px;color:var(--muted);
  font-size:13.5px;line-height:1.5}
.mp-grid .mp-stat{display:block;margin-top:7px;font:var(--t-status);
  letter-spacing:var(--ls-status);text-transform:uppercase;color:var(--muted)}
.mp-grid .mp-stat.is-live{color:var(--ok)}
@media(prefers-reduced-motion:reduce){.mp-grid a:hover{transform:none}}

/* ── theme toggle ──────────────────────────── */
.theme-toggle{width:38px;height:38px;border-radius:50%;border:1px solid var(--line);background:transparent;
  color:var(--muted);cursor:pointer;display:flex;align-items:center;justify-content:center;
  transition:color .25s,border-color .25s,transform .3s cubic-bezier(.34,1.56,.64,1)}
.theme-toggle:hover{color:var(--gold);border-color:var(--gold-dim);transform:rotate(20deg)}
.theme-toggle .moon{display:none}
:root[data-theme="dark"] .theme-toggle .sun{display:none}
:root[data-theme="dark"] .theme-toggle .moon{display:block}
@media(prefers-color-scheme:dark){
  :root:not([data-theme]) .theme-toggle .sun{display:none}
  :root:not([data-theme]) .theme-toggle .moon{display:block}
}

/* ── contact popover ───────────────────────── */
.contact-pop{position:fixed;top:0;left:0;z-index:300;width:max-content;max-width:calc(100vw - 24px);
  background:
    linear-gradient(160deg,rgba(var(--tint-rgb),.10),transparent 42%),
    var(--panel);
  border:1px solid var(--line-bright);border-radius:15px;padding:14px;
  box-shadow:0 24px 54px -20px rgba(var(--shadow-rgb),.5);
  display:flex;flex-direction:column;gap:9px;
  /* visibility, not just opacity: neither opacity:0 nor pointer-events:none
     takes an element out of the tab order, so the closed popover's mailto link
     stayed focusable on all 15 pages that carry it — an invisible tab stop the
     screen reader could not announce, because aria-hidden had pruned it.
     Delayed so the close transition still plays. */
  opacity:0;transform:translateY(-6px) scale(.97);pointer-events:none;transform-origin:top center;
  visibility:hidden;
  transition:opacity .22s cubic-bezier(.16,.8,.28,1),transform .22s cubic-bezier(.16,.8,.28,1),
             visibility 0s linear .22s}
.contact-pop.flip{transform:translateY(6px) scale(.97);transform-origin:bottom center}
.contact-pop.show{opacity:1;transform:none;pointer-events:auto;visibility:visible;
  transition-delay:0s}
.contact-tail{position:absolute;width:11px;height:11px;background:var(--panel);
  border-left:1px solid var(--line-bright);border-top:1px solid var(--line-bright);
  top:-6px;left:var(--tail-x,50%);margin-left:-6px;transform:rotate(45deg);border-radius:2px 0 0 0}
.contact-pop.flip .contact-tail{top:auto;bottom:-6px;transform:rotate(225deg)}
.contact-pop-label{font:var(--t-eyebrow);letter-spacing:var(--ls-eyebrow);
  text-transform:uppercase;color:var(--muted2);padding:0 3px}
.contact-mail{display:flex;align-items:center;gap:10px;text-decoration:none;
  font:500 clamp(.875rem,3.2vw,.9375rem)/1.4 var(--font-interface);letter-spacing:-.004em;
  color:var(--gold-bright);
  padding:11px 13px;border-radius:11px;background:rgba(var(--tint-rgb),.09);
  border:1px solid transparent;
  transition:background .22s,border-color .22s,transform .22s cubic-bezier(.16,.8,.28,1)}
.contact-mail:hover,.contact-mail:focus-visible{background:rgba(var(--tint-rgb),.17);border-color:var(--gold-dim);transform:translateX(2px)}
.cm-icon{flex-shrink:0;opacity:.75}
.cm-text{white-space:nowrap}
.cm-arw{flex-shrink:0;margin-left:6px;opacity:0;transform:translateX(-4px);
  transition:opacity .22s,transform .22s cubic-bezier(.16,.8,.28,1)}
.contact-mail:hover .cm-arw,.contact-mail:focus-visible .cm-arw{opacity:1;transform:none}

/* ── reveal on scroll ──────────────────────── */
.reveal{opacity:0;transform:translateY(26px);
  transition:opacity .8s cubic-bezier(.16,.8,.28,1),transform .8s cubic-bezier(.16,.8,.28,1)}
.reveal.in{opacity:1;transform:translateY(0)}
.reveal-1{transition-delay:.06s}.reveal-2{transition-delay:.14s}.reveal-3{transition-delay:.22s}

/* ── print ─────────────────────────────────── */
@media print{
  :root{--bg:#fff;--bg2:#fff;--panel:#fff;--text:#000;--muted:#333;--muted2:#555;
    --gold:#6b5416;--gold-bright:#6b5416;--gold-dim:#8a8a8a;
    --line:#ccc;--line-bright:#999;--hdr:#fff}
  body{background:#fff!important;color:#000!important;font-size:11pt}
  .site-hdr,header,nav,.nav-toggle,.theme-toggle,.mobile-menu,.contact-pop,.skip-link,
  .to-top,.grain,.cursor-dot,.scroll-progress,.ticker-wrap,.aurora,.orbit-svg,
  .home-link,.btn,.cta,.card-cta,.btn-preview,.gallery,.lightbox,#ideas{display:none!important}
  .reveal{opacity:1!important;transform:none!important}
  a{color:#000;text-decoration:underline}
  /* spell out link targets, since a printed page can't be clicked */
  main a[href^="http"]::after,.wrap a[href^="http"]::after{content:" (" attr(href) ")";font-size:9pt;color:#555}
  h1,h2,h3{page-break-after:avoid}
  li,.entry,.feat li{page-break-inside:avoid}
  img{max-width:100%!important}
}

@media(prefers-reduced-motion:reduce){
  /* global catch-all so every page loading this sheet is covered, not just the shared chrome */
  *,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;
    transition-duration:.01ms!important;scroll-behavior:auto!important}
  .reveal{opacity:1;transform:none}
  .theme-toggle:hover{transform:none}
}

/* ── press feedback, site-wide ───────────────
   A control that moves under the finger feels connected to the tap. Gated on
   fine pointers with hover, so touch devices never get a stuck hover state. */
@media (hover:hover) and (pointer:fine){
  .btn,.pcard-go,.pcard-2nd,.btn-preview,.cta,.dl-alt,.pfilter{
    transition:transform var(--t-press) var(--e-out),
               color var(--t-ui) var(--e-out),
               border-color var(--t-ui) var(--e-out),
               background var(--t-ui) var(--e-out)}
  .btn:active,.pcard-go:active,.pcard-2nd:active,.btn-preview:active,
  .cta:active,.dl-alt:active,.pfilter:active{transform:scale(.97)}
}

/* ── editorial voice ─────────────────────────
   The serif is allowed here and nowhere else: pull quotes and the philosophy
   passages. Everything structural is set in the grotesk. */
blockquote,.story blockquote,.pull-quote{
  font:var(--t-quote);letter-spacing:var(--ls-quote);font-style:italic;
  color:var(--text);max-width:var(--measure-tight)}

/* ══ Phase 5 — global shell ═══════════════════════════════════════════ */

/* ── nav: one indicator that travels ─────────
   A single rail slides between items rather than each link owning a border,
   so the eye tracks one object instead of watching four things blink. It is
   driven by custom properties set in site.js; with no JS it simply never
   shows, and the per-link underline still marks the current page. */
.site-hdr nav,header nav{position:relative}
.nav-rail{position:absolute;left:0;top:50%;height:32px;width:var(--rail-w,0);
  transform:translate(var(--rail-x,0),-50%);border-radius:var(--r-sm);
  background:var(--glass-strong);border:1px solid var(--glass-brd);
  opacity:0;pointer-events:none;z-index:0;
  transition:transform var(--t-ui) var(--e-inout),width var(--t-ui) var(--e-inout),
             opacity var(--t-quick) var(--e-out)}
.nav-rail.on{opacity:1}
nav a{position:relative;z-index:1}

/* the header tightens on scroll — once, subtly, not on every pixel */
header.site-hdr,body > header{transition:padding var(--t-panel) var(--e-inout),
  background var(--t-panel) var(--e-inout)}
.hdr-inner{transition:padding var(--t-panel) var(--e-inout)}
.is-scrolled .hdr-inner{padding-top:10px;padding-bottom:10px}

/* ── mobile menu as a sheet ──────────────────
   Full-width, anchored to the bottom on phones so it opens under the thumb
   rather than out of reach at the top. */
.mobile-menu{gap:var(--sp-5);padding:var(--sp-5) var(--sp-5) var(--sp-6)}
.mm-head{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4);
  padding-bottom:var(--sp-4);border-bottom:1px solid var(--line)}
.mm-label{font:var(--t-eyebrow);letter-spacing:var(--ls-eyebrow);
  text-transform:uppercase;color:var(--muted2);padding:0}
.mm-status{display:inline-flex;align-items:center;gap:7px;color:var(--muted);
  font:var(--t-status);letter-spacing:var(--ls-status);text-transform:uppercase}
.mm-status i{width:6px;height:6px;border-radius:50%;background:var(--ok-dot);
  box-shadow:0 0 7px var(--ok-dot);flex-shrink:0}
.mm-nav{display:flex;flex-direction:column}
.mm-nav a{display:flex;flex-direction:column;gap:2px;padding:var(--sp-3) var(--sp-2);
  border-radius:var(--r-md);text-decoration:none;color:var(--text)}
/* :not(.w):not(.wg) — descent.js splits .mm into word spans, which match
   this at (0,1,2) and beat .mm-nav a, restyling every menu label. */
.mm-nav a span:not(.w):not(.wg){font:600 1.25rem/1.25 var(--font-interface);letter-spacing:-.02em}
.mm-nav a em{font-style:normal;color:var(--muted2);
  font:var(--t-body-sm);letter-spacing:var(--ls-body-sm)}
.mm-cap{display:block;margin-bottom:var(--sp-2);color:var(--muted2);
  font:var(--t-eyebrow);letter-spacing:var(--ls-eyebrow);text-transform:uppercase}
.mm-chips{display:flex;flex-wrap:wrap;gap:var(--sp-2)}
.mm-chips a{padding:9px 13px;border-radius:var(--r-pill);text-decoration:none;
  color:var(--muted);border:1px solid var(--line);font:var(--t-body-sm)}
.mm-mail{display:inline-flex;align-items:center;gap:9px;min-height:44px;
  color:var(--muted);text-decoration:none;font:var(--t-body-sm)}
@media (hover:hover) and (pointer:fine){
  .mm-nav a:hover{background:var(--glass);color:var(--gold-bright)}
  .mm-chips a:hover,.mm-mail:hover{color:var(--text);border-color:var(--gold-dim)}
}

/* ── footer ──────────────────────────────────
   The wordmark is the ending, set in the display face and clipped to the
   container so it reads as a printed mark rather than a heading. */
.site-foot{margin-top:var(--sp-9);padding:var(--sp-8) 0 var(--sp-6);
  border-top:1px solid var(--line)}
/* The footer is one shared component, so it owns its own width. Without this it
   inherited each page's reading column - 760px on legal pages, 680px on
   editorial - which wrapped "Why we build this way" and "Delete your account"
   mid-phrase and truncated the LYVORIAN watermark to "LYVORI". 1120px is the
   width the approved products page gives it. */
.site-foot .wrap{max-width:1120px}
.foot-top{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.4fr);
  gap:var(--sp-7);align-items:start}
.foot-line{margin:0 0 var(--sp-3);color:var(--text);max-width:26ch;
  font:var(--t-body-lg);letter-spacing:var(--ls-body-lg)}
.foot-mail{color:var(--gold);text-decoration:none;font:var(--t-body-sm);
  border-bottom:1px solid var(--line-bright);padding-bottom:2px}
.foot-cols{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--sp-5)}
.foot-cols div{display:flex;flex-direction:column;gap:10px}
.foot-cap{color:var(--muted);font:var(--t-eyebrow);
  letter-spacing:var(--ls-eyebrow);text-transform:uppercase;margin-bottom:2px}
.foot-cols a{color:var(--muted);text-decoration:none;font:var(--t-body-sm);
  width:fit-content}
.foot-mark{margin:var(--sp-8) 0 var(--sp-5);color:var(--text);opacity:.07;
  font-family:var(--font-display);font-weight:500;line-height:.82;
  font-size:clamp(4rem,15.5vw,13rem);letter-spacing:-.04em;
  text-align:center;user-select:none;overflow:hidden}
.foot-base{display:flex;flex-wrap:wrap;gap:var(--sp-3) var(--sp-5);
  align-items:center;justify-content:space-between;
  padding-top:var(--sp-4);border-top:1px solid var(--line);
  color:var(--muted);font:var(--t-caption);letter-spacing:var(--ls-caption)}
.foot-live{display:inline-flex;align-items:center;gap:7px;color:var(--ok)}
.foot-live i{width:6px;height:6px;border-radius:50%;background:var(--ok-dot);
  box-shadow:0 0 7px var(--ok-dot)}
@media (hover:hover) and (pointer:fine){
  .foot-cols a:hover,.foot-mail:hover{color:var(--gold-bright)}
}
@media(max-width:940px){
  .foot-top{grid-template-columns:1fr;gap:var(--sp-6)}
  .foot-cols{grid-template-columns:repeat(2,1fr)}
}
@media(max-width:480px){
  .foot-cols{grid-template-columns:1fr;gap:var(--sp-5)}
}
@media(prefers-reduced-motion:reduce){
  .nav-rail{transition:opacity var(--t-quick) linear}
}
