/* ============================================================
   Human Labb: shared styles for secondary pages
   (index.html / about.html carry their own inline CSS)
   ============================================================ */
:root{
  /* dark-first: Ink is the ground, chrome is the voice */
  --sand:#0D0D0F; --sand-2:#131318; --paper:#1A1A20;
  --ink:#F2EDE4; --ink-2:#C6C6CC; --muted:#9A9AA0;
  --rust:#C56A1C; --rust-fill:#B46118; --rust-ink:#E08A34;
  --rust-dk:#A45415; --rust-soft:#E9BC7C; --rust-on-ink:#E08A34;
  --echo-caps-light:#AAAAA9; --echo-caps-dark:#827F7B;
  --line:rgba(255,255,255,.09); --obsidian:#131318;
  --card:rgba(255,255,255,.035); --card-line:rgba(255,255,255,.07);
  /* Opaque card base for the scattered quote piles. Those cards overlap, so they can't
     use the translucent --card without the quote underneath bleeding through, but the
     value must still be themed, or bone mode gets a black card with black text. */
  --card-solid:#191714;
  --nav-bg:rgba(13,13,15,.72); --echo-b:var(--echo-caps-dark);
  --ghost-line:rgba(255,255,255,.32); --ghost-hover:#fff;
  --max:1200px; --r:18px;
}
[data-theme="bone"]{
  --sand:#F2EDE4; --sand-2:#EAE3D5; --paper:#FBF9F5;
  --ink:#14130F; --ink-2:#3A382F; --muted:#6B675C;
  --rust-ink:#A45415;
  --line:rgba(20,19,15,.12);
  --card:rgba(255,255,255,.62); --card-line:rgba(20,19,15,.08);
  --card-solid:#FBF9F5;   /* paper, not near-black */
  --nav-bg:rgba(242,237,228,.84); --echo-b:var(--echo-caps-light);
  --ghost-line:rgba(20,19,15,.4); --ghost-hover:#14130F;
}
*{box-sizing:border-box;margin:0;padding:0}
[hidden]{display:none!important}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%;scroll-padding-top:84px}
body{background:var(--sand);color:var(--ink-2);
  font:400 17px/1.65 "Instrument Sans",system-ui,-apple-system,sans-serif;
  -webkit-font-smoothing:antialiased}
h1,h2,h3,h4{font-family:"Instrument Sans",sans-serif;letter-spacing:-.022em;line-height:1.1;
  font-weight:600;color:var(--ink)}
.ask{font-family:"Cormorant Garamond",Georgia,serif;font-style:italic;font-weight:600;letter-spacing:-.005em}
.ask em,.ask i{font-style:inherit}
img{max-width:100%;display:block}
a{color:inherit}
.wrap{max-width:var(--max);margin:0 auto;padding:0 24px}
.eyebrow{font-size:11px;font-weight:600;letter-spacing:.24em;text-transform:uppercase;color:var(--rust-ink)}
.chapter{border-top:1px solid var(--line)}
.eyebrow .idx{color:var(--muted);margin-right:12px}
/* The house photographic treatment: warm sepia, slightly lifted saturation, a touch of
   contrast. One definition for stills AND self-hosted video so the two can never drift.
   Exposed as a variable because a YouTube facade has to re-apply it to a poster image
   (see .ytlite below): an <iframe> is cross-origin, so a filter on it is ignored, and
   would tint YouTube's own chrome if it weren't. */
:root{--tint:sepia(.5) hue-rotate(-10deg) saturate(1.3) contrast(1.05) brightness(.96)}
.tint,.videowrap video{filter:var(--tint)}

/* ---- YouTube facade (.ytlite) ----
   Use this INSTEAD of a bare <iframe> when the video is hosted on YouTube. A cross-origin
   iframe cannot be filtered, so the only way to keep the house grade is to show our own
   tinted poster and swap the iframe in on click. Three things this buys beyond the look:
   nothing loads from YouTube until the visitor asks (so no third-party cookies on page
   load), the page stays fast, and the poster is a frame we control rather than YouTube's
   auto-thumbnail.

   Markup:
     <div class="videowrap ytlite" data-yt="VIDEO_ID">
       <img class="tint" src="img/your-poster.jpg" alt="">
       <button class="ytplay" type="button" aria-label="Play the video">…</button>
     </div>
   The iframe replaces the contents on click: from that point YouTube's player renders
   untinted, which is correct: once it is playing it is YouTube's surface, not ours. */
/* .videowrap's layout previously existed ONLY in index.html, so a facade on any other
   page had no positioning context and the play button collapsed against the page. The
   podcast page is the likely second home for this, so it belongs in the shared sheet. */
.videowrap{position:relative;border-radius:var(--r);overflow:hidden;
  background:var(--obsidian);aspect-ratio:16/9}
.videowrap video{width:100%;height:100%;object-fit:cover;display:block}
.vlen{position:absolute;right:12px;bottom:12px;background:rgba(10,9,7,.72);color:#fff;
  font-size:12px;font-weight:600;padding:4px 9px;border-radius:6px;letter-spacing:.02em;
  pointer-events:none}
.ytlite{cursor:pointer}
.ytlite img{width:100%;height:100%;object-fit:cover;display:block}
.ytlite iframe{width:100%;height:100%;border:0;display:block}
/* the play button: the house rust circle, not YouTube's red, since the poster is ours.
   Bottom-LEFT, not centred: the centred play button is a YouTube convention rather than a
   requirement, and a poster's subject is usually near the middle: on the home page's
   invitation still it landed squarely on D's face. .vlen holds the bottom-right, so the
   two never collide. If a poster puts its subject left of centre, flip this to
   `right:22px` for that block and move .vlen. */
.ytlite .ytplay{position:absolute;left:22px;bottom:22px;width:74px;height:74px;
  border-radius:50%;border:0;cursor:pointer;display:grid;place-items:center;
  background:rgba(197,106,28,.92);box-shadow:0 10px 34px rgba(0,0,0,.45);
  transition:transform .25s cubic-bezier(.22,.9,.3,1),background .25s}
.ytlite .ytplay svg{width:26px;height:26px;fill:#fff;margin-left:4px}
.ytlite:hover .ytplay{transform:scale(1.07);background:rgba(197,106,28,1)}
.ytlite .ytplay:focus-visible{outline:2px solid #fff;outline-offset:4px}
@media(prefers-reduced-motion:reduce){.ytlite .ytplay{transition:none}}


/* beta signage */
.beta-tag{font:600 10px "Instrument Sans",sans-serif;letter-spacing:.18em;text-transform:uppercase;
  color:var(--rust-ink);border:1px solid var(--rust-ink);border-radius:999px;padding:4px 9px;flex-shrink:0}
/* ⚠️ ORPHANED 7 Aug 2026 (D): the "Open beta" bar was REMOVED FROM THE MARKUP on all
   10 pages. These rules are kept so restoring it is one <div>, not a CSS rebuild.
   Delete them together with the markup if the bar is retired for good. */
.beta-bar{background:var(--rust-fill);color:#fff;text-align:center;
  font:600 12.5px/1.5 "Instrument Sans",sans-serif;letter-spacing:.03em;padding:9px 18px}
.beta-bar b{text-transform:uppercase;letter-spacing:.16em;margin-right:10px}

/* nav */
nav.site{position:sticky;top:0;z-index:50;background:var(--nav-bg);
  backdrop-filter:saturate(160%) blur(14px);border-bottom:1px solid var(--line)}
.nav-in{max-width:var(--max);margin:0 auto;padding:12px 24px;display:flex;align-items:center;gap:28px}
.brand{display:flex;align-items:center;gap:11px;margin-right:auto;text-decoration:none;flex-shrink:0}
.brand img{height:38px;width:auto;display:block}
.brand .wm{font-family:"Instrument Sans";font-weight:700;font-size:16px;letter-spacing:.18em;color:var(--ink)}
.brand .wm span{color:var(--echo-b)}
.nav-links{display:flex;gap:30px;font-size:12px;font-weight:600;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-2)}
.nav-links a{text-decoration:none;padding:4px 0;border-bottom:1px solid transparent;transition:.15s}
.nav-links a:hover,.nav-links a.on{color:var(--rust-ink);border-color:var(--rust-ink)}

/* programmes dropdown */
.nav-drop{position:relative}
.nav-drop > a{display:inline-flex;align-items:center;gap:6px}
.nav-drop > a::after{content:"";width:5px;height:5px;flex-shrink:0;margin-top:-2px;
  border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;
  transform:rotate(45deg);transition:transform .2s ease}
.nav-drop.open > a::after{transform:rotate(-135deg) translate(-2px,-2px)}
.nav-menu{position:absolute;top:100%;left:-18px;margin-top:14px;min-width:288px;
  background:var(--sand);border:1px solid var(--line);border-radius:14px;
  padding:16px 6px 10px;box-shadow:0 18px 44px rgba(0,0,0,.24);
  opacity:0;visibility:hidden;transform:translateY(-6px);pointer-events:none;
  transition:opacity .2s ease,transform .25s cubic-bezier(.2,.6,.2,1),visibility 0s .25s}
.nav-drop.open .nav-menu{opacity:1;visibility:visible;transform:none;pointer-events:auto;
  transition:opacity .2s ease,transform .25s cubic-bezier(.2,.6,.2,1)}
/* hover bridge: keeps the menu reachable across the gap */
.nav-menu::before{content:"";position:absolute;left:0;right:0;top:-16px;height:16px}
.nav-grp{font-size:10px;font-weight:700;letter-spacing:.22em;text-transform:uppercase;
  color:var(--rust-ink);padding:10px 18px 6px}
.nav-grp:first-child{padding-top:0}
.nav-menu a{display:block;padding:9px 18px;border-bottom:none;text-transform:none;
  letter-spacing:0;font-size:14.5px;font-weight:600;color:var(--ink);border-radius:8px}
.nav-menu a:hover,.nav-menu a.on{color:var(--rust-ink);border-color:transparent;background:var(--card)}
.nav-menu a i{display:block;font-style:normal;font-size:12.5px;font-weight:500;
  color:var(--muted);letter-spacing:.01em;margin-top:2px}
.nav-menu a:hover i{color:var(--ink-2)}
.nav-menu hr{border:none;border-top:1px solid var(--line);margin:8px 18px}

.btn{display:inline-flex;align-items:center;gap:8px;background:var(--rust-fill);color:#fff;
  text-decoration:none;font-weight:600;font-size:14.5px;padding:13px 26px;border-radius:999px;
  transition:.18s;border:none;cursor:pointer;white-space:nowrap}
.btn:hover{background:var(--rust)}
.btn:active{transform:scale(.98)}
.btn.ghost{background:transparent;color:var(--ink);border:1px solid var(--ghost-line);padding:12px 25px}
.btn.ghost:hover{border-color:var(--ghost-hover);color:var(--ghost-hover);background:transparent}
.btn.lg{font-size:15.5px;padding:16px 32px}
.btn.lg.ghost{padding:15px 31px}
.btn-soon{background:var(--card);color:var(--muted);border:1px solid var(--card-line);cursor:default}
.btn-soon:hover{background:var(--card)}
.soon-note{font-size:11px;font-weight:600;letter-spacing:.22em;text-transform:uppercase;
  color:var(--rust-ink);margin-left:14px}

/* theme selector */
.theme-btn{width:34px;height:34px;flex-shrink:0;border-radius:50%;border:1px solid var(--line);
  background:none;color:var(--muted);display:grid;place-items:center;cursor:pointer;transition:.15s}
.theme-btn:hover{color:var(--ink);border-color:var(--ghost-line)}
.theme-btn svg{display:block}

/* mobile menu */
.menu-btn{display:none;width:34px;height:34px;flex-shrink:0;border-radius:50%;
  border:1px solid var(--line);background:none;cursor:pointer;position:relative}
.menu-btn span{position:absolute;left:10px;right:10px;height:1.5px;background:var(--ink);
  transition:transform .3s ease;top:14px}
.menu-btn span:last-child{top:18px}
.menu-btn.open span:first-child{transform:translateY(2px) rotate(45deg)}
.menu-btn.open span:last-child{transform:translateY(-2px) rotate(-45deg)}
.mnav{position:fixed;inset:0;z-index:40;background:var(--sand);
  display:flex;flex-direction:column;justify-content:flex-start;padding:88px 26px 40px;
  overflow-y:auto;overscroll-behavior:contain;
  opacity:0;visibility:hidden;pointer-events:none;transition:opacity .35s ease,visibility 0s .35s}
/* once the list is taller than the screen, start it at the top instead of centring */
@media(max-height:720px){.mnav{justify-content:flex-start}}
.mnav.open{opacity:1;visibility:visible;pointer-events:auto;transition:opacity .35s ease}
.mnav .mlink{font-family:"Cormorant Garamond",Georgia,serif;font-style:italic;font-weight:600;
  font-size:clamp(32px,8.5vw,44px);line-height:1.15;color:var(--ink);text-decoration:none;
  padding:14px 0;border-bottom:1px solid var(--line);
  opacity:0;transform:translateY(16px);
  transition:opacity .45s ease,transform .55s cubic-bezier(.2,.6,.2,1)}
.mnav.open .mlink{opacity:1;transform:none}
/* stagger is applied per-item by site.js so the list can grow */

/* mobile programmes group */
.mnav .mgrp{font-family:"Instrument Sans",sans-serif;font-style:normal;
  font-size:10px;font-weight:700;letter-spacing:.22em;text-transform:uppercase;
  color:var(--rust-ink);padding:16px 0 2px;border-bottom:none;
  opacity:0;transform:translateY(16px);
  transition:opacity .45s ease,transform .55s cubic-bezier(.2,.6,.2,1)}
.mnav.open .mgrp{opacity:1;transform:none}
.mnav .mlink.sub{font-size:clamp(23px,5.6vw,29px);padding:8px 0 8px 20px;border-bottom:none}
.mnav .mlink.sub i{display:block;font-family:"Instrument Sans",sans-serif;font-style:normal;
  font-size:12px;font-weight:500;letter-spacing:.01em;color:var(--muted);margin-top:1px}
.mnav .mrule{height:1px;background:var(--line);margin:14px 0 0;border:none;
  opacity:0;transition:opacity .45s ease}
.mnav.open .mrule{opacity:1}
.mnav .btn{margin-top:32px;align-self:flex-start;opacity:0;transform:translateY(16px);
  transition:opacity .45s ease .35s,transform .55s cubic-bezier(.2,.6,.2,1) .35s}
.mnav.open .btn{opacity:1;transform:none}
.nav-cta-short{display:none}

/* page hero (secondary pages) */
.phero{padding:110px 0 70px}
.phero h1{font-size:clamp(42px,6.8vw,84px);margin:16px 0 24px;max-width:16ch}
.phero .lede{font-size:clamp(18px,2vw,20px);color:var(--ink-2);line-height:1.55;max-width:56ch}
.phero .meta{margin-top:26px;display:flex;gap:18px;flex-wrap:wrap;
  font-size:12px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--muted)}
.phero .cta-row{display:flex;gap:14px;flex-wrap:wrap;align-items:center;margin-top:36px}

section{padding:100px 0}
.sec-head{max-width:64ch;margin-bottom:48px}
.sec-head h2{font-size:clamp(28px,4vw,46px);margin:14px 0 18px}
.sec-head p{font-size:18px;color:var(--ink-2)}

/* footer */
footer{background:var(--obsidian);color:#A9A398;padding:96px 0 40px;font-size:15px}
.fgrid{display:grid;grid-template-columns:1.6fr 1fr 1fr 1fr;gap:34px;margin-bottom:40px}
footer h4{color:#fff;font-size:15px;margin-bottom:14px}
footer a{color:#A9A398;text-decoration:none;display:block;padding:5px 0}
footer a:hover{color:var(--rust-soft)}
.fbrand{display:flex;align-items:center;gap:12px;margin-bottom:14px}
.fbrand img{height:44px;width:auto;display:block}
.fbrand .wm{font-family:"Instrument Sans";font-weight:700;font-size:17px;letter-spacing:.18em;color:#fff}
.fbrand .wm span{color:var(--echo-caps-dark)}
.fnote{max-width:34ch;line-height:1.6;font-size:14.5px}
/* Social icon row in the Connect column. `footer a` is display:block, so each icon
   anchor has to override that to sit in a row. The 40px box is deliberate: it is the
   tap target, larger than the 20px glyph inside it, so the row stays thumb-friendly on
   mobile without the icons themselves growing.
   ⚠️ index.html and about.html load neither this file nor site.js, so they carry their
   own copies of this block, if you change it here, change it in both. (CLAUDE.md) */
.fsocial{display:flex;gap:6px;margin:2px 0 10px;margin-left:-10px}
.fsocial a{display:inline-flex;align-items:center;justify-content:center;
  width:40px;height:40px;padding:0;border-radius:9px;color:#A9A398;
  transition:color .2s ease,background .2s ease}
.fsocial a:hover{color:var(--rust-soft);background:rgba(255,255,255,.06)}
.fsocial a:focus-visible{outline:2px solid var(--rust-soft);outline-offset:2px}
.fsocial svg{width:20px;height:20px;display:block;fill:currentColor}
.fbot{border-top:1px solid rgba(255,255,255,.11);padding-top:22px;display:flex;
  justify-content:space-between;gap:18px;flex-wrap:wrap;font-size:13.5px;color:#79746A}

/* scroll reveals */
.rev{opacity:0;transform:translateY(22px);
  transition:opacity .7s ease,transform .85s cubic-bezier(.2,.6,.2,1)}
.rev.in{opacity:1;transform:translateY(0)}

@media(max-width:900px){
  .nav-links{display:none}
  .menu-btn{display:block}
  .fgrid{grid-template-columns:1fr 1fr}
  section{padding:64px 0}
  .phero{padding:72px 0 52px}
}
@media(max-width:640px){
  .brand .wm{display:none}
  .nav-in{gap:10px;padding:12px 14px}
  .theme-btn{width:32px;height:32px}
  nav.site .btn{padding:10px 14px;font-size:12.5px}
  .btn{padding:11px 20px;font-size:13.5px}
  .btn.lg{padding:14px 26px;font-size:14.5px}
  .btn.lg.ghost{padding:13px 25px}
  .fgrid{grid-template-columns:1fr}
}
@media(max-width:360px){
  .beta-tag{display:none}
  .nav-in{gap:8px;padding:12px 10px}
  nav.site .nav-snapshot .nav-cta-wide{display:none}
  nav.site .nav-snapshot .nav-cta-short{display:inline}
}

/* ============================================================
   The enquiry modal: SITE-WIDE component (docs/46 Part 7)
   ------------------------------------------------------------
   Opened by any [data-enquire] trigger. Never on a timer or exit intent: it is an
   offer, not a trap.

   The markup is INJECTED BY site.js, not written into each page: eight hand-copied
   modals is the drift CLAUDE.md warns about, and the `.enq > p` bug below was already
   fixed on one page and left broken on another because of exactly that.

   ⚠️ index.html and about.html load neither this file nor site.js, so they carry
   their own copies. Change one, change all three.
   ============================================================ */
/* Reserve the scrollbar gutter permanently, so locking scroll for the modal removes
   nothing and the page cannot shift. */
html{scrollbar-gutter:stable}
.enq-back{position:fixed;inset:0;z-index:120;background:rgba(8,8,10,.72);
  backdrop-filter:blur(3px);display:none;align-items:center;justify-content:center;
  padding:20px}
.enq-back[open],.enq-back.on{display:flex}
.enq{background:var(--card);border:1px solid var(--card-line);border-radius:var(--r);
  padding:34px clamp(22px,4vw,40px);max-width:520px;width:100%;max-height:90vh;
  overflow-y:auto;position:relative}
.enq h3{font-size:22px;margin:0 0 10px}
/* Must cover the paragraph inside .enq-form, not just a direct child of .enq: the
   intro sits inside the form wrapper, and a `.enq > p` rule alone left it with no
   bottom margin, colliding with the first field label. */
.enq > p,.enq-form > p,.enq-done > p{font-size:15.5px;line-height:1.6;color:var(--ink-2);
  margin:0 0 22px;max-width:46ch}
.enq label{display:block;font-size:13px;font-weight:600;letter-spacing:.04em;
  color:var(--muted);margin:0 0 6px}
/* --bg is the field ground on the shared pages; index.html names the same colour
   --sand and defines no --bg, which left its inputs transparent. The fallback chain
   keeps one rule correct on all three copies. */
/* line-height is set explicitly so <select> matches <input>. `font:inherit` does NOT carry
   line-height into a select, the browser applies `normal`, so at identical padding the
   selects rendered 45px against the inputs' 50px and read as thinner controls in the same
   column. Stating it once here keeps every field on the same height. */
.enq input,.enq select,.enq textarea{width:100%;font:inherit;font-size:15.5px;
  line-height:1.65;
  color:var(--ink);background:var(--bg,var(--sand,#0D0D0F));border:1px solid var(--card-line);
  border-radius:10px;padding:11px 13px;margin-bottom:16px}
/* selects also need the UA appearance off, or Safari/iOS re-imposes its own metrics and
   the height goes back out of step. The arrow is drawn back on as a background chevron.
   ⚠️ background-color is restated here: the rule above uses the `background` SHORTHAND,
   which resets background-image to none. Setting only background-image below it would be
   silently wiped: it was, first time round. */
.enq select{appearance:none;-webkit-appearance:none;padding-right:38px;
  background-color:var(--bg,var(--sand,#0D0D0F));
  background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),
    linear-gradient(135deg,currentColor 50%,transparent 50%);
  background-position:calc(100% - 20px) calc(50% + 1px),calc(100% - 15px) calc(50% + 1px);
  background-size:5px 5px,5px 5px;background-repeat:no-repeat}
.enq textarea{min-height:96px;resize:vertical}
.enq input:focus,.enq select:focus,.enq textarea:focus{outline:2px solid var(--rust-ink);
  outline-offset:-1px;border-color:transparent}
.enq .enq-note{font-size:14px;color:var(--muted);margin:-8px 0 22px;max-width:46ch}
/* the "Optional" tag: covers legend too, since a fieldset's legend is not a label */
.enq label i,.enq .enq-fs legend i{font-style:normal;font-weight:500;color:var(--muted);
  font-size:11.5px;letter-spacing:.1em;text-transform:uppercase;margin-left:6px}
/* field help: sits between a label and its input, so it must not carry the label's
   bottom margin or the pair drifts apart */
.enq .fh{font-size:13.5px;line-height:1.5;color:var(--muted);margin:-2px 0 8px;max-width:44ch}
.enq .enq-priv{font-size:13px;color:var(--muted);margin:14px 0 0;max-width:44ch}
/* Honeypot: off-canvas rather than display:none, because some bots skip hidden
   fields. People never see it; a script filling every input trips it. */
.enq-hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
.enq .enq-close{position:absolute;top:12px;right:14px;background:none;border:0;
  font-size:26px;line-height:1;color:var(--muted);cursor:pointer;padding:6px 10px}
.enq .enq-close:hover{color:var(--ink)}
.enq .enq-done{display:none}
.enq.sent .enq-form{display:none}
.enq.sent .enq-done{display:block}
.enq .enq-done p{font-size:16.5px;line-height:1.65;color:var(--ink-2);margin:0}
/* a text trigger sitting inside body copy.
   ⚠️ Keep in step with sessions.html's local copy of this rule (it carries its own).
   The hover/focus/bone rules were added 8 Aug 2026 when the in-body "ask us anything"
   invitations across resone/play/open/vocal-freedom were rerouted from Instagram to
   [data-enquire]: this file's copy had only the base rule, so those new buttons had no
   visible keyboard focus ring and no bone-theme contrast override. */
button.linkish{background:none;border:0;padding:0;font:inherit;cursor:pointer;
  color:var(--rust-ink);font-weight:600;text-decoration:none}
button.linkish:hover{text-decoration:underline}
button.linkish:focus-visible{outline:2px solid var(--rust-ink);outline-offset:2px;
  border-radius:3px}
[data-theme="bone"] button.linkish{color:#8A4410}

/* ============================================================================
   THE ENVELOPE + LETTER OVERLAY
   Moved here from index.html 7 Aug 2026 (D asked for the same envelope on the
   sessions page). It was inline in index.html because index shares no files with the
   other pages, but the moment a SECOND page needed it, keeping it there would have
   meant a third hand-maintained copy of ~320 lines, which is the exact drift CLAUDE.md
   warns about with .ytlite and the contact modal.
   ⚠️ index.html loads NEITHER site.css NOR site.js, so it keeps its own copy and this
   one serves every other page. The two ARE duplicated: that is the cost of index being
   self-contained. If you change one, change both.
   ============================================================================ */
/* ---- the envelope (7 Aug 2026, D) -----------------------------------------------
   ONE testimonial in the feed is a ~450-word letter written seven months after a
   session (supabase/update-testimonials-sessions-2026-08-07.sql). It cannot render as
   a pile card: the deck's cards are 268px wide and sized for a sentence or two, so the
   letter would either overflow or be silently clipped mid-sentence.
   So in the pile it is an ENVELOPE: a card-sized object that says "this is a letter,
   there is more inside", and clicking it slides the letter out into an overlay at a
   readable width. The length problem becomes the feature: the reader chooses to open it.
   ⚠️ This is a SECOND paper surface on the site. The first is proto/sessions.html's
   .letter, which renders the same text inline. They share the perspective-tilt + uneven-
   corners + fibre-texture approach deliberately: a reader who sees both should register
   them as the same object. If the treatment changes in one, change it in the other.
   ⚠️ index.html shares NO css/js with the other pages (see CLAUDE.md), so this lives
   here and nowhere else. */
/* ⚠️ The envelope IS the card face: the pile's .q padding and rust wash would otherwise
   frame it like a card with a picture of an envelope inside it, which is what the first
   build did. Padding to zero, background off, and .env fills the card edge to edge. */
.q.envelope{cursor:pointer;padding:0;background:none;border:0;overflow:visible}
.quotes.deck .q.envelope{padding:0}
/* the envelope face. The flap is a ::after triangle rather than an image, so it themes
   itself and stays crisp at any size. */
/* ⚠️ The envelope must NOT read as one of the quote cards (D, 7 Aug 2026: "make the
   envelope stand out so it looks different"). In a pile of 27 rust-washed rectangles a
   subtle object disappears, and if it disappears, nobody clicks it and the letter is
   effectively unpublished. Four things separate it, deliberately compounding:
     1. It is PALER and warmer than the cards: envelope stock against dark cards.
     2. It sits BIGGER (scale + a stronger shadow), as the one object lying on top.
     3. It is straighter than its neighbours (see --rot damping below): everything else
        is thrown at an angle, so near-level reads as deliberately placed.
     4. It carries a rust ring and a soft glow that no card has.
   The stamp and postmark then do the rest: no other card has any colour like them. */
.env{position:relative;display:block;width:100%;aspect-ratio:1.62/1;border-radius:3px;
  /* paper stock, not card: a warm bone wash that stays light in BOTH themes rather than
     inheriting --card-solid, which is near-black in dark mode and would sink it back
     into the pile. This is the one surface on the page that ignores the theme ground. */
  background:linear-gradient(158deg,#F4EADC,#E8DAC6 62%,#DFCDB4);
  border:1px solid rgba(197,106,28,.55);overflow:hidden;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.5),
    0 0 0 1px rgba(197,106,28,.18),
    0 10px 26px rgba(0,0,0,.30),
    0 26px 60px rgba(0,0,0,.34)}
/* the ink on bone stock: fixed dark values, since the face no longer follows the theme */
.env .addr .to{color:#241F18}
.env .addr .from{color:#4A4238}
.env .addr .from .loc,.env .addr .meta{color:#6B6154}
.env .pmark{color:rgba(150,72,18,.72);border-color:rgba(150,72,18,.5)}
.env .stamp b,.env .stamp i{color:#241F18}
.env .open-cue{color:#A8500F}
/* lifted clear of the heap and barely tilted: the letter is the object on top */
.quotes.deck .q.envelope{transform:rotate(calc(var(--rot,0deg) * .3)) scale(1.06);z-index:60}
.quotes.deck .q.envelope:hover,.quotes.deck .q.envelope:focus-within{
  transform:rotate(calc(var(--rot,0deg) * .12)) translateY(-14px) scale(1.10);
  z-index:950}
/* the flap: a wide shallow triangle from the top edge, drawn with a clip-path so its
   two diagonals read as folded paper. Slightly darker than the face, as a fold would be. */
/* ⚠️ 40%, not 56%. The flap is the SEALED edge: it must read as a fold across the top
   of the envelope, leaving the lower two-thirds as the face where the address sits. At
   56% the triangle's point reached the middle of the card and swallowed the address
   block, which is the layout the first build shipped. */
.env::after{content:"";position:absolute;left:-1px;right:-1px;top:0;height:40%;
  background:linear-gradient(180deg,rgba(0,0,0,.13),rgba(0,0,0,.04));
  border-bottom:1px solid rgba(197,106,28,.22);
  clip-path:polygon(0 0,100% 0,50% 100%)}
/* the postmark: concentric rings + the date, rotated like a hand-stamped cancel.
   Deliberately imprecise: a perfectly level postmark reads as a logo.
   Sized 58px and pinned top-left BESIDE the stamp, the way a cancel actually falls.
   It must not drift down into the address block: at 74px/top:15% it collided with it. */
.env .pmark{position:absolute;left:7%;top:9%;width:58px;height:58px;border-radius:50%;
  border:1.5px solid rgba(197,106,28,.42);transform:rotate(-13deg);
  display:grid;place-items:center;text-align:center;line-height:1.2;padding:6px;
  font-size:7px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
  color:rgba(197,106,28,.62);z-index:2}
.env .pmark::before{content:"";position:absolute;inset:5px;border-radius:50%;
  border:1px solid rgba(197,106,28,.28)}
/* the stamp, top-right. China, and it is literally correct, not a stand-in for the
   language (D, 7 Aug 2026): they met and did the session in CHIANG MAI, Dec 2025/Jan
   2026, and she wrote this seven months on from SHENZHEN. So the letter genuinely comes
   from China; the city in the letter's own text (Pai, then back to Chiang Mai) is where
   the events happened, not where it was posted from. An earlier draft of this comment
   treated the stamp as a compromise between language and geography: it isn't.
   The perforated edge is a repeating radial-gradient mask, so there is no image. */
.env .stamp{position:absolute;right:8%;top:11%;width:56px;height:66px;z-index:2;
  background:linear-gradient(165deg,rgba(197,106,28,.34),rgba(197,106,28,.14));
  display:grid;place-items:center;gap:2px;
  filter:drop-shadow(0 1px 1px rgba(0,0,0,.25));
  -webkit-mask:radial-gradient(circle at 50% 0,transparent 3px,#000 3.5px) 0 0/9px 9px repeat-x,
        radial-gradient(circle at 50% 100%,transparent 3px,#000 3.5px) 0 100%/9px 9px repeat-x,
        linear-gradient(#000,#000) 50% 50%/100% calc(100% - 12px) no-repeat;
  mask:radial-gradient(circle at 50% 0,transparent 3px,#000 3.5px) 0 0/9px 9px repeat-x,
        radial-gradient(circle at 50% 100%,transparent 3px,#000 3.5px) 0 100%/9px 9px repeat-x,
        linear-gradient(#000,#000) 50% 50%/100% calc(100% - 12px) no-repeat}
.env .stamp b{font-family:"Songti SC","Noto Serif CJK SC",Georgia,serif;
  font-size:17px;font-weight:600;color:var(--ink);opacity:.78;line-height:1}
.env .stamp i{font-style:normal;font-size:6.5px;font-weight:700;letter-spacing:.1em;
  color:var(--ink);opacity:.5}
/* the address block, where a name would go. It carries the attribution instead, which
   is the one piece of information that must survive without opening. */
/* the address block. ⚠️ display:block on the spans is load-bearing: they are inline
   <span>s (the pile's markup is spans, not divs) and without it the name, the location
   and the timing all run together on one line, which is what the first build did.
   Right-padded so it can never collide with the open cue sitting in the same corner. */
.env .addr{position:absolute;left:9%;right:9%;bottom:12%;z-index:2;
  padding-right:64px}
/* "To Derrick": the recipient line, and the biggest type on the envelope. It is what
   makes the object read as addressed mail rather than a decorated card. */
.env .addr .to{display:block;font-family:"Cormorant Garamond",Georgia,serif;
  font-style:italic;font-weight:600;font-size:19px;line-height:1.2;color:var(--ink);
  margin:0 0 2px}
/* the sender, a step down: "from Anonymous · Female, China" */
.env .addr .from{display:block;font-family:"Cormorant Garamond",Georgia,serif;
  font-style:italic;font-weight:500;font-size:13px;line-height:1.25;margin:0 0 4px}
.env .addr .from .loc{letter-spacing:0}
.env .addr .meta{display:block;font-size:9.5px;font-weight:600;letter-spacing:.12em;
  text-transform:uppercase;color:var(--muted);line-height:1.3}
/* the affordance. Without this the envelope is a pretty object with no signal that it
   opens: the single most likely failure of the whole idea. */
.env .open-cue{position:absolute;right:9%;bottom:12%;z-index:2;font-size:10px;
  font-weight:700;letter-spacing:.13em;text-transform:uppercase;
  color:var(--rust-ink);opacity:.85;white-space:nowrap}
.q.envelope:hover .env .open-cue{opacity:1}
/* the flap lifts a little on hover: the "it opens" cue made physical */
.env::after{transition:transform .35s cubic-bezier(.2,.7,.3,1)}
.q.envelope:hover .env::after,.q.envelope:focus-within .env::after{
  transform:rotateX(26deg);transform-origin:50% 0}
[data-theme="bone"] .env .pmark{color:#8A4410;border-color:rgba(138,68,16,.45)}
[data-theme="bone"] .env .addr .to{color:#2A2620}

/* ---- the opened letter (overlay) ------------------------------------------------
   D asked for the real thing: the envelope opens, the letter slides out, then it
   unfolds. That is three beats and they must be SEQUENTIAL: played together it reads
   as one blurry move. The stages are driven by classes the JS adds on a timeline
   (.s-open → .s-out → .s-flat); each stage owns one transform and nothing else, which
   is what keeps them legible.
     s-open  the flap rotates back on its hinge          (380ms)
     s-out   the folded sheet rises out of the envelope  (460ms)
     s-flat  the folds drop open, top and bottom thirds  (560ms)
   Reduced-motion collapses the whole thing to a fade: see the block at the foot. */
.lt-back{position:fixed;inset:0;z-index:1200;display:grid;place-items:center;
  padding:28px 20px;background:rgba(0,0,0,.62);backdrop-filter:blur(3px);
  opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s;
  perspective:1600px}
.lt-back.on{opacity:1;visibility:visible}

/* the envelope that plays the animation, centred behind the sheet. It is a prop: it
   exists only for the open, and is faded out once the letter is flat. */
/* scaled up with the sheet: the folded letter has to look like it fits INSIDE this, so
   if the sheet's width changes, change this too. 520 ≈ the folded sheet's ~347px plus a
   comfortable mouth either side. */
.lt-env{position:absolute;width:min(520px,86vw);aspect-ratio:1.62/1;
  border-radius:3px;pointer-events:none;
  background:linear-gradient(158deg,#F4EADC,#E8DAC6 62%,#DFCDB4);
  border:1px solid rgba(197,106,28,.55);
  box-shadow:0 20px 50px rgba(0,0,0,.45);
  opacity:0;transition:opacity .3s ease .1s;z-index:2}
.lt-back.on .lt-env{opacity:1}
/* once the sheet is out and flat the envelope has done its job */
.lt-back.s-flat .lt-env{opacity:0;transition:opacity .45s ease}
/* the BODY of the envelope: the front panel the letter slides out from BEHIND.
   z-index above the sheet, so the letter genuinely emerges from inside it. */
.lt-env .body{position:absolute;inset:0;border-radius:inherit;z-index:3;
  background:linear-gradient(158deg,#EFE2D0,#E2D2BB 60%,#D8C4A8);
  border-top:1px solid rgba(197,106,28,.28);
  clip-path:polygon(0 34%,50% 76%,100% 34%,100% 100%,0 100%)}
/* the FLAP: hinged at the top edge, rotates back through -168deg to open. */
.lt-env .flap{position:absolute;left:-1px;right:-1px;top:0;height:56%;z-index:5;
  transform-origin:50% 0;transform:rotateX(0deg);
  transition:transform .38s cubic-bezier(.4,0,.35,1),z-index 0s .19s;
  background:linear-gradient(180deg,#EDE0CD,#E3D3BC);
  border:1px solid rgba(197,106,28,.35);border-top:0;
  clip-path:polygon(0 0,100% 0,50% 100%)}
.lt-back.s-open .lt-env .flap{transform:rotateX(-168deg);z-index:1}

/* the sheet slides UP out of the envelope, then unfolds.
   ⚠️ ABSOLUTE while it is animating, not a grid item. The letter is a tall element, and
   as a grid child its box stays where the grid puts it: transform-origin:50% 100% then
   anchors to the bottom of that tall box, which measured ~280px BELOW the envelope. The
   letter was scaling up from under the envelope rather than out of it, which is exactly
   the "paper doesn't come out" fault. Absolute + the same centring as .lt-env puts the
   two objects in the same place, so "inside" is true rather than approximated.
   It returns to static flow at .s-flat, where it needs real layout to scroll. */
/* ⚠️ WIDTH IS SET BY THE TEXT, NOT BY THE SCREEN (D, 7 Aug 2026: "it shouldn't take up
   the whole screen … the width should be just a bit wider than the actual text").
   A brief 1020px version was tried and reverted: a sheet that fills the viewport stops
   reading as a letter and becomes a page. So the sheet is sized to its own MEASURE: the
   paragraphs are capped at 62ch, and the sheet is that measure plus its margins and no
   more. Measured rather than guessed: the text column runs ~548px at this size, and the
   56+68px of side padding puts the sheet at ~672px: just wider than the text.
   ⚠️ If the type size or the 62ch cap changes, this number has to move with it: they
   are one decision expressed in two places. Check by measuring a <p> against the sheet:
   the difference should be the padding and nothing more. */
.lt-sheet{position:absolute;left:50%;top:50%;
  transform-box:border-box;
  z-index:4;width:min(672px,92vw);max-height:calc(100vh - 56px);
  overflow:hidden;-webkit-overflow-scrolling:touch;
  /* the margins are what make it read as paper rather than a text box. Left is deepest
     because the rule sits there; these are counted into the sheet's width above. */
  padding:52px 56px 42px 68px;
  /* ⚠️ STARTS INSIDE THE ENVELOPE, and the numbers have to earn that.
     The prop envelope is min(420px,82vw) wide; the sheet is min(760px,100%). For the
     letter to look CONTAINED at rest it must be narrower than the envelope mouth and
     sitting down at the envelope's own height: 760 × .42 = 319px was wider than the
     envelope's fold line, so it read as a big sheet shrinking rather than a letter
     tucked inside. .34 puts it at ~258px, comfortably inside the 420px mouth.
     translateY is +96px: the envelope is ~260px tall, so this drops the sheet's centre
     to the envelope's lower half where the contents actually sit.
     scaleY(.06) is the letter folded flat: near-invisible edge-on, which is why it
     then has somewhere to rise FROM.
     The origin is the BOTTOM edge, so it grows upward out of the mouth rather than
     expanding around its own middle. */
  /* translate(-50%,-50%) is the CENTRING half and must lead every transform below:
     the element is pinned at left:50%/top:50%, so without it the sheet hangs down and
     right of centre. Everything after it is the animation.
     +78px drops the centred sheet into the envelope's lower half (the envelope is
     ~259px tall, so its contents sit around +70 to +90 from centre).
     scaleY(.06) is the letter folded flat: near invisible edge-on, so it has somewhere
     to rise from. Origin at the bottom edge means it grows UP out of the mouth. */
  /* ⚠️ TRANSLATE BEFORE SCALE, and the translate is in UNSCALED pixels.
     Transforms apply left-to-right on the element's coordinate system, so any translate
     written AFTER a scale() is multiplied by it: translateY(86px) after scale(.34)
     moves only 29px. Worse, transform-origin:50% 100% on a ~690px-tall sheet anchored it
     to the bottom of its own box, which is what put the letter 275px BELOW the envelope
     in the first build and made it look like it was rising from nowhere.
     So: centre it, translate in real screen pixels, THEN scale about its own centre.
     +88px puts the folded sheet in the envelope's lower half (envelope ≈259px tall,
     centred on the same point, so its contents sit around +70…+95 from centre). */
  transform:translate(-50%,-50%) translateY(88px) scale(.34) scaleY(.06);opacity:0;
  transform-origin:50% 50%;
  transition:transform .46s cubic-bezier(.25,.8,.3,1),opacity .22s ease;
  /* the same paper recipe as sessions.html's .letter: see the ⚠️ above.
     ⚠️ PINNED LIGHT IN BOTH THEMES (D, 7 Aug 2026), like the envelope it comes out of.
     A letter is a physical object the reader is looking at, not a panel in the interface,
     and a near-black sheet is not a letter. The envelope in the pile was already pinned
     for this reason; the sheet inside it has to match or the letter changes colour as it
     slides out. Ink is pinned with it, below. */
  border-radius:3px 8px 4px 10px / 8px 3px 10px 4px;
  background:
    repeating-linear-gradient(97deg,rgba(0,0,0,.014) 0 2px,transparent 2px 5px),
    repeating-linear-gradient(6deg,rgba(0,0,0,.012) 0 3px,transparent 3px 7px),
    radial-gradient(130% 90% at 10% -6%,rgba(197,106,28,.07),transparent 58%),
    radial-gradient(110% 80% at 104% 106%,rgba(0,0,0,.075),transparent 55%),
    #FBF9F5;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.06),inset 0 -1px 0 rgba(0,0,0,.05),
    0 1px 1px rgba(0,0,0,.16),0 30px 70px -20px rgba(0,0,0,.6)}
/* BEAT 2: the folded sheet is DRAWN OUT of the envelope. It must travel far enough to
   clear the mouth: the envelope is ~260px tall, so the sheet lifts to -150px, putting
   its body well above the fold line while still visibly the same folded object.
   Width stays at .34 (still the narrow, letter-in-envelope width) and only the HEIGHT
   opens, .06 → .30, because a letter coming out of an envelope gets taller, not
   wider. Widening here was what made the earlier version read as a zoom. */
/* ⚠️ .60, not .34 (D, 7 Aug 2026: "about half the size it should be", referring to the
   letter AS IT COMES OUT, not the flat sheet, which is sized to its own measure above).
   At .34 the emerging letter was ~270px against a 520px envelope, so it read as a small
   slip rather than a folded sheet that had been inside. .60 puts it at ~474px: close to
   the envelope's own width, which is what the real object does: a letter is folded to
   fit its pocket, so as it is drawn out it is visibly the same order of size, not a
   fraction of it. (.55 was tried first; D asked for ~10% more.)
   The rest state stays small, it is hidden inside the envelope anyway, so the growth
   from tucked-in to drawn-out is a real change in size rather than a nudge. */
.lt-back.s-out .lt-sheet{opacity:1;
  transform:translate(-50%,-50%) translateY(-185px) scale(.60) scaleY(.30)}
/* BEAT 3: the folds drop open and it settles at full size. The longer, softer curve
   here is the unfold itself; it must feel like paper relaxing, not a panel snapping. */
/* stays absolutely centred: translate(-50%,-50%) alone IS the centred resting position,
   so the letter settles exactly where it unfolded rather than jumping into grid flow at
   the end of the animation. overflow:auto returns so a long letter scrolls. */
.lt-back.s-flat .lt-sheet{transform:translate(-50%,-50%) rotate(-.4deg);
  max-height:calc(100vh - 56px);overflow:auto;
  transition:transform .56s cubic-bezier(.22,.9,.28,1),opacity .3s ease}
/* the two fold creases, fading as the letter flattens: the detail that makes the
   unfold legible rather than just a scale-up */
.lt-sheet::after{content:"";position:absolute;left:0;right:0;top:0;bottom:0;
  pointer-events:none;z-index:2;opacity:0;transition:opacity .4s ease;
  background:linear-gradient(180deg,transparent 31%,rgba(0,0,0,.13) 33%,transparent 35%,
    transparent 64%,rgba(0,0,0,.13) 66%,transparent 68%)}
.lt-back.s-out .lt-sheet::after{opacity:1}
.lt-back.s-flat .lt-sheet::after{opacity:0}
/* the margin rule, as on the sessions page */
.lt-sheet::before{content:"";position:absolute;left:28px;top:0;bottom:0;width:1px;
  background:linear-gradient(180deg,transparent,rgba(197,106,28,.30) 8%,
    rgba(197,106,28,.30) 92%,transparent)}
/* ---- the ink, pinned with the paper ----
   The sheet is fixed light in both themes, so every colour on it must be fixed too:
   --ink would paint bone text on bone paper under the dark theme. Values are the bone
   theme's own tokens hardcoded: ink #14130F, muted #6B675C, rust #8A4410. */
/* the measure. 62ch at this size is what SETS the sheet's width (see .lt-sheet): the
   paper is sized to the text, not the other way round. */
.lt-sheet p{font-family:"Cormorant Garamond",Georgia,serif;font-style:italic;
  font-weight:500;font-size:clamp(18px,1.4vw,20.5px);line-height:1.72;color:#14130F;
  margin:0 0 20px;max-width:62ch}
.lt-sheet p:first-of-type{font-size:clamp(20px,1.6vw,23px);line-height:1.5;
  margin-bottom:26px}
.lt-sheet p:last-of-type{margin-bottom:0}
.lt-sheet .who{margin-top:30px;padding-top:16px;text-align:right;
  border-top:1px solid rgba(197,106,28,.18);
  font-size:11px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;
  color:#8A4410}
.lt-sheet .who .loc{color:#6B675C}
.lt-sheet .who .when{display:block;margin-top:5px;color:#6B675C;
  letter-spacing:.1em;text-transform:none;font-size:11.5px;font-style:italic}
/* the translation disclosure: same claim the sessions page makes in its lead-in. It is
   provenance, so it sits ABOVE the letter where it is read first, not in a footer. */
.lt-sheet .ltr{display:block;margin:0 0 20px;font-size:12.5px;font-weight:500;
  letter-spacing:.02em;color:#6B675C;font-style:normal}
/* the close sits ON the paper, so it takes paper ink too */
.lt-close{position:absolute;top:10px;right:12px;z-index:3;background:none;border:0;
  font-size:26px;line-height:1;color:#6B675C;cursor:pointer;padding:6px 10px}
.lt-close:hover{color:#8A4410}
.lt-hint{margin:14px auto 0;text-align:center;font-size:11.5px;letter-spacing:.1em;
  text-transform:uppercase;color:rgba(255,255,255,.62)}
@media(max-width:640px){
  .lt-sheet{padding:38px 22px 30px 32px;max-height:calc(100vh - 32px)}
  .lt-sheet::before{left:16px}
}
@media(prefers-reduced-motion:reduce){
  /* the whole three-beat sequence collapses to a plain fade. The prop envelope is not
     shown at all: a flap rotating open is exactly the kind of motion this setting is
     asking us not to play. */
  .lt-back{transition:opacity .01s linear}
  .lt-env{display:none}
  /* still absolutely centred: transform:none would drop it down-right of centre */
  .lt-sheet,.lt-back.s-out .lt-sheet,.lt-back.s-flat .lt-sheet{
    transition:opacity .01s linear;transform:translate(-50%,-50%);opacity:1;
    max-height:calc(100vh - 56px);overflow:auto}
  .lt-sheet::after{display:none}
  .env::after,.q.envelope:hover .env::after{transition:none;transform:none}
}
