/* ============================================================
   SERVED SOCIAL — THE FRONT DOOR
   ------------------------------------------------------------
   Everything templates/coming_soon.html needs, and nothing any
   other page reads. It is deliberately standalone: the shared
   stylesheets are edited by other work in flight, and the ONE
   page a stranger sees should not be able to break because a
   component two rooms away moved.

   The palette, the space scale and the depth pairs are copied
   from static/styles/served-tokens.css so the front door and the
   real landing are the same house. If the tokens move, move these.

   Two rooms, both designed:
     dark  (default)  slate ground, linen type, claret accent
     light            linen ground, ink type, claret accent
   The pile paints itself from --pile-* below, so the physics is
   lit for whichever room it is in.
   ============================================================ */

/* ---------------- self-hosted type ----------------
   No CDN. The app runs behind a CSP and a font that silently
   falls back to Arial is the difference between "funded" and
   "made in a games room". Subset from the repo's own OFL
   originals by tools/build_comingsoon_assets.py — 76KB for
   three variable families. */
@font-face{
  font-family:"Archivo Narrow CS"; font-style:normal; font-weight:400 700;
  font-display:swap; src:url("fonts/ArchivoNarrow-VF.woff") format("woff-variations");
}
@font-face{
  font-family:"Inter CS"; font-style:normal; font-weight:100 900;
  font-display:swap; src:url("fonts/Inter-VF.woff") format("woff-variations");
}
@font-face{
  font-family:"JetBrains Mono CS"; font-style:normal; font-weight:100 800;
  font-display:swap; src:url("fonts/JetBrainsMono-VF.woff") format("woff-variations");
}

:root{
  --claret:#B52D3B; --claret-deep:#8E1F2C; --claret-lift:#E4576A;

  --font-display:"Archivo Narrow CS","Archivo Narrow","Arial Narrow",sans-serif;
  --font-body:"Inter CS","Inter",system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  --font-mono:"JetBrains Mono CS","JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;

  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px;
  --s6:32px; --s7:48px; --s8:64px; --s9:96px;
  --r-s:8px; --r-m:12px; --r-l:20px; --r-xl:26px; --r-pill:999px;

  --ease:cubic-bezier(.16,.84,.44,1);
  --dur:220ms;
  --gutter:clamp(20px,4.4vw,64px);
  --max:1320px;

  /* dark — the default room */
  --bg:#0F1014;
  --bg-2:#141620;
  --surface:#16181D;
  --raised:#1E2128;
  --text:#F7F4EF;
  --text-2:#C6CBD5;
  --muted:#9AA0AC;
  --accent:#E4576A;              /* claret, lifted so it passes on slate */
  --accent-ink:#F7F4EF;          /* type ON a claret fill */
  --btn:#B52D3B;
  --btn-hover:#C63A48;
  --hairline:rgba(247,244,239,.10);
  --hairline-2:rgba(247,244,239,.20);
  --field:rgba(247,244,239,.04);
  --lift:0 2px 4px rgba(6,7,10,.4), 0 36px 64px -18px rgba(6,7,10,.72);
  --edge:inset 0 1px 0 rgba(247,244,239,.07);
  --glow:radial-gradient(58% 46% at 74% 26%, rgba(181,45,59,.20), transparent 68%),
         radial-gradient(46% 40% at 12% 88%, rgba(247,244,239,.05), transparent 70%);

  /* handed to the canvas, read back by pile.js */
  --pile-shadow:rgba(0,0,0,.55);
  --pile-rule:rgba(247,244,239,.10);
  --pile-edge:rgba(0,0,0,.28);

  color-scheme:dark;
}

@media (prefers-color-scheme: light){
  :root{
    --bg:#F7F4EF;
    --bg-2:#EFEAE1;
    /* the counter runs white at the top to linen at the foot, so the heap
       has a ground to sit on — on flat white the cards float */
    --surface:#EAE4D9;
    --raised:#FFFFFF;
    --text:#121216;
    --text-2:#3A3D45;
    /* the tokens' ink-500 (#6B7079) is 4.54:1 on linen and 3.93:1 on the
       counter's warm ground — a pass on paper and a fail on the panel, for
       11px uppercase labels. This is the same grey a step darker: 5.75:1
       and 4.99:1. */
    --muted:#5C6068;
    --accent:#B52D3B;
    --accent-ink:#FFFFFF;
    --btn:#B52D3B;
    --btn-hover:#8E1F2C;
    --hairline:rgba(18,18,22,.12);
    --hairline-2:rgba(18,18,22,.22);
    --field:rgba(18,18,22,.03);
    --lift:0 1px 2px rgba(18,18,22,.10), 0 30px 56px -20px rgba(18,18,22,.26);
    --edge:inset 0 1px 0 rgba(255,255,255,.9);
    --glow:radial-gradient(58% 46% at 74% 26%, rgba(181,45,59,.09), transparent 68%),
           radial-gradient(46% 40% at 12% 88%, rgba(18,18,22,.04), transparent 70%);
    --pile-shadow:rgba(18,18,22,.30);
    --pile-rule:rgba(18,18,22,.12);
    --pile-edge:rgba(18,18,22,.20);
    color-scheme:light;
  }
}

/* ============================================================
   SHELL
   ============================================================ */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0; min-height:100dvh;
  display:grid; grid-template-rows:auto 1fr auto;
  background:var(--bg);
  background-image:var(--glow),
    linear-gradient(178deg,var(--bg-2) 0%, var(--bg) 46%, var(--bg) 100%);
  background-attachment:fixed;
  color:var(--text);
  font-family:var(--font-body);
  font-size:16px; line-height:1.5;
  -webkit-font-smoothing:antialiased; -moz-osx-font-smoothing:grayscale;
  font-variant-ligatures:common-ligatures;
}

.wrap{width:100%; max-width:var(--max); margin-inline:auto; padding-inline:var(--gutter)}

.skip{
  position:fixed; top:var(--s3); left:var(--s3); z-index:60;
  transform:translateY(-200%);
  background:var(--btn); color:var(--accent-ink);
  padding:10px 16px; border-radius:var(--r-s); text-decoration:none; font-weight:600;
}
.skip:focus-visible{transform:none}

:where(a,button,input):focus-visible{
  outline:2px solid var(--accent); outline-offset:3px; border-radius:var(--r-s);
}

/* ---------------- the label voice ---------------- */
.label{
  font-family:var(--font-mono); font-size:11px; font-weight:500;
  letter-spacing:.17em; text-transform:uppercase; color:var(--muted);
  font-feature-settings:"tnum" 1;
}

/* ============================================================
   HEADER — wordmark left, the way back in on the right
   ============================================================ */
.top{padding-block:clamp(18px,2.4vw,28px)}
.top__in{display:flex; align-items:center; justify-content:space-between; gap:var(--s4)}
.top__mark{display:block; line-height:0}
/* the lower bound is what a 320px phone can spare next to the sign-in
   button — at 148px the pair pushed the page 4px wide */
.top__mark img{width:clamp(124px,28vw,196px); height:auto; display:block}
.top__mark .on-dark{display:block}
.top__mark .on-light{display:none}
@media (prefers-color-scheme: light){
  .top__mark .on-dark{display:none}
  .top__mark .on-light{display:block}
}

.signin{
  display:inline-flex; align-items:center; gap:9px;
  font-size:13px; font-weight:500; letter-spacing:.01em; text-decoration:none;
  color:var(--text-2);
  border:1px solid var(--hairline); border-radius:var(--r-pill);
  padding:9px 16px; background:var(--field);
  transition:color var(--dur) var(--ease), border-color var(--dur) var(--ease),
             transform var(--dur) var(--ease);
  white-space:nowrap;
}
.signin:hover{color:var(--text); border-color:var(--hairline-2); transform:translateY(-1px)}
.signin .dot{
  width:6px; height:6px; border-radius:50%; background:var(--accent);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}

/* ============================================================
   HERO
   ============================================================ */
.hero{display:flex; align-items:center; padding-block:clamp(24px,4vw,56px)}
.hero__grid{
  display:grid; gap:clamp(36px,4.6vw,72px);
  grid-template-areas:"top" "show" "bot";
  width:100%;
}
@media (min-width:1000px){
  .hero__grid{
    grid-template-columns:minmax(0,1fr) minmax(390px,536px);
    grid-template-areas:"top show" "bot show";
    gap:clamp(40px,4.6vw,88px);
    align-items:center;
    row-gap:clamp(28px,3vw,40px);
  }
  .hero__top{align-self:end}
  .hero__bot{align-self:start}
  .hero__show{grid-area:show; align-self:center}
}
.hero__top{grid-area:top}
.hero__bot{grid-area:bot}
.hero__show{grid-area:show}

/* ---------------- eyebrow + drawn rule ---------------- */
.eyebrow{display:flex; align-items:center; gap:14px; margin:0 0 clamp(18px,2vw,26px)}
.eyebrow .label{color:var(--accent)}
.eyebrow svg{display:block; overflow:visible; flex:0 1 120px; min-width:40px}
.eyebrow .rule{stroke:var(--hairline-2); stroke-width:1; fill:none}

/* ---------------- the headline ----------------
   Archivo Narrow set large and tight. The accent is colour only —
   there is no italic axis on this face and a faux-oblique on a
   condensed grotesque is a giveaway. */
h1{
  font-family:var(--font-display); font-weight:700;
  font-size:clamp(3.1rem,7.4vw,5.65rem);
  line-height:.93; letter-spacing:-.018em;
  margin:0 0 clamp(20px,2.2vw,28px);
  text-wrap:balance;
}
h1 .accent{color:var(--accent)}
/* SplitText wraps each word and, with `mask`, clips it itself — this
   rule only makes the word transformable. Do NOT add overflow here as
   well: two clips on one word eats the descenders. */
h1 .cs-word{display:inline-block; will-change:transform}

.lead{
  margin:0; max-width:46ch;
  font-size:clamp(1.02rem,1.18vw,1.16rem); line-height:1.62; color:var(--text-2);
}
.lead strong{color:var(--text); font-weight:600}

/* ---------------- the launch list ---------------- */
.join{margin:0 0 var(--s4); max-width:520px}
.join__row{display:flex; gap:var(--s2); flex-wrap:wrap}
.join__field{
  flex:1 1 220px; min-width:0;
  font:inherit; font-size:15px; color:var(--text);
  background:var(--field); border:1px solid var(--hairline-2);
  border-radius:var(--r-m); padding:13px 15px;
  transition:border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.join__field::placeholder{color:var(--muted)}
.join__field:hover{border-color:var(--accent)}
.join__field:focus{outline:none; border-color:var(--accent);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent)}

.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:9px;
  font:inherit; font-size:15px; font-weight:600; letter-spacing:.005em;
  border:1px solid transparent; border-radius:var(--r-m);
  padding:13px 22px; cursor:pointer; text-decoration:none; white-space:nowrap;
  transition:background var(--dur) var(--ease), transform var(--dur) var(--ease),
             box-shadow var(--dur) var(--ease);
}
.btn--primary{background:var(--btn); color:var(--accent-ink);
  box-shadow:0 1px 2px rgba(6,7,10,.3), 0 10px 22px -10px rgba(181,45,59,.7)}
.btn--primary:hover{background:var(--btn-hover); transform:translateY(-1px);
  box-shadow:0 1px 2px rgba(6,7,10,.3), 0 16px 30px -12px rgba(181,45,59,.8)}
.btn--primary:active{transform:none}
.btn__arrow{transition:transform var(--dur) var(--ease)}
.btn:hover .btn__arrow{transform:translateX(3px)}

.join__note{margin:10px 0 0; font-size:12.5px; line-height:1.5; color:var(--muted)}
.join__note a{color:var(--text-2)}
/* `.join__done` and the `[data-sent]` states that drove it are gone with the
   mailto they belonged to: the form posts to auth.request_invite now, and the
   confirmation is that route's own flash on the page it redirects to. */

/* ---------------- the spec strip ----------------
   The divider is an ::after on the item, never a ::before on the next
   one: when the strip wraps, a trailing rule ends a line the way a
   comma does, where a leading one starts the new line with a stray
   mark and looks like a mistake. */
.spec{list-style:none; display:flex; flex-wrap:wrap; gap:6px 0; margin:0; padding:0}
.spec li{display:flex; align-items:center}
.spec li:not(:last-child)::after{
  content:""; width:1px; height:11px; margin-inline:14px; background:var(--hairline-2);
}
@media (max-width:560px){
  .spec li:not(:last-child)::after{margin-inline:11px}
}

/* ============================================================
   THE COUNTER — what the pile is set down on
   ============================================================ */
.counter{
  position:relative; border-radius:var(--r-xl);
  background:linear-gradient(180deg,var(--raised),var(--surface));
  border:1px solid var(--hairline);
  box-shadow:var(--lift),var(--edge);
  overflow:hidden;
}
.counter__bar,.counter__foot{
  display:flex; align-items:center; justify-content:space-between; gap:var(--s4);
  padding:var(--s4) clamp(16px,2.4vw,22px);
}
.counter__bar{border-bottom:1px solid var(--hairline)}
.counter__foot{border-top:1px solid var(--hairline); padding-block:13px}
.counter__bar .label:first-child{color:var(--text-2)}
/* ---------------- THE DEFAULT IS THE FALLBACK ----------------
   THIS DIRECTION IS THE FIX, AND IT IS WORTH THE PARAGRAPH.

   It used to be the other way round: the canvas showed by default and the
   fanned hand was revealed by a <style> inside <noscript>. A <style> inside
   <noscript> applies ONLY when scripting is switched off — so it covered
   exactly one of the two ways this panel can fail, and not the common one.
   With scripting ON and pile.js simply not arriving — a blip, a slow network,
   an ad blocker, a CSP, a syntax error in a sibling file — the noscript block
   never applies, the canvas is displayed, and a canvas nothing has drawn on is
   transparent. The visitor got a large empty box where the hero should be, on
   the only page anonymous visitors can reach.

   So the panel now starts in the state that needs nothing: the fanned hand,
   cut from the same sprite sheet, which is correct HTML and CSS with no
   JavaScript at all. `pile.js` sets data-pile-live="true" on the panel only
   after it has confirmed BOTH that the canvas element exists and that
   window.Matter loaded, and the rule below then swaps to the physics. Every
   failure mode — scripting off, script blocked, script errored, Matter
   missing — lands on the same working picture, because it is the one that was
   already there. */
.counter canvas{display:none}
.counter .fanned{display:flex}
.counter .js-only{display:none}
.counter .nojs-only{display:inline}

.counter[data-pile-live="true"] canvas{
  display:block; width:100%; height:auto;
  touch-action:pan-y;                 /* a finger still scrolls the page */
  cursor:grab;
}
.counter[data-pile-live="true"] .fanned{display:none}
.counter[data-pile-live="true"] .js-only{display:inline}
.counter[data-pile-live="true"] .nojs-only{display:none}
.counter canvas:active{cursor:grabbing}
.counter .dot{
  display:inline-block; width:6px; height:6px; border-radius:50%;
  background:var(--accent); margin-right:8px; vertical-align:middle;
}

/* ---------------- the hand that is always dealt ----------------
   Five of the same renders, off the same one sprite sheet, dealt out on the
   counter. This is what the panel shows until the physics takes over, and what
   it keeps showing if the physics never does. Nobody arrives at an empty box. */
.fanned{
  padding:clamp(40px,7vw,74px) clamp(16px,3vw,26px) clamp(48px,8vw,86px);
  justify-content:center; align-items:center; gap:0; overflow:hidden;
}
.fanned i{
  display:block; flex:0 0 auto; width:clamp(76px,26%,196px); aspect-ratio:4/5;
  border-radius:10px; background-repeat:no-repeat;
  background-image:var(--atlas);
  background-size:700% 400%;          /* the atlas is 7 columns x 4 rows */
  box-shadow:var(--lift);
  border:1px solid var(--pile-edge);
}
/* percentages on margin-inline resolve against the ROW, not the card, so
   -8% pulls each card an eighth of the panel over its neighbour and the
   five of them stay inside it at every width */
.fanned i + i{margin-inline-start:-8%}
.fanned i:nth-child(1){background-position:0     0; transform:rotate(-8deg)   translateY(14px)}
.fanned i:nth-child(2){background-position:16.6% 0; transform:rotate(-4deg)   translateY(4px)}
.fanned i:nth-child(3){background-position:33.3% 0; transform:rotate(0deg)    translateY(-8px); z-index:2}
.fanned i:nth-child(4){background-position:50%   0; transform:rotate(4deg)    translateY(4px)}
.fanned i:nth-child(5){background-position:66.6% 0; transform:rotate(8deg)    translateY(14px)}


.provenance{
  margin:var(--s4) 0 0; font-size:12.5px; line-height:1.55; color:var(--muted);
  max-width:52ch;
}
.provenance b{color:var(--text-2); font-weight:500}

/* ============================================================
   FOOT
   ============================================================ */
.base{padding-block:clamp(20px,2.6vw,30px); border-top:1px solid var(--hairline)}
.base__in{display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:var(--s3) var(--s5)}
.base p{margin:0; font-size:13px; color:var(--text-2); max-width:62ch; line-height:1.55}
.base p .em{color:var(--text)}
.base p.label{white-space:nowrap}
@media (max-width:560px){.base p.label{white-space:normal}}

/* ============================================================
   MOTION
   ============================================================ */
/* HOW THE ARRIVAL IS HELD BACK WITHOUT EVER TRAPPING THE PAGE.

   Nothing here hides anything on its own. `.cs-anim` is added by one
   inline line in the head — so it only ever exists when scripting is
   ON — and it carries its own CSS failsafe that puts everything back
   at 1.5s with no JavaScript involved at all.

   0.8s IS A MEASURED NUMBER, NOT A ROUND ONE. Held behind gsap loading,
   first contentful paint on a throttled 4G phone was 2.8s: the
   stylesheet landed at 0.84s and then the text sat there waiting on
   73KB of animation library. The failsafe is the promise that the
   WORDS never wait on the choreography. Its clock starts at first
   render, not at navigation, so it gives the arrival 0.8s of grace and
   then gives up on it.

   intro.js's first act is to swap `.cs-anim` for `.cs-anim-run`, which
   is the same hiding WITHOUT the failsafe — a CSS animation in its
   fill phase outranks the inline styles GSAP writes, so a failsafe
   firing mid-timeline would snap the page visible halfway through. And
   intro.js only takes over if it finds the page still hidden; arriving
   after the failsafe, it stands down rather than re-hiding text the
   visitor has already started reading. The 1ms duration is what makes
   that test exact — opacity is only ever 0 or 1, never in between. */
.cs-anim [data-cs-in],
.cs-anim h1[data-cs-split]{
  opacity:0;
  animation:cs-failsafe 1ms linear .8s forwards;
}
.cs-anim-run [data-cs-in],
.cs-anim-run h1[data-cs-split]{opacity:0}
@keyframes cs-failsafe{to{opacity:1}}

@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{
    animation-duration:.001ms !important; animation-iteration-count:1 !important;
    transition-duration:.001ms !important; scroll-behavior:auto !important;
  }
  .cs-anim [data-cs-in],
  .cs-anim h1[data-cs-split],
  .cs-anim-run [data-cs-in],
  .cs-anim-run h1[data-cs-split]{opacity:1; animation:none}
}

/* ============================================================
   NARROW
   ============================================================ */
/* One column below 1000px, and it stays LEFT — centring the stack inside
   the wrap put the headline 90-190px to the right of the wordmark above
   it, which is the sort of drift that reads as "nobody looked". */
@media (max-width:999px){
  .hero__grid{max-width:none; margin-inline:0}
  .lead{max-width:56ch}
  .hero__show{max-width:560px}
  .join{max-width:560px}
}
@media (max-width:560px){
  .top__in{gap:var(--s2)}
  .signin{padding:8px 13px; font-size:12.5px}
  .join__row{flex-direction:column; align-items:stretch}
  /* flex-basis is HEIGHT once the row turns into a column — without this
     the email field grew to a 220px-tall box */
  .join__field{flex:0 0 auto}
  .btn{width:100%}
  .counter__bar,.counter__foot{padding-inline:var(--s4)}
}
