/* ============================================================================
   VATES design tokens — VATES_UI_VISUAL_DESIGN_PATCH_V1
   Inviting, friendly, modern, calm, premium. Never clinical, never cyberpunk.

   Every colour pairing used for text or a control was checked numerically against WCAG AA before it was
   written here (27 pairings, light and dark, all >= 4.5:1 for text and >= 3:1 for focus rings). Vivid hues
   are split into a FILL token and a darker -ink token: the fill carries white text, the ink is the same hue
   made legible as text on the warm background. Choosing one value for both is how a friendly palette
   quietly fails contrast.

   State is never signalled by colour alone: every coloured state also carries a word, an icon, or both.
   ========================================================================== */
:root{
  /* --- surface */
  --bg:#F7F5F1;            /* warm off-white, not pure white */
  --card:#FFFFFF;
  --tint-blue:#EAF0FE; --tint-coral:#FDEDE6; --tint-mint:#E3F5F1;
  --line:#E4DFD6; --line-strong:#D2CCC1;

  /* --- text: deep navy, not pure black */
  --ink:#14203A; --ink-soft:#4A5568; --ink-faint:#6B7280; --on-fill:#FFFFFF;

  /* --- primary: soft electric blue */
  --primary:#2F6BF3; --primary-ink:#2456C9; --primary-deep:#1E45A8;
  /* --- secondary: warm coral, used sparingly */
  --coral:#CA4D28; --coral-ink:#B44222;
  /* --- accent: fresh mint / teal */
  --mint:#0D8574; --mint-ink:#0B6F61;

  /* --- status */
  --good:#17803D; --good-tint:#E6F4EA;
  --warn:#A65A00; --warn-tint:#FBF0E0;
  --err:#C62B21;  --err-tint:#FBEAE8;

  /* --- spacing scale */
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:20px; --s6:24px; --s7:32px; --s8:44px;
  /* --- radius */
  --r-xs:6px; --r-sm:12px; --r:18px; --r-lg:24px; --r-xl:28px; --r-pill:999px;
  --r-hair:3px;
  /* --- sizing */
  --tap:58px; --tap-sm:48px;
  /* --- type */
  --font:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
  --t-body:18px; --t-lead:20px; --t-h1:32px; --t-h2:25px; --t-h3:20px; --t-small:16px;
  /* --- elevation: soft, never heavy */
  --shadow:0 1px 2px rgba(20,32,58,.04), 0 6px 18px rgba(20,32,58,.06);
  --shadow-lift:0 2px 6px rgba(20,32,58,.06), 0 14px 34px rgba(20,32,58,.10);
  /* --- motion */
  --ease:cubic-bezier(.4,0,.2,1); --fast:.14s; --slow:.5s; --breathe:3.2s;
  --focus:0 0 0 4px rgba(47,107,243,.28);
}
@media (prefers-color-scheme:dark){
  :root{
    --bg:#111827; --card:#1B2333;
    --tint-blue:#16233F; --tint-coral:#33201A; --tint-mint:#122E2A;
    --line:#2A3346; --line-strong:#3A4459;
    --ink:#F2F4F8; --ink-soft:#B6BECD; --ink-faint:#8B93A3; --on-fill:#0B1220;
    --primary:#5B8DFF; --primary-ink:#8FB2FF; --primary-deep:#3D74F0;
    --coral:#FF8A66; --coral-ink:#FF8A66;
    --mint:#3DD6BC; --mint-ink:#3DD6BC;
    --good:#4ADE80; --good-tint:#14301F;
    --warn:#FBBF24; --warn-tint:#332711;
    --err:#FF7A6E;  --err-tint:#3A1D1A;
    --shadow:0 1px 2px rgba(0,0,0,.3), 0 6px 18px rgba(0,0,0,.30);
    --shadow-lift:0 2px 6px rgba(0,0,0,.35), 0 14px 34px rgba(0,0,0,.42);
    --focus:0 0 0 4px rgba(91,141,255,.35);
  }
}

*{box-sizing:border-box}
html,body{margin:0;padding:0}
body{
  background:var(--bg); color:var(--ink); font-family:var(--font);
  font-size:var(--t-body); line-height:1.55; -webkit-font-smoothing:antialiased;
  -webkit-text-size-adjust:100%;
}
h1,h2,h3{line-height:1.22; margin:0 0 var(--s3); font-weight:650; letter-spacing:-.02em}
h1{font-size:var(--t-h1)} h2{font-size:var(--t-h2)} h3{font-size:var(--t-h3)}
p{margin:0 0 var(--s4)}
:focus-visible{outline:3px solid var(--primary); outline-offset:3px; border-radius:var(--r-sm)}
/* Headings receive focus on screen change so a screen reader announces the new screen. That focus is for
   assistive tech, not a sighted affordance, so it must not paint a ring around a paragraph of text.
   Real controls keep theirs via :focus-visible above. */
[tabindex="-1"]:focus,[tabindex="-1"]:focus-visible{outline:none}
.sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);
  white-space:nowrap;border:0}
.hidden{display:none!important}
.muted{color:var(--ink-soft)}
.faint{color:var(--ink-faint); font-size:var(--t-small)}
.lead{font-size:var(--t-lead); color:var(--ink-soft)}

/* ---------- shell ---------- */
.app{max-width:660px; margin:0 auto; min-height:100dvh; display:flex; flex-direction:column}
.screen{display:none; flex:1; flex-direction:column; padding:var(--s6) var(--s5) var(--s5)}
.screen.on{display:flex}
.center{justify-content:center}
.grow{flex:1; min-height:var(--s6)}

/* ---------- brand ---------- */
.brand{text-align:center; margin:6vh 0 var(--s7)}
.mark{
  width:72px;height:72px;border-radius:var(--r-lg);margin:0 auto var(--s5);
  background:linear-gradient(145deg,var(--primary),var(--primary-deep));
  display:grid;place-items:center;color:#fff;font-size:32px;font-weight:650;letter-spacing:.02em;
  box-shadow:var(--shadow-lift);
}
.mark.mint{background:linear-gradient(145deg,var(--mint),var(--mint-ink))}
.mark.coral{background:linear-gradient(145deg,#E9825F,var(--coral))}
.wordmark{font-size:34px;font-weight:650;letter-spacing:.16em;margin:0}
.tagline{color:var(--ink-soft); font-size:17px; margin:var(--s2) 0 0; letter-spacing:.01em}

/* ---------- forms ---------- */
label{display:block; font-weight:650; margin:0 0 var(--s2); font-size:17px}
input[type=text],input[type=email],textarea,select{
  width:100%; min-height:var(--tap); padding:15px var(--s4); font:inherit; font-size:var(--t-body);
  color:var(--ink); background:var(--card); border:2px solid var(--line-strong);
  border-radius:var(--r); -webkit-appearance:none; transition:border-color var(--fast) var(--ease),
  box-shadow var(--fast) var(--ease);
}
textarea{min-height:150px; resize:vertical; line-height:1.5}
input::placeholder,textarea::placeholder{color:var(--ink-faint)}
input:focus,textarea:focus,select:focus{border-color:var(--primary); outline:none; box-shadow:var(--focus)}

.btn{
  display:flex; align-items:center; justify-content:center; gap:var(--s2);
  width:100%; min-height:var(--tap); padding:15px var(--s5); margin:var(--s4) 0 0;
  font:inherit; font-size:19px; font-weight:650; text-align:center; cursor:pointer;
  background:var(--primary); color:var(--on-fill); border:2px solid var(--primary);
  border-radius:var(--r); box-shadow:var(--shadow);
  transition:transform var(--fast) var(--ease), filter var(--fast) var(--ease),
             border-color var(--fast) var(--ease);
}

.btn:active{transform:scale(.985)}
.btn[disabled]{opacity:.55; cursor:default; filter:none; transform:none}
.btn.secondary{background:var(--card); color:var(--primary-ink); border-color:var(--line-strong);
  box-shadow:none}

.btn.mint{background:var(--mint); border-color:var(--mint); color:var(--on-fill)}
.btn.quiet{background:transparent;border-color:transparent;color:var(--ink-soft);font-weight:550;
  min-height:var(--tap-sm);margin:var(--s2) 0 0;box-shadow:none}
.btn.danger{background:transparent;color:var(--err);border-color:var(--line-strong);box-shadow:none}
.back{
  display:inline-flex;align-items:center;gap:var(--s2);background:none;border:none;padding:10px 6px;
  margin:0 0 var(--s3) -6px; font:inherit; font-size:17px; color:var(--ink-soft); cursor:pointer;
  min-height:var(--tap-sm); transition:color var(--fast) var(--ease);
}


/* ---------- code entry ---------- */
.coderow{display:flex; gap:9px; justify-content:space-between; margin:var(--s2) 0 var(--s1)}
.codebox{
  flex:1; min-width:0; height:70px; text-align:center; font-size:28px; font-weight:650;
  font-variant-numeric:tabular-nums; padding:0; border-radius:var(--r-sm);
  border:2px solid var(--line-strong); background:var(--card); color:var(--ink);
}
.codebox:focus{border-color:var(--primary); box-shadow:var(--focus); outline:none}
.codebox.filled{border-color:var(--primary); background:var(--tint-blue)}

/* ---------- notices: colour + word + icon, never colour alone ---------- */
.notice{
  display:flex; gap:var(--s3); align-items:flex-start;
  padding:var(--s4); border-radius:var(--r); margin:0 0 var(--s4); font-size:17px;
  border:2px solid transparent; background:var(--card);
}
.notice .ni{font-size:20px; line-height:1.3; flex:0 0 auto}
.notice .nb{flex:1; min-width:0}
.notice strong{display:block; font-size:18px; margin-bottom:2px}
.notice.err{background:var(--err-tint); border-color:var(--err)}
.notice.ok{background:var(--good-tint); border-color:var(--good)}
.notice.info{background:var(--tint-blue); border-color:var(--primary)}
.notice.warn{background:var(--warn-tint); border-color:var(--warn)}

/* ---------- chat ---------- */
.topbar{
  display:flex; align-items:center; justify-content:space-between; gap:var(--s3);
  padding:var(--s3) var(--s5); border-bottom:1px solid var(--line); position:sticky; top:0;
  background:var(--bg); z-index:5;
}
.topbar .name{font-weight:650; letter-spacing:.14em; font-size:18px}
.thread{flex:1; overflow-y:auto; padding:var(--s5) var(--s5) var(--s2);
  display:flex; flex-direction:column; gap:var(--s5)}
/* An empty thread must not claim half the screen. On the home state the greeting and the starters should
   sit near the top where a thumb and an eye both expect them. */
.thread:empty{flex:0 0 auto; padding:0}
.bubble{max-width:88%; padding:15px 18px; border-radius:var(--r-lg); font-size:var(--t-body);
  white-space:pre-wrap; overflow-wrap:anywhere}
.bubble.me{align-self:flex-end; background:var(--primary); color:var(--on-fill);
  border-bottom-right-radius:8px}
.bubble.v{align-self:flex-start; background:var(--card); border:1px solid var(--line);
  border-bottom-left-radius:8px; box-shadow:var(--shadow)}

/* ---------- the VATES thinking state (patch §4) ----------
   Branded, not a spinner: the mark itself breathes blue -> teal -> blue while the words stay still, so the
   motion reads as calm attention rather than as a machine working. */
.thinking{display:flex; align-items:center; gap:var(--s3); align-self:flex-start;
  padding:var(--s2) var(--s1); color:var(--ink-soft); font-size:17px}
.think-mark{
  position:relative; isolation:isolate;
  width:38px;height:38px;border-radius:var(--r-sm);flex:0 0 auto;
  display:grid;place-items:center;color:#fff;font-size:17px;font-weight:650;
  background:linear-gradient(145deg,var(--primary),var(--primary-deep));
  animation:breathe var(--breathe) var(--ease) infinite;
}
/* The blue -> teal shift and the expanding glow were originally written as animated `background` and
   `box-shadow` keyframes. Both look right and both are wrong: a gradient background and a shadow spread
   cannot be composited, so every frame of a state that runs for the whole length of an answer forced a
   repaint. Same visual, rebuilt as two stacked layers whose OPACITY and SCALE animate instead — which
   the compositor handles on its own thread. Caught by tests/test_world_animation.py, not by eye. */
.think-mark::before,.think-mark::after{content:""; position:absolute; z-index:-1; border-radius:inherit}
.think-mark::after{inset:0; background:linear-gradient(145deg,var(--mint),var(--mint-ink));
  opacity:0; animation:tintSwap var(--breathe) var(--ease) infinite}
.think-mark::before{inset:-11px; border-radius:var(--r);
  background:radial-gradient(circle, rgba(47,107,243,.34) 0%, rgba(47,107,243,0) 70%);
  opacity:0; animation:haloPulse var(--breathe) var(--ease) infinite}
@keyframes breathe{0%,100%{transform:scale(1)} 50%{transform:scale(1.07)}}
@keyframes tintSwap{0%,100%{opacity:0} 50%{opacity:1}}
@keyframes haloPulse{0%,100%{opacity:.55; transform:scale(.86)} 50%{opacity:0; transform:scale(1.12)}}
/* patch §5: no pulse, no scale. The state is still announced, in words. */
@media (prefers-reduced-motion:reduce){
  /* `*` does NOT match pseudo-elements. The original rule left every ::before/::after animation
     running under reduced motion — invisible while the only such animation was decorative, and a
     real failure the moment the thinking mark's colour shift and glow moved onto its pseudos to
     stop them repainting every frame. Caught by measuring the rendered pixel, not by reading. */
  *,*::before,*::after{animation:none!important; transition:none!important}
  .think-mark{animation:none; transform:none; box-shadow:none}
}

.answer-tools{display:flex; gap:var(--s2); flex-wrap:wrap; margin-top:var(--s3)}
.chip{
  background:transparent; border:1.5px solid var(--line-strong); color:var(--ink-soft);
  border-radius:var(--r-pill); padding:10px 16px; font:inherit; font-size:var(--t-small);
  cursor:pointer; min-height:var(--tap-sm);
  transition:border-color var(--fast) var(--ease), color var(--fast) var(--ease);
}

.chip[aria-pressed=true]{background:var(--tint-mint); border-color:var(--mint);
  color:var(--mint-ink); font-weight:650}
details.more{margin-top:var(--s3); border-top:1px solid var(--line); padding-top:var(--s2)}
/* A summary that does not look openable is not an affordance. The marker is drawn explicitly because
   `display:flex` on a <summary> suppresses the browser's own triangle. */
details.more summary{cursor:pointer; color:var(--ink-soft); font-size:var(--t-small);
  min-height:var(--tap-sm); display:flex; align-items:center; gap:var(--s2); list-style:none;
  transition:color var(--fast) var(--ease)}
details.more summary::-webkit-details-marker{display:none}
details.more summary::before{content:"›"; display:inline-block; font-size:20px; line-height:1;
  color:var(--primary-ink); transition:transform var(--fast) var(--ease)}
details.more[open] summary::before{transform:rotate(90deg)}

details.more .src{font-size:var(--t-small); color:var(--ink-soft); padding:var(--s2) 0;
  border-bottom:1px solid var(--line)}
details.more .src:last-child{border-bottom:none}

/* ---------- composer ---------- */
.composer{
  position:sticky; bottom:0; background:var(--bg); border-top:1px solid var(--line);
  padding:var(--s3) var(--s4) calc(var(--s3) + env(safe-area-inset-bottom));
  display:flex; gap:var(--s3); align-items:flex-end;
}
.composer textarea{min-height:var(--tap); max-height:160px; margin:0; border-radius:var(--r-xl);
  padding:15px 18px; resize:none}   /* the drag handle is noise; the box auto-grows as you type */
.iconbtn{
  flex:0 0 auto; width:var(--tap); height:var(--tap); border-radius:50%;
  border:2px solid var(--line-strong); background:var(--card); color:var(--ink);
  font-size:25px; cursor:pointer; display:grid; place-items:center;
  transition:border-color var(--fast) var(--ease);
}

.iconbtn.primary{background:var(--primary); border-color:var(--primary); color:var(--on-fill)}

/* ---------- starters ---------- */
.starters{display:grid; gap:var(--s3); margin:var(--s5) 0 0}
.starter{
  display:flex; align-items:center; gap:var(--s4);
  text-align:left; background:var(--card); border:1.5px solid var(--line); border-radius:var(--r);
  padding:var(--s4) var(--s5); font:inherit; font-size:var(--t-body); color:var(--ink);
  cursor:pointer; min-height:var(--tap); box-shadow:var(--shadow);
  transition:border-color var(--fast) var(--ease), transform var(--fast) var(--ease);
}

.starter[aria-pressed=true]{border-color:var(--primary); background:var(--tint-blue)}
.starter .ic{width:44px;height:44px;border-radius:var(--r-sm);display:grid;place-items:center;
  font-size:21px;flex:0 0 auto;background:var(--tint-blue)}
.starter .ic.mint{background:var(--tint-mint)} .starter .ic.coral{background:var(--tint-coral)}
.starter .tx{min-width:0}
.starter b{display:block; font-weight:650}
.starter span{color:var(--ink-soft); font-size:var(--t-small); display:block}

/* ---------- sheet ---------- */
.scrim{position:fixed; inset:0; background:rgba(20,32,58,.45); display:none; z-index:40}
.scrim.on{display:block}
.sheet{
  position:fixed; left:0; right:0; bottom:0; z-index:41; background:var(--card);
  border-radius:var(--r-lg) var(--r-lg) 0 0;
  padding:var(--s2) var(--s5) calc(var(--s6) + env(safe-area-inset-bottom));
  max-width:660px; margin:0 auto; box-shadow:var(--shadow-lift); display:none;
}
.sheet.on{display:block}
.grabber{width:44px;height:5px;border-radius:var(--r-hair);background:var(--line-strong);margin:var(--s2) auto var(--s5)}
.sheetbtn{
  display:flex; align-items:center; gap:var(--s4); width:100%; min-height:66px; padding:var(--s3) var(--s3);
  background:none; border:none; border-bottom:1px solid var(--line); font:inherit; font-size:19px;
  color:var(--ink); cursor:pointer; text-align:left;
}
.sheetbtn:last-of-type{border-bottom:none}
.sheetbtn .ic{width:46px;height:46px;border-radius:var(--r-sm);background:var(--tint-blue);
  display:grid;place-items:center;font-size:22px;flex:0 0 auto}
.sheetbtn .ic.mint{background:var(--tint-mint)} .sheetbtn .ic.coral{background:var(--tint-coral)}

/* ---------- cards / lists ---------- */
.card{background:var(--card); border:1px solid var(--line); border-radius:var(--r);
  padding:var(--s4) var(--s5); margin:0 0 var(--s3); box-shadow:var(--shadow)}
.row{display:flex; align-items:center; gap:var(--s3); justify-content:space-between; flex-wrap:wrap}
.row .t{font-weight:650; min-width:0; overflow-wrap:anywhere}
.kind{font-size:14px; color:var(--primary-ink); background:var(--tint-blue); border-radius:var(--r-pill);
  padding:5px 12px; font-weight:650}
.kind.mint{color:var(--mint-ink); background:var(--tint-mint)}
.kind.coral{color:var(--coral-ink); background:var(--tint-coral)}
.empty{text-align:center; color:var(--ink-soft); padding:var(--s8) var(--s5)}
.empty .big{font-size:46px; margin-bottom:var(--s3)}

/* ---------- nav ---------- */
.nav{
  display:flex; border-top:1px solid var(--line); background:var(--bg);
  padding-bottom:env(safe-area-inset-bottom); position:sticky; bottom:0; z-index:6;
}
.nav button{
  flex:1; min-height:62px; background:none; border:none; font:inherit; font-size:15px; font-weight:650;
  color:var(--ink-faint); cursor:pointer; display:flex; flex-direction:column; align-items:center;
  gap:2px; padding:var(--s2) var(--s1); border-top:3px solid transparent; margin-top:-1px;
}
.nav button .ic{font-size:22px}
.nav button[aria-current=page]{color:var(--primary-ink); border-top-color:var(--primary)}

/* ---------- progress dots ---------- */
.dots{display:flex; gap:var(--s2); justify-content:center; margin:0 0 var(--s6)}
.dots i{width:9px;height:9px;border-radius:var(--r-pill);background:var(--line-strong);
  transition:width var(--fast) var(--ease)}
.dots i.on{background:var(--primary); width:28px; border-radius:var(--r-xs)}

/* ---------- stats ---------- */
.stat{display:flex; gap:var(--s3); margin:0 0 var(--s4)}
.stat .box{flex:1; background:var(--card); border:1px solid var(--line); border-radius:var(--r);
  padding:var(--s4); text-align:center; box-shadow:var(--shadow)}
.stat .n{font-size:34px; font-weight:650; line-height:1.1; font-variant-numeric:tabular-nums}
.stat .l{font-size:15px; color:var(--ink-soft)}

/* ---------- §12 localisation resilience ----------
   Nothing is sized to the length of an English word. Buttons and chips wrap instead of clipping, rows
   reflow, and the code boxes stay on one line because digits are digits in every language we ship. */
.btn,.starter,.sheetbtn,.chip,.nav button{white-space:normal; overflow-wrap:anywhere; hyphens:auto}
.btn{line-height:1.3}
.nav button{font-size:clamp(12px,3.4vw,15px)}
.kind{white-space:normal}

@media (min-width:700px){
  body{font-size:19px}
  .app{border-left:1px solid var(--line); border-right:1px solid var(--line)}
}
/* honour a user who has enlarged text: nothing is pinned to a viewport-relative height */
@media (max-height:640px){ .brand{margin:var(--s6) 0 var(--s5)} .mark{width:60px;height:60px} }

/* ============================================================================
   VATES_LIVING_WORLD_ANIMATION_PATCH_V1
   A faint world-intelligence sphere behind the arrival screens, and a small orbiting
   world beside the mark while Vates is thinking.

   Three rules govern everything below.

   1. It must never compete with the next action. The world is `position:fixed`,
      `pointer-events:none`, and sits under `.app` in the stacking order. It is out of flow, so it
      cannot move a control by a single pixel, and a vignette darkens the ground behind the form so
      the input and the button stay the brightest things on the screen.
   2. Only `transform` and `opacity` are animated — the two properties the compositor can handle
      without a layout or paint pass. A grep-level test enforces this, because the cheapest way for
      this file to start draining a phone battery is for someone to animate `height` here later.
   3. Every animated element carries a resting value outside its keyframes, so that under
      `prefers-reduced-motion` — where the global rule at the top of this file kills all animation
      with `!important` — the globe is still a globe rather than an invisible one.

   Scope, decided deliberately: the cosmic ground is applied to the ARRIVAL screens only (welcome,
   code, waitlist, onboarding). Chat, Knowledge and You keep the warm AA-verified surface, because
   those are the screens a low-digital-literacy user reads every day, and a dark decorative ground is
   a worse place to read a long answer than a plain one.
   ========================================================================== */
:root{
  /* --- cosmic ground (arrival screens only) */
  --cos-1:#1B0E33;        /* deep indigo-violet */
  --cos-2:#0E0A22;        /* near-black indigo */
  --cos-field:#241640;    /* input fill */
  --cos-line:#806EAA;     /* field + hairline: 4.09:1 on ground, 3.74:1 on fill (non-text AA >=3) */
  --cos-ink:#FFFFFF;      /* 18.15:1 */
  --cos-lead:#D9CFE8;     /* 12.13:1 */
  --cos-faint:#BFB2D6;    /*  9.12:1 */
  --cos-ph:#A99BC4;       /*  6.47:1 on the field fill */
  --cos-peach:#FFB088;    /* 10.21:1 */
  --cos-violet:#C08CFF;   /*  7.29:1 */
  --cos-btn-ink:#2A0F3D;  /* 7.75:1 – 10.95:1 across every stop of the warm gradient */
  /* the warm gradient EL asked for: yellow-peach -> pink -> violet */
  --cos-grad:linear-gradient(102deg,#FFC48A 0%,#FF9BC4 52%,#C79BFF 100%);
  /* --- world motion: idle is deliberately slower than anything a person would call "moving" */
  --w-spin-a:150s; --w-spin-b:210s; --w-spin-c:280s;
  --w-drift:34s; --w-node:7s;
  --w-opacity:.34;
}

/* ---------- the world layer ---------- */
.cosmos{
  position:fixed; inset:0; z-index:0; display:none; overflow:hidden;
  pointer-events:none;            /* it can never take a tap meant for a control */
  contain:strict;                 /* nothing inside can invalidate layout outside */
}
body.cosmic .cosmos{display:block}
.app{position:relative; z-index:1}   /* every control sits above the world, always */

.cos-ground{position:absolute; inset:0;
  background:
    radial-gradient(120% 90% at 78% 96%, rgba(255,150,110,.30) 0%, rgba(255,150,110,0) 58%),
    radial-gradient(95% 70% at 12% 4%, rgba(150,110,255,.26) 0%, rgba(150,110,255,0) 62%),
    linear-gradient(168deg,var(--cos-1) 0%,var(--cos-2) 72%);
}
/* the warm planet limb, mostly off-screen bottom-right, as in EL's reference */
.cos-limb{
  position:absolute; right:-46vw; bottom:-42vh; width:96vw; height:96vw;
  border-radius:50%; opacity:.5;
  background:radial-gradient(circle at 32% 30%, rgba(255,196,138,.50) 0%, rgba(255,120,150,.26) 38%,
             rgba(150,80,200,.10) 62%, rgba(150,80,200,0) 74%);
  animation:limbGlow 46s var(--ease) infinite;
}
/* a vignette so the form is always the brightest region — the low-literacy rule, in CSS */
.cos-veil{position:absolute; inset:0;
  background:linear-gradient(180deg, rgba(14,10,34,0) 22%, rgba(14,10,34,.42) 56%,
             rgba(14,10,34,.66) 80%, rgba(14,10,34,.78) 100%)}

.world{
  position:absolute; left:50%; top:clamp(40px,11vh,120px);
  width:min(150vw,620px); aspect-ratio:1; transform:translate3d(-50%,0,0);
  opacity:var(--w-opacity);
  animation:worldDrift var(--w-drift) var(--ease) infinite;
  will-change:transform;
}
.world .sph{fill:none; stroke:var(--cos-violet); stroke-width:.7; opacity:.30}
.world .arc{fill:none; stroke:var(--cos-peach); stroke-width:.9; opacity:.34}
.world .lnk{stroke:var(--cos-violet); stroke-width:.6; opacity:.22}
.world .nd{fill:var(--cos-peach); opacity:.55}     /* resting value: still a node with motion off */
.world .nd.v{fill:var(--cos-violet)}
.world .st{fill:#FFFFFF; opacity:.5}

/* Rotation is applied to groups whose geometry is centred on the sphere, so `fill-box` puts the
   origin exactly at the centre without depending on `transform-box:view-box` support. */
.w-spin{transform-box:fill-box; transform-origin:50% 50%; will-change:transform}
.w-a{animation:wspin var(--w-spin-a) linear infinite}
.w-b{animation:wspin var(--w-spin-b) linear infinite reverse}
.w-c{animation:wspin var(--w-spin-c) linear infinite}

.world .nd{animation:nodeBreathe var(--w-node) var(--ease) infinite}
.world .nd:nth-of-type(2n){animation-delay:-2.3s}
.world .nd:nth-of-type(3n){animation-delay:-4.1s; animation-duration:9s}
.world .nd:nth-of-type(5n){animation-delay:-5.6s; animation-duration:11s}

@keyframes wspin{from{transform:rotate(0deg)} to{transform:rotate(360deg)}}
@keyframes worldDrift{
  0%,100%{transform:translate3d(-50%,0,0) scale(1)}
  50%    {transform:translate3d(-50%,-10px,0) scale(1.02)}
}
@keyframes nodeBreathe{0%,100%{opacity:.30} 50%{opacity:.80}}
@keyframes limbGlow{0%,100%{opacity:.44; transform:scale(1)} 50%{opacity:.58; transform:scale(1.03)}}

/* Battery: a world nobody is looking at should not be drawn. Set by app.js on visibilitychange. */
body.world-paused .cosmos *{animation-play-state:paused}

/* ---------- arrival screens on the cosmic ground ---------- */
body.cosmic{background:var(--cos-2); color:var(--cos-ink)}
body.cosmic h1,body.cosmic h2,body.cosmic h3{color:var(--cos-ink)}
body.cosmic .lead{color:var(--cos-lead)}
body.cosmic .faint{color:var(--cos-faint)}
body.cosmic label{color:var(--cos-ink)}
body.cosmic .tagline{color:var(--cos-peach)}
body.cosmic .wordmark{color:var(--cos-ink)}
body.cosmic .app{border-left:none; border-right:none}
body.cosmic input[type=text],body.cosmic input[type=email],body.cosmic textarea,body.cosmic .codebox{
  background:rgba(36,22,64,.78); border-color:var(--cos-line); color:var(--cos-ink);
}
body.cosmic input::placeholder,body.cosmic textarea::placeholder{color:var(--cos-ph)}
body.cosmic input:focus,body.cosmic textarea:focus,body.cosmic .codebox:focus{
  border-color:var(--cos-peach); box-shadow:0 0 0 4px rgba(255,176,136,.30)
}
body.cosmic .codebox.filled{border-color:var(--cos-peach); background:rgba(255,176,136,.16)}
body.cosmic :focus-visible{outline-color:var(--cos-peach)}
body.cosmic .btn{
  background:var(--cos-grad); color:var(--cos-btn-ink); border-color:transparent;
  box-shadow:0 6px 26px rgba(255,140,160,.28);
}
body.cosmic .btn.quiet{background:none; color:var(--cos-lead); box-shadow:none}
body.cosmic .btn.secondary{background:rgba(36,22,64,.78); color:var(--cos-ink); border-color:var(--cos-line)}
body.cosmic .back{color:var(--cos-lead)}
body.cosmic .dots i{background:rgba(255,255,255,.28)}
body.cosmic .dots i.on{background:var(--cos-peach)}
body.cosmic .mark{background:var(--cos-grad); color:var(--cos-btn-ink);
  box-shadow:0 10px 40px rgba(199,155,255,.34)}
body.cosmic .mark.mint,body.cosmic .mark.coral{background:var(--cos-grad); color:var(--cos-btn-ink)}
/* Notices keep a light fill so the wording stays maximally readable; the ink is pinned dark
   explicitly, because inheriting the cosmic white here would put white text on a pale tint. */
body.cosmic .notice{color:var(--cos-btn-ink)}
body.cosmic .notice strong{color:var(--cos-btn-ink)}

/* ---------- the thinking world (chat surface, light ground) ----------
   The mark breathes, a small sphere turns beside it, and two nodes light. Same vocabulary as the
   arrival world, one tenth the size, so the two states read as one system. */
.think-world{width:38px;height:38px;flex:0 0 auto;margin-left:calc(var(--s2) * -1);opacity:.85}
.think-world .tw-sph{fill:none;stroke:var(--ink-faint);stroke-width:1;opacity:.30}
.think-world .tw-ring{fill:none;stroke:var(--primary);stroke-width:1.4;opacity:.42;
  transform-box:fill-box;transform-origin:50% 50%;animation:wspin 9s linear infinite}
.think-world .tw-ring2{fill:none;stroke:var(--mint);stroke-width:1.4;opacity:.38;
  transform-box:fill-box;transform-origin:50% 50%;animation:wspin 13s linear infinite reverse}
.think-world .tw-nd{fill:var(--mint);opacity:.55;animation:nodeBreathe 2.6s var(--ease) infinite}
.think-world .tw-nd:nth-of-type(2){animation-delay:-.9s}
.think-world .tw-nd:nth-of-type(3){animation-delay:-1.7s;fill:var(--primary)}
/* ANSWER READY: the thinking block converges away, the answer arrives. Exit is subtler than
   entry — 180ms opacity-and-scale out against 300ms rise in. */
.thinking.out{animation:thinkOut .18s var(--ease) forwards}
@keyframes thinkOut{to{opacity:0;transform:scale(.96)}}
.ans-in{animation:ansIn .3s var(--ease)}
@keyframes ansIn{from{opacity:0;transform:translate3d(0,6px,0)} to{opacity:1;transform:none}}

/* ---------- reduced motion ----------
   The global `*{animation:none!important}` rule earlier in this file already stops every keyframe
   above. What is repeated here is the part that is easy to get wrong: the world must remain VISIBLE
   and correctly positioned once its animations are gone, and the words must still be there. */
@media (prefers-reduced-motion:reduce){
  .world{transform:translate3d(-50%,0,0); opacity:var(--w-opacity)}
  .cos-limb{opacity:.5; transform:none}
  .world .nd,.think-world .tw-nd{opacity:.55}
  .thinking.out{opacity:1; transform:none}
}

/* ============================================================================
   Defect VF-108 — the fifteen inline `style` attributes that never applied.

   The page ships `default-src 'none'; style-src 'self'` with no `'unsafe-inline'`, and `style-src`
   governs the `style` CONTENT ATTRIBUTE as well as `<style>` blocks. Every inline style in index.html
   was therefore refused by the browser in production. Measured, not reasoned: serving the real static
   directory behind the real `edge.security_headers()` CSP and loading it in Chrome produced fifteen
   `Applying inline style violates the following Content Security Policy directive 'style-src 'self''`
   console violations — one per attribute.

   It survived the last ticket because the screenshot harness renders from `file://`, where no response
   header exists and therefore no CSP applies. The renders were honest about the markup and silent about
   the policy. The fix is not to relax the CSP; it is to stop needing the exception.
   ========================================================================== */
.mark.solo{margin:0 auto var(--s5)}
.mark.ob{margin-bottom:var(--s6)}
.brand.tight{margin-bottom:var(--s5)}
.screen.flush{padding:0}
#home{padding:var(--s6) var(--s5) var(--s4); flex:1}
/* Vertical padding on an INLINE box does not grow the line box but does grow the hit area, so the
   link reads as part of the sentence and still answers a 44px thumb. Measured: 22px text + 2x11px. */
.btn.link{display:inline; min-height:auto; padding:11px 2px; margin:0; text-decoration:underline;
  width:auto; font-size:inherit; line-height:1.4}
.gap-t2{margin-top:var(--s2)}
.gap-t4{margin-top:var(--s4)}
.gap-t5{margin-top:var(--s5)}
.gap-t6{margin-top:var(--s6)}
.gap-t7{margin-top:var(--s7)}

/* ============================================================================
   Hover is a desktop enhancement, never an affordance.
   On a touch screen `:hover` sticks after a tap until the user taps somewhere else, leaving a
   control looking selected when it is not. Every rule below is purely cosmetic — nothing is
   revealed by hover and nothing is reachable only through it — so gating the whole set behind a
   real pointer removes the stuck highlight and costs a phone nothing.
   ========================================================================== */
@media (hover:hover) and (pointer:fine){
  .btn:hover{filter:brightness(1.07)}
  .btn.secondary:hover{border-color:var(--primary)}
  .back:hover{color:var(--primary-ink)}
  .chip:hover{border-color:var(--primary); color:var(--primary-ink)}
  details.more summary:hover{color:var(--primary-ink)}
  .iconbtn:hover{border-color:var(--primary)}
  .starter:hover{border-color:var(--primary); transform:translateY(-1px)}
  body.cosmic .btn.secondary:hover{border-color:var(--cos-peach)}
}
