/* Mythkin site — one stylesheet, no build step, no external requests.

   THE PALETTE IS THE SHIPPING APP'S, TOKEN FOR TOKEN. It is copied out of
   mythkin-app/tokens.css (itself generated from tokens.json) rather than
   approximated, because the previous version of this file opened with the same
   claim and was not true of it: the app moved to the warm ember/hearth brand
   and the site stayed on the plum one it started with. Measured, the two
   disagreed on almost every ink —

       token          shipping app        this file, before
       tint (light)   #8C3A2B terracotta  #6B3FA0 purple
       tint (dark)    #E4795C ember       #9D6FD9 lavender
       bg   (dark)    #170F0C warm black  #140B26 plum
       text (light)   #1F1512             #1A1526

   so every screenshot on the page sat in a purple frame advertising a
   terracotta product. If tokens.json changes again, re-copy this block; do not
   hand-tune a value to taste, because the phone captures cannot follow.

   --goldText / --tintText exist because the plain --gold and --tint are FILL
   colours and fail WCAG AA as small ink on the page background. Gold at 11px/800
   measured 3.56–4.02:1 in light mode at all three of its uses here. Use the
   *Text pair for type and the plain one for fills; the app draws exactly this
   distinction for exactly this reason. */
:root{
  color-scheme: light dark;
  --bg:#FBF6EC; --surface:#FFFFFF; --surface2:#F5EADD;
  --text:#1F1512; --dim:#6E6058;
  --tint:#8C3A2B; --tint2:#B4611C; --gold:#9A7B22;
  --good:#2F6B5E; --heart:#B8455F; --border:#E8DCC9;
  --onTint:#FFF8F2; --goldText:#7E6010; --tintText:#853526;

  --serif:"New York","Iowan Old Style",Georgia,serif;
  --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
  --r-s:8px; --r-m:12px; --r-card:14px; --r-l:20px; --r-pill:999px;

  /* Two measures, not one. The landing page is wide so the room rails have
     somewhere to go; PROSE inside it is capped separately, because widening the
     column to 60rem previously stretched body copy to 104–115 characters a line
     — roughly double a comfortable measure. */
  --maxw:64rem;
  --prose:38rem;

  /* Elevation as a token pair. These were hardcoded rgba(0,0,0,.30), which is
     invisible against a #170F0C page: the device mockups lost their edges in
     dark mode and sat at 1.29:1 against the background. */
  --shadow-lift:0 22px 50px rgba(0,0,0,.30),0 2px 8px rgba(0,0,0,.14);
  --shadow-soft:0 14px 34px rgba(0,0,0,.24),0 2px 6px rgba(0,0,0,.12);
}

/* THREE-WAY, and the explicit choice must win in BOTH directions. The media
   query is scoped :not([data-theme="light"]) so a reader on a dark OS who picks
   Light actually gets light; html[data-theme="dark"] is the mirror for a light
   OS. Without both halves a toggle only ever works one way, which reads as a
   broken control. `color-scheme` moves too, or form controls and scrollbars
   keep rendering in the OS's scheme underneath the chosen palette. */
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){
    --bg:#170F0C; --surface:#241814; --surface2:#31201A;
    --text:#F7EBE3; --dim:#C0AA9E;
    --tint:#E4795C; --tint2:#F2A860; --gold:#E8C06A;
    --good:#79C7B4; --heart:#F28BA8; --border:#3A2620;
    --onTint:#170F0C; --goldText:#E8C06A; --tintText:#F0997F;
    --shadow-lift:0 22px 50px rgba(0,0,0,.55),0 0 0 1px rgba(247,235,227,.07);
    --shadow-soft:0 14px 34px rgba(0,0,0,.45),0 0 0 1px rgba(247,235,227,.06);
  }
}
html[data-theme="dark"]{
  color-scheme:dark;
  --bg:#170F0C; --surface:#241814; --surface2:#31201A;
  --text:#F7EBE3; --dim:#C0AA9E;
  --tint:#E4795C; --tint2:#F2A860; --gold:#E8C06A;
  --good:#79C7B4; --heart:#F28BA8; --border:#3A2620;
  --onTint:#170F0C; --goldText:#E8C06A; --tintText:#F0997F;
  --shadow-lift:0 22px 50px rgba(0,0,0,.55),0 0 0 1px rgba(247,235,227,.07);
  --shadow-soft:0 14px 34px rgba(0,0,0,.45),0 0 0 1px rgba(247,235,227,.06);
}
html[data-theme="light"]{color-scheme:light}

*{box-sizing:border-box}
body{margin:0;font:16px/1.6 var(--sans);background:var(--bg);color:var(--text);
  -webkit-font-smoothing:antialiased}
img{max-width:100%}
a{color:var(--tintText)}

/* EVERY focusable thing, not just a and button. The old rule named those two,
   so the FAQ's <summary> disclosures fell back to the browser's default ring. */
:focus-visible{outline:2px solid var(--tint);outline-offset:3px;border-radius:4px}

/* The page had no skip link and no <main>, so a keyboard user entered through
   the header every time and the room rails put 60 tab stops in front of the
   content. */
.skip{position:absolute;left:-9999px;top:0;z-index:100;background:var(--tint);
  color:var(--onTint);padding:.7rem 1.1rem;border-radius:0 0 var(--r-m) 0;
  font-weight:700;text-decoration:none}
.skip:focus{left:0}
.sr-only{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap}

.wrap{max-width:var(--maxw);margin:0 auto;padding:0 1.5rem}

/* ---- theme picker ---------------------------------------------------------
   Three segments, not a switch — a binary cannot express "follow my system",
   which is what most people actually want. The pressed segment paints
   --onTint, never white: white on the dark-mode ember measured 3.68:1, which
   is the same defect the app diagnosed by number and fixed with this token. */
.themepick{display:inline-flex;gap:2px;padding:2px;border:1px solid var(--border);
  border-radius:var(--r-pill);background:var(--surface);line-height:1;flex:none}
.themepick button{appearance:none;border:0;background:none;cursor:pointer;
  font:600 .74rem/1 var(--sans);color:var(--dim);
  padding:.5rem .7rem;border-radius:var(--r-pill);min-height:38px}
.themepick button[aria-pressed="true"]{background:var(--tint);color:var(--onTint)}
@media (prefers-reduced-motion:no-preference){
  .themepick button{transition:background .15s ease,color .15s ease}
}

/* ---- site chrome ---------------------------------------------------------- */
header.site{position:sticky;top:0;z-index:20;
  background:color-mix(in srgb,var(--bg) 84%,transparent);
  backdrop-filter:blur(14px);
  border-bottom:1px solid color-mix(in srgb,var(--border) 65%,transparent)}
header.site .wrap{display:flex;align-items:center;gap:1rem;
  padding-top:.7rem;padding-bottom:.7rem}
header.site .brand,a.brand{display:flex;align-items:center;gap:.6rem;
  font-family:var(--serif);font-size:1.3rem;font-weight:700;color:var(--text);
  text-decoration:none;letter-spacing:-.015em}
a.brand img{width:34px;height:34px;border-radius:9px}
header.site nav{margin-left:auto;display:flex;gap:.4rem;flex-wrap:wrap}
/* 44pt targets. These were 21.8–23px tall on a phone, half Apple's minimum. */
header.site nav a{color:var(--dim);text-decoration:none;font-size:.9rem;
  padding:.62rem .5rem;display:inline-flex;align-items:center;min-height:44px}
header.site nav a:hover,header.site nav a:focus{color:var(--tintText);
  text-decoration:underline}
@media(max-width:760px){header.site nav{display:none}}

footer.site{border-top:1px solid var(--border);margin-top:3.5rem;
  padding:1.6rem 0 3rem;color:var(--dim);font-size:.86rem}
footer.site .wrap{display:flex;gap:.4rem 1.2rem;flex-wrap:wrap;align-items:center}
footer.site a{color:var(--dim);text-decoration:none;
  padding:.62rem 0;display:inline-flex;align-items:center;min-height:44px}
footer.site a:hover,footer.site a:focus{color:var(--tintText);text-decoration:underline}

/* ---- type -----------------------------------------------------------------
   h3 previously rendered at three sizes from 11.52px to 43.2px depending on
   which component it landed in, so the heading LEVEL carried no relationship to
   visual weight — a screen-reader user and a sighted user were reading two
   different documents. One size per level; components restyle with a class. */
h1{font-family:var(--serif);font-size:clamp(2rem,4.4vw,2.9rem);line-height:1.1;
  margin:0 0 .6rem;letter-spacing:-.02em;text-wrap:balance}
h2{font-family:var(--serif);font-size:clamp(1.6rem,3.2vw,2.25rem);line-height:1.15;
  margin:0 0 .6rem;letter-spacing:-.018em;text-wrap:balance}
h3{font-family:var(--serif);font-size:1.2rem;line-height:1.3;margin:0 0 .45rem;
  letter-spacing:-.01em}
p,li{color:var(--text)}
.lede{font-size:1.12rem;line-height:1.6;color:var(--dim);margin-top:0;
  max-width:var(--prose)}
.sub{color:var(--dim);font-size:1.02rem;line-height:1.65;max-width:var(--prose);
  margin:0 0 2rem}
.meta{color:var(--dim);font-size:.85rem}
.eyebrow{display:inline-flex;align-items:center;gap:.55rem;font-size:.74rem;
  font-weight:800;letter-spacing:.15em;text-transform:uppercase;
  color:var(--goldText);margin-bottom:1rem}
.eyebrow::before{content:"";width:26px;height:1px;background:var(--gold)}

section{padding:3.1rem 0}
@media(max-width:820px){section{padding:2.4rem 0}}

/* ---- buttons --------------------------------------------------------------- */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:.5rem;
  background:var(--tint);color:var(--onTint);text-decoration:none;font-weight:700;
  font-size:.95rem;padding:.85rem 1.5rem;border-radius:var(--r-pill);border:0;
  min-height:48px;
  box-shadow:0 10px 30px -10px color-mix(in srgb,var(--tint) 70%,transparent)}
.btn:hover{filter:brightness(1.06)}
.btn.ghost{background:var(--surface);color:var(--text);
  border:1px solid var(--border);box-shadow:none}
.cta{display:flex;flex-wrap:wrap;gap:.7rem;margin-top:1.8rem}

/* ================================ LANDING ================================= */

/* Hero — firelight. A warm radial behind everything, and the portrait lit from
   the same side as the glow. */
.hero{position:relative;overflow:hidden;padding:4.5rem 0 3.5rem;isolation:isolate}
.hero::before{content:"";position:absolute;inset:-30% -10% auto -10%;height:130%;
  z-index:-1;pointer-events:none;
  background:radial-gradient(58% 46% at 22% 30%,
    color-mix(in srgb,var(--tint) 26%,transparent) 0%,
    color-mix(in srgb,var(--tint2) 11%,transparent) 42%, transparent 72%)}
/* 1.25/.75, not 1.05/.95. The headline's first clause is a fixed string —
   "Talk to Sherlock Holmes." — and at the display size it needs ~550px to stay
   on one line. A 483px column broke it after "Sherlock" and left "Holmes."
   stranded, which reads as a layout accident rather than a beat. */
.hero-grid{display:grid;grid-template-columns:1.25fr .75fr;gap:3rem;
  align-items:center}
/* Sized to land the two-clause headline on three lines at desktop and keep the
   badges above the fold. The em is display:block because the sentence is a
   setup and a payoff — running them together loses the beat, and letting them
   wrap arbitrarily put "Holmes." alone on line two. */
.hero-copy h1{font-size:clamp(2.05rem,3.7vw,2.85rem);line-height:1.08;
  letter-spacing:-.028em;margin-bottom:1rem}
.hero-copy h1 em{display:block;font-style:italic;color:var(--tintText)}
.badges{display:flex;flex-wrap:wrap;gap:.42rem;margin:1.6rem 0 0;padding:0;
  list-style:none}
.badges li{font-size:.78rem;color:var(--dim);border:1px solid var(--border);
  border-radius:var(--r-pill);padding:.32rem .75rem;background:var(--surface)}

.lit{margin:0}
.lit-frame{position:relative;border-radius:var(--r-l);overflow:hidden;
  aspect-ratio:1;box-shadow:var(--shadow-lift);background:var(--surface2)}

/* THE HERO IS A STACK NOW, not one portrait. Every slide is in the DOM and the
   first one carries `.on` from the builder, so a reader with no JS gets exactly
   the page that shipped before — one face, fully composed — and hero.js only
   ever adds the rotation on top. */
.slide{position:absolute;inset:0;opacity:0;transition:opacity 900ms ease}
.slide.on{opacity:1}
.slide>img{width:100%;height:100%;object-fit:cover;display:block}
/* The scrim moved off .lit-frame and onto the slide. On the frame it painted
   ABOVE the absolutely-positioned slides — including the caption inside them —
   and buried the quote it exists to make readable. */
.slide::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(200deg,transparent 32%,rgba(23,15,12,.82) 90%)}
.quote{position:absolute;left:1.3rem;right:1.3rem;bottom:1.2rem;z-index:2;
  margin:0;color:#F7EBE3}
.quote p{font-family:var(--serif);font-size:1.02rem;line-height:1.45;
  margin:0 0 .5rem;color:#F7EBE3;text-shadow:0 2px 14px rgba(0,0,0,.75)}
.quote span{font-size:.72rem;letter-spacing:.08em;text-transform:uppercase;
  color:#E8C06A;font-weight:700}
.quote span b{font-weight:700;color:#F7EBE3}
/* Was `.ai-chip`, reading "AI CHARACTER" on every slide. The badge row under
   the headline says "Every reply marked AI" in words a reader can act on; this
   corner is better spent naming the room the face is actually from, which is a
   fact about that face and changes with it. */
.room-chip{position:absolute;top:.9rem;right:.9rem;z-index:2;font-size:.64rem;
  font-weight:800;letter-spacing:.1em;text-transform:uppercase;
  background:rgba(23,15,12,.74);color:#F7EBE3;
  border:1px solid rgba(247,235,227,.28);padding:.28rem .6rem;
  border-radius:var(--r-pill);backdrop-filter:blur(6px)}

/* WCAG 2.2.2 — anything that moves by itself for more than five seconds needs
   a stop. Hidden in the markup and unhidden by hero.js, so it never appears as
   a dead control on a page whose script did not run. */
.lit-pause{position:absolute;top:.85rem;left:.85rem;z-index:3;
  width:32px;height:32px;border-radius:50%;padding:0;cursor:pointer;
  border:1px solid rgba(247,235,227,.28);background:rgba(23,15,12,.74);
  backdrop-filter:blur(6px);display:grid;place-items:center;
  opacity:.55;transition:opacity 180ms ease}
.lit-pause[hidden]{display:none}
.lit:hover .lit-pause,.lit-pause:focus-visible{opacity:1}
.lit-pause::before{content:"";display:block;
  width:9px;height:11px;box-sizing:border-box;
  border-left:3px solid #F7EBE3;border-right:3px solid #F7EBE3}
.lit-pause[data-state="paused"]::before{width:0;height:0;border:0;
  border-left:10px solid #F7EBE3;border-top:6px solid transparent;
  border-bottom:6px solid transparent;margin-left:2px}

/* Marquee — THREE rows drifting alternate ways, carrying the whole roster.
   aria-hidden in the markup: it is decoration, and 254 unlabelled faces in the
   tab order is not navigation. */
.marquee{overflow:hidden;
  mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent)}
.mrow{display:flex;gap:.8rem;width:max-content;padding:.4rem 0}
.mtile{position:relative;flex:none;display:block;line-height:0;
  border-radius:var(--r-m);overflow:hidden}
.mrow img{width:104px;height:104px;border-radius:var(--r-m);object-fit:cover;
  border:1px solid var(--border);background:var(--surface2);display:block}
/* THE NAME, ON THE TILE, ON HOVER.
   Inside the tile rather than below it, because .marquee is overflow:hidden and
   a label hanging off the bottom would be clipped by the very box that makes
   the strip a strip. Scrim under the text: these are paintings, and light
   shoulders and pale skies are both common, so a bare word is legible on some
   tiles and gone on others. */
.mtile b{position:absolute;left:0;right:0;bottom:0;padding:.42rem .4rem .34rem;
  font-size:.66rem;line-height:1.15;font-weight:700;text-align:center;
  color:#F7EBE3;letter-spacing:.01em;
  background:linear-gradient(to top,rgba(23,15,12,.94),rgba(23,15,12,.72) 62%,transparent);
  opacity:0;transition:opacity 130ms ease;pointer-events:none;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Same guard as the pause: on touch there is no hover, so revealing a name on a
   synthesised one would show a label the reader cannot dismiss, over a row that
   is still moving because the pause is (correctly) desktop-only. */
@media (hover: hover) and (pointer: fine){
  .mtile:hover b{opacity:1}
}
@media (prefers-reduced-motion:no-preference){
  /* --drift is written per row by the builder from that row's tile count, so
     the SPEED stays put as the roster grows instead of the duration doing. */
  .mrow{animation:drift var(--drift,78s) linear infinite}
  .mrow.rev{animation-direction:reverse}
  @keyframes drift{to{transform:translateX(-50%)}}
  /* POINT AT IT AND IT STOPS. A tile that slides out from under the cursor
     cannot be read, so pausing is what makes the name usable rather than a
     flourish. Only the hovered ROW stops — the other two keep going, so the
     page does not freeze under one pointer.

     GUARDED ON A REAL POINTER. Touch browsers synthesise :hover on tap and then
     LEAVE IT STUCK until you tap elsewhere — so on a phone, where there is no
     hover to begin with and no name to reveal, a stray tap would have frozen a
     row indefinitely and looked like the strip had crashed. `hover: hover` and
     `pointer: fine` together are the only combination that means "a pointer
     that can rest on something". */
  @media (hover: hover) and (pointer: fine){
    .mrow:hover{animation-play-state:paused}
  }
  /* AND IT STOPS WHEN NOBODY IS LOOKING. Three rows of 628 tiles animating
     while the reader is four sections down is battery spent on a strip that is
     off screen. Set by an IntersectionObserver at the foot of the page. */
  .marquee.idle .mrow{animation-play-state:paused}
  /* HALF SPEED ON A PHONE, and it is a loading fix as much as a taste one.
     The same px/s churns a 402px window far faster than a 1280px one, and
     native lazy-loading does not keep up with tiles that arrive by transform:
     measured 1 blank plate in the 6 fully-visible tiles at 402px versus 0 in 24
     at 1280px. Slowing the row gives the loader the head start the transform
     does not. */
  @media(max-width:820px){.mrow{animation-duration:calc(var(--drift,78s) * 2)}}
}

/* Room bands ----------------------------------------------------------------
   HEADING, then a one-line note, then the rail — all left-aligned on the same
   axis. The note used to sit OPPOSITE the heading, right-aligned, and at three
   or four sentences it rendered as a block of prose marooned in the gap between
   the title and the cards: it pushed every rail most of a screen down and read
   as filler rather than as a label. */
.band{margin-bottom:2.2rem}
.band-head{display:flex;align-items:baseline;gap:.6rem}
.band-head h3{font-family:var(--serif);font-size:1.3rem;letter-spacing:-.012em;
  margin:0}
/* The count is a quiet number beside the name, not a shouted uppercase label.
   "38 KIN" in gold caps competed with the room name for the same attention. */
.band-head .n{font-size:.82rem;font-weight:600;color:var(--dim);
  font-variant-numeric:tabular-nums}
.band-note{margin:.25rem 0 .95rem;color:var(--dim);font-size:.9rem;
  line-height:1.5;max-width:62ch}
.band-note em{font-style:italic}

.band-strip{display:flex;gap:.7rem;overflow-x:auto;padding-bottom:.7rem;
  scroll-snap-type:x proximity;scrollbar-width:thin;
  overscroll-behavior-x:contain}
/* EVERY CARD THE SAME HEIGHT. The name and the tagline are both free text off
   the roster — "Winifred Pemberton" wraps to two lines where "Moses" does not,
   and a three-line tagline sits beside a one-line one — so without a clamp the
   baselines under a rail go ragged and the row stops reading as a row. Two
   lines of tagline is enough to sell a character; the rest is on their page. */
/* 150px, not 132. A NAME IS AN IDENTITY and must not be elided: at 132 the
   single-line clamp rendered "Winifred…", which is worse than any tagline
   truncation. 150 fits the longest name in the roster on one line and gives the
   taglines a couple more words before the ellipsis. */
.band-strip figure{margin:0;flex:none;width:150px;scroll-snap-align:start;
  display:flex;flex-direction:column}
.band-strip img{width:150px;height:150px;object-fit:cover;border-radius:var(--r-m);
  border:1px solid var(--border);background:var(--surface2)}
.band-strip b{display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;
  overflow:hidden;font-size:.85rem;line-height:1.3;margin-top:.55rem}
.band-strip span{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
  overflow:hidden;font-size:.76rem;color:var(--dim);line-height:1.4;
  margin-top:.12rem;min-height:2.24em}

/* Memory section — an HTML phone, labelled as one. */
.split{display:grid;grid-template-columns:minmax(0,320px) minmax(0,1fr);
  gap:3rem;align-items:start}
@media(max-width:820px){.split{grid-template-columns:1fr;gap:2rem}}
.phone-wrap{margin:0}
.phone{border-radius:40px;padding:10px;background:var(--surface);
  border:1px solid var(--border);box-shadow:var(--shadow-lift)}
.phone-screen{border-radius:31px;background:var(--bg);padding:0 0 .8rem;
  overflow:hidden;border:1px solid var(--border)}
.chead{display:flex;align-items:center;gap:.6rem;padding:1rem .9rem .7rem;
  border-bottom:1px solid var(--border)}
.chead img{width:38px;height:38px;border-radius:26%;object-fit:cover}
.cname b{display:block;font-size:.94rem;line-height:1.2}
.cname i{font-style:normal;font-size:.74rem;color:var(--dim)}
.clog{padding:.9rem;display:flex;flex-direction:column;gap:.55rem}
.bub{max-width:86%;padding:.6rem .8rem;border-radius:16px;font-size:.85rem;
  line-height:1.45;margin:0}
.bub.me{align-self:flex-end;background:var(--tint);color:var(--onTint);
  border-bottom-right-radius:5px}
.bub.them{align-self:flex-start;background:var(--surface);
  border:1px solid var(--border);border-bottom-left-radius:5px}
.bub .mem{display:block;margin-top:.45rem;padding-top:.4rem;
  border-top:1px solid var(--border);font-size:.7rem;color:var(--goldText);
  font-weight:700}
.cdisc{text-align:center;font-size:.68rem;color:var(--dim);margin:.3rem 0 0}
.figlabel{margin:.8rem 0 0;font-size:.78rem;color:var(--dim);line-height:1.5}

.memside h3{margin-top:0}
.memside>p{color:var(--dim);line-height:1.65;max-width:var(--prose)}
.memcard{background:var(--surface);border:1px solid var(--border);
  border-radius:var(--r-m);padding:.8rem .95rem;display:flex;gap:.8rem;
  align-items:flex-start;margin:.6rem 0}
.memcard .t{font-size:.62rem;font-weight:800;letter-spacing:.1em;
  text-transform:uppercase;color:var(--goldText);flex:none;width:56px;
  padding-top:.2rem}
.memcard p{margin:0;font-size:.9rem;line-height:1.45}
.memcard .x{margin-left:auto;color:var(--dim);flex:none;font-size:1.1rem;
  line-height:1}
.note{margin-top:1.4rem;padding-top:1.1rem;border-top:1px solid var(--border);
  font-size:.9rem;color:var(--dim);line-height:1.6;max-width:var(--prose)}

/* Screenshot gallery */
.shots{display:grid;gap:1.6rem 1.1rem;margin:0;
  grid-template-columns:repeat(auto-fit,minmax(190px,1fr))}
.shot{margin:0}
.shot picture{display:block;border-radius:26px;padding:7px;background:var(--surface);
  border:1px solid var(--border);box-shadow:var(--shadow-soft)}
.shot img{width:100%;height:auto;display:block;border-radius:20px}
.shot figcaption{margin-top:.7rem;color:var(--dim);font-size:.86rem;line-height:1.45}
@media(max-width:520px){
  .shots{grid-template-columns:repeat(2,1fr);gap:1.2rem .8rem}
  .shot figcaption{font-size:.8rem}
}

/* Feature cards / collections / refusals */
.feat{display:grid;grid-template-columns:1fr;gap:1.05rem}
@media(min-width:760px){.feat{grid-template-columns:repeat(2,1fr)}}
.card{background:var(--surface);border:1px solid var(--border);
  border-radius:var(--r-l);padding:1.4rem 1.5rem}
.card p{margin:0;color:var(--dim);line-height:1.62;font-size:.94rem}
/* `margin:0` on every card paragraph was written when a card held one. With two
   they butted directly together and read as a single run-on block — the break
   the author intended was invisible. Only the gap BETWEEN siblings is restored,
   so a single-paragraph card keeps its tight box. */
.card p + p{margin-top:.72rem}

/* The ember disc. Same shape as the app's own row marks, and the ink is the
   ON-tint token because the glyph sits ON the tint — brand accent drawn on
   brand accent measures under 2:1 and reads as a smudge
   ([[accent-as-ink-on-its-own-tint]]). */
.card-mark{display:grid;place-items:center;width:36px;height:36px;
  border-radius:50%;background:var(--tint2);margin-bottom:.85rem}
.card-mark svg{width:20px;height:20px;fill:none;stroke:var(--onTint);
  stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}

.setlist{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));
  gap:1.4rem 2rem}
/* THE OTHER 25 ARE NAMES, NOT PARAGRAPHS. All 31 at full length is honest and
   unreadable — a wall of grey prose at the exact point in the page where the
   paintings stop. Six carry the argument; the rest carry the number, which is
   all a reader needs from them. */
/* The rail's own full stop — same 150px track as a face so the row keeps its
   rhythm, but a plate rather than a painting so it never reads as a kin. */
.band-strip figure.band-more{display:grid;place-content:center;text-align:center;
  height:150px;border-radius:var(--r-m);background:var(--surface2);
  border:1px dashed var(--border);padding:0 .6rem}
.band-strip figure.band-more b{font-family:var(--serif);font-size:1.5rem;
  color:var(--tintText);-webkit-line-clamp:none;display:block}
.band-strip figure.band-more span{color:var(--dim);-webkit-line-clamp:none;
  display:block}

.setall{margin-top:1.7rem;border-top:1px solid var(--border);padding-top:1rem}
.setall summary{cursor:pointer;font-weight:700;list-style:none;
  display:flex;align-items:center;gap:.5rem;min-height:44px;font-size:.95rem}
.setall summary::-webkit-details-marker{display:none}
.setall summary span{font-weight:400;color:var(--dim);font-size:.88rem}
.setall summary::after{content:'+';margin-left:auto;color:var(--tintText);
  font-size:1.3rem;line-height:1;flex:0 0 auto}
@media (prefers-reduced-motion:no-preference){
  .setall summary::after{transition:transform .2s}}
.setall[open] summary::after{transform:rotate(45deg)}
.setall[open] .setchips{margin-top:.9rem}
.setchips{display:flex;flex-wrap:wrap;gap:.45rem;padding:0;margin:0;
  list-style:none}
.setchips li{font-size:.82rem;color:var(--dim);background:var(--surface);
  border:1px solid var(--border);border-radius:var(--r-pill);
  padding:.36rem .8rem;font-family:var(--serif)}
.set{border-left:2px solid var(--gold);padding-left:1rem}
.set b{font-family:var(--serif);font-size:1.05rem;display:block;margin-bottom:.22rem}
/* NO CLAMP AT ALL. The builder stops at a sentence end under a 240 ceiling the
   longest blurb in the set (202) clears — and then a CSS line-clamp put the
   ellipsis straight back where no build check can see it. Four lines was still
   ~140 characters in a 240px column, so "The Nile" went on ending mid-word
   after the Python side had been fixed. Six items with ragged bottoms is what a
   grid is for; a truncation nobody can grep for is not. */
.set span{
  display:block;color:var(--dim);font-size:.88rem;line-height:1.5;
  min-height:2.7em}
.safety{background:var(--surface2)}
/* Two columns on wide screens: the argument on the left, the list on the right.
   A single measured column inside a full-bleed tinted band left a third of the
   band visibly empty, which reads as unfinished rather than as white space. */
.safety-grid{display:grid;gap:1rem 3rem;align-items:start}
@media(min-width:900px){.safety-grid{grid-template-columns:minmax(0,.85fr) minmax(0,1fr)}}
.refusals{list-style:none;padding:0;margin:0;display:grid;gap:.6rem}
.refusals li{position:relative;padding-left:1.6rem;line-height:1.6}
.refusals li::before{content:"—";position:absolute;left:0;color:var(--tintText);
  font-weight:700}

/* FAQ */
.faq-section details{border-bottom:1px solid var(--border);padding:.95rem 0}
.faq-section summary{cursor:pointer;font-weight:700;list-style:none;
  display:flex;justify-content:space-between;align-items:center;gap:1rem;
  min-height:44px}
.faq-section summary::-webkit-details-marker{display:none}
.faq-section summary::after{content:'+';color:var(--tintText);font-size:1.3rem;
  font-weight:400;line-height:1;flex:0 0 auto}
@media (prefers-reduced-motion:no-preference){
  .faq-section summary::after{transition:transform .2s}}
.faq-section details[open] summary::after{transform:rotate(45deg)}
.faq-section details p{margin:.7rem 0 0;color:var(--dim);line-height:1.65;
  max-width:var(--prose)}

@media(max-width:820px){
  .hero{padding:2.6rem 0 2.2rem}
  .hero-grid{grid-template-columns:1fr;gap:2.2rem}
  .lit{max-width:340px;width:100%;margin:0 auto}
}
/* Stacked, the two calls to action sized to their own labels and came out
   ragged — "See how the memory works" against a much shorter "Meet the cast".
   Two buttons in a column read as a pair or as a mistake. */
@media(max-width:560px){
  .cta{flex-direction:column;align-items:stretch}
  .cta .btn{width:100%}
}

/* =============================== DOCUMENTS ================================
   The legal pages keep a plain reading column, deliberately: a privacy policy
   should read like a document, not a brochure. They share the palette above so
   the whole site is one product, and they carry the same theme control — the
   picker used to exist on index.html alone, so choosing Light there and
   following a link gave you a dark policy page with nothing to change it. */
.wrap.doc{max-width:46rem;padding-top:1rem}
.doc h1{margin-top:1.5rem}
.doc h2{font-size:1.35rem;margin:2.4rem 0 .5rem}
.doc h3{font-size:1.05rem;margin:1.6rem 0 .3rem}
.doc p,.doc li{line-height:1.72}
.doc ul{padding-left:1.2rem}
.doc li{margin:.4rem 0}
.card h3,.card h2.card-heading{margin-top:0}
.card h2.card-heading{font-size:1.05rem;font-family:var(--serif)}
.card{margin:1rem 0}
table{width:100%;border-collapse:collapse;margin:1rem 0;font-size:.95rem}
th,td{text-align:left;padding:.6rem .55rem;border-bottom:1px solid var(--border);
  vertical-align:top}
th{color:var(--dim);font-weight:700;font-size:.8rem;text-transform:uppercase;
  letter-spacing:.05em}
.scroll{overflow-x:auto}
.callout{border-left:3px solid var(--gold);padding:.75rem 0 .75rem 1rem;
  margin:1.25rem 0}
.crisis{border:2px solid var(--tint);border-radius:var(--r-card);
  padding:1.25rem 1.35rem;margin:1.5rem 0}
.crisis strong{font-size:1.05rem}
ul.plain{list-style:none;padding:0}
ul.plain li{padding:.35rem 0;border-bottom:1px solid var(--border)}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms !important;
    animation-iteration-count:1 !important;transition-duration:.001ms !important}
}
@media (forced-colors:active){
  .card,.memcard,.phone,.phone-screen,.band-strip img,.mrow img{
    background:Canvas;border-color:CanvasText}
}

/* Screenshot gallery — the THEMED PAIR variant.
   `.shot picture` above styles the frame and assumes one <picture> whose dark
   twin arrives via <source media="(prefers-color-scheme:dark)">. That is right
   on a page whose scheme is only ever the OS preference, and wrong on THIS one:
   it has an explicit theme picker, so the media query alone fails in both
   directions — a reader on a light OS who picks Dark gets light screenshots on
   a dark page, and one on a dark OS who picks Light gets dark shots on a light
   page. A <picture> cannot help, because it resolves before any class on <html>
   is consulted.
   So the frame is repeated verbatim on a plain box, both images ship, and the
   swap uses THIS FILE'S OWN tri-state selectors — the same shape as the palette
   block at the top. The hidden twin is display:none, so it also leaves the
   accessibility tree and is never announced twice. */
.shot .pic{display:block;border-radius:26px;padding:7px;background:var(--surface);
  border:1px solid var(--border);box-shadow:var(--shadow-soft)}
.shot .pic img{width:100%;height:auto;display:block;border-radius:20px}
/* EVERY SWAP RULE CARRIES `img`, and it has to. Written as `.shot .dk` the hide
   is (0,2,0) and loses to `.shot .pic img` at (0,2,1) directly above — so in
   the DEFAULT state (light OS, no explicit pick, which is most readers) BOTH
   screenshots rendered, stacked, one above the other. The themed states all
   worked, because `:root:not([data-theme="light"]) .shot .dk` is (0,4,0) and
   outranks it; only the one state with no attribute on <html> was broken, which
   is exactly the state a quick look in dark mode would not show you. */
.shot .pic img.dk{display:none}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .shot .pic img.lt{display:none}
  :root:not([data-theme="light"]) .shot .pic img.dk{display:block}
}
html[data-theme="dark"] .shot .pic img.lt{display:none}
html[data-theme="dark"] .shot .pic img.dk{display:block}
html[data-theme="light"] .shot .pic img.lt{display:block}
html[data-theme="light"] .shot .pic img.dk{display:none}
