@charset "utf-8";
/* ==========================================================================
   STRAND — golake.app/strand
   The product website for the Strand Android app.

   Every colour here is a token lifted out of the app's own StrandTheme
   (lib/presentation/theme/strand_theme.dart) or out of the Timefall sky
   (lib/timefall/sky_painters.dart). Nothing is invented here.

   Contents
     1. typeface
     2. tokens
     3. reset
     4. type
     5. masthead
     6. section 1 — philosophy + product
     7. section 2 — strand
     8. section 3 — timefall
     9. beta
    10. footer
    11. motion + reveal
   ========================================================================== */

/* ---------------------------------------------------------------- 1. face */
/* Inter, the app's only family. One variable file, self-hosted, latin subset.
   The full wght range is declared because Strand sets real weights up to 800
   and a narrower range would make the browser synthesise a faux bold. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/inter-latin-var.woff2") format("woff2");
}

/* --------------------------------------------------------------- 2. tokens */
:root {
  /* Paper. The app's light theme: warm, not white, so the gold belongs to it. */
  --paper:      #f6f4f0;
  --paper-lift: #ffffff;
  --paper-well: #efebe4;
  --ink:        #171614;
  --rule:       #e2ddd4;
  --body:       #6b6560;   /* 5.09:1 on --paper */
  --faint:      #6e6a65;   /* 4.61:1 on --paper — text-safe */
  --ghost:      #cdc6ba;   /* hairlines only, never text */

  /* Night. The app's dark theme, which is the app's real ground. */
  --night:      #000000;
  --night-lift: #141414;
  --night-well: #1c1c1c;
  --rule-night: #2a2a2e;
  --snow:       #f0efec;
  --mist:       #8c8c8c;   /* 6.25:1 on --night */
  --dim:        #7a7a7a;   /* 4.89:1 on --night */

  /* The one colour.
     --gold is Strand's accent on BOTH themes: 0xFFF1C13D in strand_theme.dart,
     and the exact fill in brand/svg/strand-signal-mark.svg. It is used for
     marks, fills and anything decorative.

     --gold-ink is the app's accentInk, 0xFF8A6A12. The app keeps these apart on
     purpose — its own comment says "gold that reads on black is far too light to
     read on paper. This is the one that may be used as an accent-coloured label
     or icon." So it is for accent-coloured TEXT on paper, never for the mark.
     Painting the logo with this was what made it read as muddy brown. */
  --gold:      #f1c13d;
  --gold-ink:  #8a6a12;    /* accentInk: accent-coloured text on paper only */
  --gold-wash: rgba(241, 193, 61, 0.14);

  /* Sleep stages, exactly as strand_theme.dart assigns them on dark. */
  --deep:  #4a7fd4;
  --rem:   #9b6bd1;
  --light: #c9c4bc;
  --awake: #e8913c;

  /* The Timefall sky. */
  --sky-deep:    #060913;
  --sky-horizon: #0b1020;
  --sky-nadir:   #03040a;
  --dust:        #8fa6c8;

  --sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI Variable Text",
          "Segoe UI", Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif;

  /* StrandMotion: quick 180ms, standard 240ms, easeOutCubic. */
  --quick:    180ms;
  --standard: 240ms;
  --ease:     cubic-bezier(0.33, 0, 0.15, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);

  --gutter:  clamp(1.15rem, 3.4vw, 3rem);
  --width:   1320px;
  --measure: 40rem;

  /* One vertical scale. Short enough that the page stays navigable, long
     enough that distinct ideas still get air around them. Every gap on the
     page is one of these three values. */
  --s1: clamp(2.75rem, 6.2vh, 4.75rem);
  --s2: clamp(1.75rem, 3.6vh, 2.75rem);
  --s3: clamp(1.125rem, 2.2vh, 1.6rem);

  /* Touch. Coarse pointers get the larger minimum: 16px is the size at which
     iOS Safari stops zooming the viewport on focus, and 44px is the smallest
     target a thumb reliably lands on. Declared as tokens so the phone rules
     below only have to name them. */
  --tap:   44px;
  --field: 16px;
}

/* -------------------------------------------------------------- 3. reset */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-weight: 400;
  font-size: 100%;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

h1, h2, h3, h4, p, figure, blockquote, dl, dd { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
img, svg, canvas { display: block; max-width: 100%; }
img { height: auto; }
a { color: inherit; text-decoration: none; }
button, input { font: inherit; color: inherit; }

::selection { background: var(--ink); color: var(--paper); }

:focus-visible { outline: 2px solid var(--gold-ink); outline-offset: 4px; }
.night :focus-visible,
.void :focus-visible { outline-color: var(--gold); }
[id] { scroll-margin-top: clamp(1rem, 3vh, 2.25rem); }
[tabindex="-1"]:focus { outline: none; }

.vh {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.skip {
  position: absolute; top: 0; left: 50%;
  transform: translate(-50%, -140%);
  z-index: 90;
  display: flex;
  align-items: center;
  min-height: var(--tap);
  padding: 0.55rem 1.1rem;
  background: var(--ink); color: var(--paper);
  font-size: 0.6875rem; font-weight: 600;
  letter-spacing: 0.09em; text-transform: uppercase;
  transition: transform var(--standard) var(--ease-out);
}
.skip:focus { transform: translate(-50%, 0); }

.shell {
  width: min(100% - (var(--gutter) * 2), var(--width));
  margin-inline: auto;
}

/* A notch is wider than the gutter, and the home indicator sits under whatever
   is last. `viewport-fit=cover` is already declared in the meta, so the safe
   areas have to be honoured here or the mark and the footer credits end up
   under the sensor housing and the bar. Only applied where they exist, so a
   flat screen sees no change. */
@supports (padding: max(0px)) {
  .shell {
    padding-inline: max(0px, env(safe-area-inset-left))
                     max(0px, env(safe-area-inset-right));
    width: min(100% - (var(--gutter) * 2) - max(0px, env(safe-area-inset-left))
                                 - max(0px, env(safe-area-inset-right)), var(--width));
  }
  .masthead {
    padding-top: calc(clamp(1.1rem, 2.6vh, 1.6rem) + max(0px, env(safe-area-inset-top)));
  }
  .footer {
    padding-bottom: calc(clamp(1.25rem, 2.8vh, 1.75rem) + max(0px, env(safe-area-inset-bottom)));
  }
  .pause, .sky__sticky { padding-bottom: 0; }
}

/* ----------------------------------------------------------------- 4. type */
/* The app's scale, carried up and then tightened once. Hierarchy is carried by
   weight and size — never by a second family, and never by size for its own
   sake. Two moments are deliberately larger than the rest of the page: the hero
   headline, and the Timefall name. Everything else sits on this scale. */

.display {
  font-size: clamp(1.9rem, 3.9vw, 3.125rem);
  font-weight: 800;
  line-height: 1.08;
  letter-spacing: -0.032em;
}

.h1 {
  font-size: clamp(1.45rem, 2.6vw, 2.125rem);
  font-weight: 700;
  line-height: 1.14;
  letter-spacing: -0.026em;
}

.h2 {
  font-size: clamp(1.125rem, 1.7vw, 1.4375rem);
  font-weight: 700;
  line-height: 1.24;
  letter-spacing: -0.02em;
}

.h3 {
  font-size: clamp(0.9375rem, 1.05vw, 1.0625rem);
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: -0.012em;
}

/* The lower bound is 17px, not 15: on a phone 15px body copy is a genuine
   strain, and the reading passages are the point of the page. */
.lead {
  font-size: clamp(1.0625rem, 1.05vw, 1.125rem);
  font-weight: 400;
  line-height: 1.55;
  letter-spacing: -0.008em;
  color: var(--body);
  max-width: 36rem;
}

.prose {
  font-size: clamp(0.9375rem, 0.92vw, 0.9688rem);
  line-height: 1.65;
  letter-spacing: -0.004em;
  color: var(--body);
  max-width: 40rem;
}
.prose + .prose { margin-top: 1em; }

/* StrandScale.overline: letterspaced caps in the quietest tone. The 10px floor
   holds on a phone. It is the smallest thing on the page and it is not worth
   shrinking further to save a few pixels of scroll. */
.eyebrow {
  display: block;
  font-size: 0.6875rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
}
.night .eyebrow, .void .eyebrow { color: var(--dim); }

.two-tone { color: var(--gold-ink); }
.night .two-tone, .void .two-tone { color: var(--gold); }

.gold { color: var(--gold-ink); }
.night .gold, .void .gold { color: var(--gold); }

.rule {
  height: 1px; border: 0; margin: 0;
  background: var(--rule);
}
.night .rule, .void .rule { background: var(--rule-night); }

/* The act number. Set the way the app sets a stage legend.
   --faint rather than --ghost: this carries words, and --ghost is the hairline
   tone, which is 1.5:1 on paper and unreadable. */
.act__num {
  font-size: 0.6875rem; font-weight: 600;
  letter-spacing: 0.1em; color: var(--faint);
}
.void .act__num { color: var(--dim); }

/* --dim is the app's textFaint, specified for pure black. On the Timefall
   ground the real ratio is lower than its nominal one, and three places in this
   section were sitting just under AA because of it. --mist is the app's own
   textMuted (#8C8C8C, 6.25:1 on black) and clears it comfortably without
   brightening anything enough to read as a different colour. */
.void .eyebrow,
.void .kind__meta,
.void .weave__hint,
.void .weave__why { color: var(--mist); }

/* ------------------------------------------------------------ 5. masthead */
/* Two rows, both on the centre line: the mark alone, then the menu beneath it.
   Deliberate air between them and one hairline under the pair, so the whole
   thing reads as a single object rather than as a bar and a toolbar. */
.masthead {
  padding: clamp(1.1rem, 2.6vh, 1.6rem) 0 clamp(0.875rem, 2vh, 1.15rem);
  background: var(--paper);
}

.bar {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(0.5rem, 1.3vh, 0.8rem);
  padding-bottom: clamp(0.8rem, 1.9vh, 1.05rem);
  border-bottom: 1px solid var(--rule);
}
.bar__brand { margin: 0; font-size: inherit; font-weight: inherit; }

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: var(--tap);
  padding-inline: 0.25rem;
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: -0.014em;
  color: var(--ink);
}
.wordmark svg { width: 18px; height: 18px; overflow: visible; }
/* The fill goes on the <svg>, not on the path and circle: those live inside the
   shadow tree of <use>, and a selector never reaches across into one. Set here,
   it inherits in — which is the difference between a gold mark and a black one
   that vanishes on the black footer.

   --gold, the exact #F1C13D from the brand SVG, and not accentInk: this is the
   mark itself, which is a fill, and the app fills the mark with the accent. */
.wordmark svg { fill: var(--gold); }

.nav ul {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  /* The gap stays a visual gap; the extra reach below comes from the links
     themselves, so the row does not grow taller to accommodate thumbs. */
  gap: 0 clamp(1.1rem, 2.6vw, 2rem);
}
.nav a {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0.004em;
  color: var(--body);
  white-space: nowrap;
  transition: color var(--quick) var(--ease);
}
.nav a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: 1px; background: currentColor;
  transform: scaleX(0); transform-origin: left;
  transition: transform var(--standard) var(--ease-out);
}
.nav a:hover { color: var(--ink); }
.nav a:hover::after { transform: scaleX(1); }

/* ---------------------------------------------------- 6. philosophy + product */
.act { position: relative; }

.act--paper { background: var(--paper); color: var(--ink); }
.act--night { background: var(--night); color: var(--snow); }

/* .act--void is the Timefall ground. `.void` is kept as an alias because several
   of the tone rules below were written against it — and it is what makes those
   rules actually match. `.act--void` and `.void` are the same element but are
   different class names, so a selector written for one does nothing for the
   other; three grey overrides silently never applied. */
.act--void { color: var(--snow); }

/* --- the opening ------------------------------------------------------- */
/* One centred composition: provenance, headline, the paragraph, the three
   quiet facts, and the six absences. Each step is separated from the last by
   --s2 or --s3, so the column reads downward instead of sideways. */
.hero {
  padding: clamp(1.75rem, 4.5vh, 3.25rem) 0 0;
  text-align: center;
}
.hero h1 { margin-top: 0.9rem; }
.hero .lead { margin: var(--s3) auto 0; max-width: 40rem; }

.hero__foot {
  margin-top: var(--s2);
  padding-top: var(--s3);
  border-top: 1px solid var(--rule);
  display: flex; flex-wrap: wrap;
  align-items: center; justify-content: center;
  gap: 0.55rem 1.7rem;
  font-size: 0.875rem;
  color: var(--faint);
}
.hero__foot span { display: inline-flex; align-items: center; gap: 0.45rem; }
/* Decoration, not text: the accent, not accentInk. */
.hero__foot span::before {
  content: ""; width: 3px; height: 3px; border-radius: 50%;
  background: var(--gold); flex: none;
}

/* --- the absences ------------------------------------------------------ */
/* Six of them, as one block. These used to be three here and four more further
   down the page, which meant the same claims were made twice and the second
   telling was longer than the first. Full width of the shell, so its rule sits
   directly under the hero's and the two read as one frame; three across and
   two down, so the grid closes rather than trailing off. */
.absences {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-top: var(--s2);
  border-top: 1px solid var(--rule);
  text-align: left;
}
.absence {
  padding: var(--s3) clamp(1rem, 2.2vw, 1.6rem) var(--s3) 0;
  border-bottom: 1px solid var(--rule);
}
.absence + .absence { padding-left: clamp(1rem, 2.2vw, 1.6rem); }
.absence:nth-child(3n + 2),
.absence:nth-child(3n + 3) { border-left: 1px solid var(--rule); }
.absence:nth-child(n + 4) { border-top: 1px solid var(--rule); }

@media (max-width: 46rem) {
  .absences { grid-template-columns: minmax(0, 1fr); }
  .absence { padding: var(--s3) 0; }
  .absence + .absence { padding-left: 0; }
  .absence:nth-child(3n + 2),
  .absence:nth-child(3n + 3) { border-left: 0; }
  .absence:nth-child(n + 4) { border-top: 0; }
}

.absence__word {
  display: block;
  font-size: clamp(1.0625rem, 1.6vw, 1.375rem);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.026em;
  color: var(--ink);
}
.absence__word s {
  text-decoration: line-through;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--ghost);
  color: var(--ghost);
}
.absence__note {
  display: block;
  margin-top: 0.45rem;
  font-size: 0.875rem;
  line-height: 1.6;
  color: var(--body);
  max-width: 34ch;
}

/* --- the dark interlude: the product ---------------------------------- */
.night {
  background: var(--night);
  color: var(--snow);
  position: relative;
  overflow: hidden;
  margin-top: var(--s1);
}
.night::before {
  /* One soft warm light, low and off-centre — the app's strand-bar gradient,
     blown up to a room. It is the only gradient on the page and it is barely
     there. */
  content: "";
  position: absolute; inset: 0;
  background: radial-gradient(
    68% 52% at 74% 12%,
    rgba(241, 193, 61, 0.075) 0%,
    rgba(241, 193, 61, 0.028) 42%,
    rgba(0, 0, 0, 0) 74%
  );
  pointer-events: none;
}
.night > * { position: relative; }

.night__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.6fr);
  gap: clamp(1.75rem, 4.5vw, 4rem);
  align-items: center;
}
.night .shell { padding-block: var(--s1); }
@media (max-width: 56rem) {
  .night__grid { grid-template-columns: minmax(0, 1fr); gap: 2rem; }
  /* The phone leads on a narrow screen, but not by a whole extra screen of its
     own: capped so it is still a portrait screen rather than a full-height
     image, and the heading follows immediately instead of a scroll away. */
  .device { order: -1; justify-self: center; width: min(100%, 13.5rem); }
}

.night .lead, .night .prose { color: var(--mist); }
.night h2, .night h3 { color: var(--snow); }
.night__grid .lead { margin-top: var(--s3); max-width: 34rem; }

/* The four things, as a hairline table rather than a card grid. */
.facts { margin-top: var(--s2); }
.facts__row {
  display: grid;
  grid-template-columns: 1.5rem minmax(0, 1fr);
  gap: 0 0.7rem;
  padding: 0.8rem 0;
  border-top: 1px solid var(--rule-night);
}
.facts__row:last-child { border-bottom: 1px solid var(--rule-night); }
.facts__k {
  font-size: 0.6875rem; font-weight: 600;
  letter-spacing: 0.07em; color: var(--dim);
  padding-top: 0.34em;
}
.facts__v { font-size: 0.875rem; line-height: 1.65; color: var(--mist); }
.facts__v b { color: var(--snow); font-weight: 600; }

/* --- device frame ------------------------------------------------------ */
.device {
  position: relative;
  width: min(100%, 15.5rem);
  justify-self: end;
}
.device__screen {
  position: relative;
  border-radius: 22px;
  overflow: hidden;
  background: var(--night);
  box-shadow:
    0 0 0 1px rgba(240, 239, 236, 0.09),
    0 2px 1px rgba(0, 0, 0, 0.5),
    0 28px 56px -24px rgba(0, 0, 0, 0.9),
    0 6px 18px -10px rgba(0, 0, 0, 0.7);
}
.device__screen img { width: 100%; }
.device__caption {
  margin-top: 0.7rem;
  font-size: 0.75rem;
  letter-spacing: 0.01em;
  /* These screens get reused: the friends shot sits on the black ground, the
     two Strand shots sit on paper. The caption needs a tone that clears 4.5:1
     on either, so it is picked per ground rather than fixed. */
  color: var(--faint);
}
.night .device__caption { color: var(--dim); }
@media (max-width: 56rem) { .device { justify-self: center; } }

/* Two sheets, side by side — as they do on a phone. */
.sheets {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(1.25rem, 3.5vw, 3rem);
  align-items: start;
}
/* In a two-up grid the default `end` justification strands them at opposite
   edges of their columns, which reads as a mistake rather than as a pair. */
.sheets .device { justify-self: center; }
@media (max-width: 32rem) { .sheets { grid-template-columns: minmax(0, 1fr); } }

/* --- the stage ribbon --------------------------------------------------- */
/* The product's own chart, promoted to a graphic element. Proportions are the
   real totals from that night: 68 + 98 + 187 + 38 minutes. */
.ribbon__bar {
  display: flex;
  height: 8px;
  border-radius: 999px;
  overflow: hidden;
  background: var(--paper-well);
}
.ribbon__bar span { display: block; height: 100%; }
.ribbon__bar span + span { box-shadow: -1px 0 0 rgba(0, 0, 0, 0.14); }
.ribbon__key {
  display: flex; flex-wrap: wrap;
  gap: 0.4rem 1.15rem;
  margin-top: 0.65rem;
  font-size: 0.8125rem; color: var(--body);
}
.ribbon__key li { display: inline-flex; align-items: center; gap: 0.45rem; }
.ribbon__key i {
  width: 10px; height: 3px; border-radius: 2px; flex: none;
}
.ribbon__note {
  margin-top: 0.75rem;
  font-size: 0.8125rem; line-height: 1.6; color: var(--faint);
  max-width: 52ch;
}

/* --- one night, as it is shared ----------------------------------------- */
/* Folded into the end of the "Somebody you love is asleep right now" block, so
   it inherits that block's column and its ground instead of sitting in a band of
   its own under a rule. Separated from the facts above by a hairline rather than
   by a section gap: it is the same subject, not a new one. */
.ribbon-set {
  margin-top: var(--s2);
  padding-top: var(--s3);
  border-top: 1px solid var(--rule-night);
}
.ribbon-set figcaption { display: block; }
.ribbon-set__said {
  display: block;
  margin-top: 0.5rem;
  font-size: 0.8125rem;
  line-height: 1.6;
  color: var(--mist);
  max-width: 46ch;
}
.ribbon-set .ribbon { margin-top: var(--s3); }
.night .ribbon__key { color: var(--mist); }
.night .ribbon__note { color: var(--dim); }

/* ---------------------------------------------------------- 7. strand */
.act--strand { padding: var(--s1) 0; }

/* Text on one side, the two real screens on the other. Set as columns rather
   than stacked, because a measure of 44rem on a 1320 shell leaves half the
   width empty underneath a heading — which reads as a mistake, not as air. */
.strand__grid {
  display: grid;
  grid-template-columns: minmax(0, 0.94fr) minmax(0, 1.06fr);
  gap: clamp(2rem, 5vw, 4.25rem);
  align-items: center;
}
@media (max-width: 56rem) {
  .strand__grid { grid-template-columns: minmax(0, 1fr); gap: var(--s2); }
}

.strand__head { min-width: 0; }
.strand__head h2 { margin-top: var(--s3); }
.strand__head .lead { margin-top: var(--s3); }
.strand__head .prose { margin-top: var(--s3); }
/* A little larger than the default device, because the column they sit in has
   room for it and the text inside those screens is worth reading. */
.strand__grid .sheets .device { width: min(100%, 17rem); }

/* -------------------------------------------------------- 8. timefall */
.act--void {
  background: var(--sky-deep);
}

/* The reveal. Generous negative space, and type far larger than anywhere else
   on the page. The pause is the point, and it is the one place where a big
   empty stretch is the design rather than an accident. */
/* Sized to the viewport, not by padding. Padding was doing the same job and kept
   drifting short of a full screen, which is what made the black read as a panel
   rather than as a pause. min-height plus centring means it is exactly one
   screen on any display, and the padding only has to keep the type off the very
   edge of a short one. */
.pause {
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding-block: clamp(3rem, 10vh, 7rem);
  text-align: center;
}
/* --mist, not --dim. --dim is 4.89:1 measured against pure black, but the sky
   ground behind this line is #060913 with the nebula over it, which drops the
   real ratio to 4.32:1 — under AA for text this size. --mist is the app's own
   textMuted and clears it with room to spare. */
.pause__more {
  font-size: clamp(0.9375rem, 1.4vw, 1.125rem);
  font-weight: 400;
  letter-spacing: 0.004em;
  color: var(--mist);
}
.pause__name {
  margin-top: clamp(0.9rem, 2.4vh, 1.5rem);
  font-size: clamp(3rem, 12.5vw, 9rem);
  font-weight: 800;
  line-height: 0.95;
  letter-spacing: -0.048em;
  color: var(--snow);
}

/* The sky. Tall, so it is a place rather than a picture. */
.sky {
  position: relative;
  height: clamp(28rem, 145vh, 60rem);
}

/* A phone in portrait cannot show much of a sky in one screenful, and the
   sticky frame is sized to the viewport, so the scroll range is what decides how
   long the reveal takes. Shortened from 145vh so the constellation assembles
   within a comfortable two-thirds scroll rather than dragging on. */
@media (max-width: 48rem) {
  .sky { height: clamp(24rem, 96vh, 34rem); }
}
.sky__sticky {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
}
.sky__sticky canvas { position: absolute; inset: 0; width: 100%; height: 100%; }

/* No JavaScript: the real screen, from the real app, filling the same place the
   canvas would have. */
.sky__sticky img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center 62%;
}

.sky__line {
  position: absolute;
  left: 0; right: 0; top: 50%;
  transform: translateY(-50%);
  text-align: center;
  padding-inline: var(--gutter);
  font-size: clamp(0.8125rem, 1.2vw, 0.9375rem);
  font-weight: 400;
  letter-spacing: 0.012em;
  color: #7d8aa6;
  pointer-events: none;
  transition: opacity 1200ms var(--ease);
}
.sky__line.is-gone { opacity: 0; }

/* --- how it works ------------------------------------------------------ */
/* The picture first, and only then the explanation of it. The weave is the
   graphic that does the explaining; the three kinds are its key; the prose
   underneath says what the picture is not. */
.how { padding: var(--s1) 0; }

.how__head { max-width: 44rem; }
.how__head h3 { margin-top: var(--s3); }

/* The graphic sits in the left column and the honest note sits directly beneath
   it, in that same column. Previously the note spanned the full width below both
   columns, so it began level with the middle of the prose and read as floating
   beside nothing in particular. Sharing the column puts the illustration and its
   own caveat together, which is the relationship they actually have. */
.how__grid {
  display: grid;
  grid-template-columns: minmax(0, 23rem) minmax(0, 1fr);
  gap: clamp(1.75rem, 4.5vw, 4rem);
  align-items: start;
  margin-top: var(--s1);
}
@media (max-width: 56rem) {
  .how__grid { grid-template-columns: minmax(0, 1fr); gap: var(--s2); }
  /* Full column width once the columns stack: the panel has the row to itself
     here, and a 23-strand field at 21rem is too small to read on a phone. */
  .weave { width: 100%; }
}
.how__col { min-width: 0; }
.how__side { min-width: 0; }
.how__side .prose { max-width: 42rem; color: #9aa8c2; }
.how__side .prose b { color: var(--snow); font-weight: 600; }
.how__side .prose + .prose { margin-top: 1em; }

.how__bar {
  margin-top: var(--s3);
  font-size: 0.8125rem;
  line-height: 1.65;
  color: #9aa8c2;
  max-width: 42rem;
}

/* The one honest paragraph. Held under the graphic by a rule and a deliberate
   gap --s3 -- so it is clearly the caption to the picture above it, and clearly
   not part of the argument running down the right. */
.how__honest {
  margin-top: var(--s2);
  padding-top: var(--s3);
  border-top: 1px solid var(--rule-night);
}
.how__honest-mark { width: 20px; height: 20px; overflow: visible; fill: var(--gold); }
.how__honest h4 { margin-top: var(--s3); }
.how__honest .prose { margin-top: 0.7rem; color: #9aa8c2; max-width: 42ch; }
.how__honest .prose b { color: var(--snow); font-weight: 600; }

/* --- the weave panel --------------------------------------------------- */
/* The panel is portrait, because the field the layout produces is very nearly
   square: putting it in a wide band would leave half the frame empty, which
   reads as a mistake rather than as a sky. The app shows the same field on a
   phone, which is portrait for the same reason. */
.weave {
  margin: 0;
  border-radius: 14px;
  overflow: hidden;
  background: #04050b;
  box-shadow:
    0 0 0 1px rgba(240, 239, 236, 0.1),
    0 24px 48px -28px rgba(0, 0, 0, 0.9);
}
.weave__stage {
  position: relative;
  aspect-ratio: 0.94 / 1;
  min-height: 15rem;
}
.weave__stage canvas { position: absolute; inset: 0; width: 100%; height: 100%; }

/* With scripting off the canvas is an empty dark rectangle, which is worse than
   saying what it would have drawn. */
.weave__fallback {
  position: absolute;
  inset: auto clamp(1rem, 4vw, 1.5rem) clamp(4rem, 11vh, 5rem);
  font-size: 0.8125rem;
  line-height: 1.6;
  color: #a8b6cf;
}

/* The read-out. A blurred surface, exactly as the app's selection panel. */
.weave__read {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 1rem;
  padding: clamp(0.85rem, 2vw, 1.2rem);
  min-height: 5rem;
  background: linear-gradient(to top, rgba(3, 4, 10, 0.92) 12%, rgba(3, 4, 10, 0.62) 58%, rgba(3, 4, 10, 0) 100%);
  pointer-events: none;
}
/* min-width:0 matters here. A flex item defaults to min-width:auto, which
   refuses to shrink below its longest word-run — so the reasons slid underneath
   the link count instead of wrapping above it. */
.weave__text { min-width: 0; max-width: 44ch; }

/* On a phone the read-out is the full width of the panel, so the link count sits
   under the reasons instead of beside them and neither is squeezed. The panel is
   also given a little more height, because at this width the read-out covers a
   larger share of it. */
@media (max-width: 40rem) {
  .weave__read {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
  }
  .weave__hint { order: -1; padding-bottom: 0; }
  .weave__stage { min-height: 17rem; }
}
.weave__strand {
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: -0.012em;
  color: var(--snow);
}
.weave__why {
  margin-top: 0.3rem;
  font-size: 0.8125rem;
  line-height: 1.55;
  color: #a8b6cf;
}
.weave__why:empty { display: none; }
.weave__hint {
  font-size: 0.6875rem; letter-spacing: 0.07em; text-transform: uppercase;
  color: var(--dim); white-space: nowrap; padding-bottom: 0.15em;
}

/* On a finger the instruction sits above the strand text as a quiet label,
   rather than competing with the link count at the end of a line. */
@media (max-width: 40rem) {
  .weave__hint { color: #8f9db6; }
}

.weave__foot {
  display: flex; flex-wrap: wrap;
  align-items: center; justify-content: space-between;
  gap: 0.5rem 1rem;
  padding: 0.7rem clamp(0.85rem, 2vw, 1.15rem);
  border-top: 1px solid rgba(240, 239, 236, 0.08);
  background: rgba(0, 0, 0, 0.4);
  font-size: 0.6875rem;
  color: var(--dim);
}
.weave__legend { display: flex; flex-wrap: wrap; gap: 0.3rem 0.9rem; }
.weave__legend li { display: inline-flex; align-items: center; gap: 0.4rem; }
.weave__legend svg { width: 18px; height: 5px; overflow: visible; flex: none; }

/* --- the three kinds, as the weave's key ------------------------------- */
.kinds { display: grid; }
.kind {
  padding: var(--s3) 0;
  border-top: 1px solid var(--rule-night);
}
.kind:last-child { border-bottom: 1px solid var(--rule-night); }
.kind__name {
  display: flex; align-items: center; gap: 0.55rem;
  font-size: 0.9375rem; font-weight: 600;
  letter-spacing: -0.008em;
  color: var(--snow);
}
.kind__name svg { width: 26px; height: 7px; flex: none; overflow: visible; }
.kind--direct   .kind__name svg { color: rgba(240, 239, 236, 0.88); }
.kind--related   .kind__name svg { color: rgba(240, 239, 236, 0.56); }
.kind--unexpected .kind__name svg { color: rgba(240, 239, 236, 0.4); }

.kind__said {
  display: block;
  margin-top: 0.4rem;
  font-size: 0.875rem;
  line-height: 1.6;
  color: var(--mist);
  max-width: 46ch;
}
.kind__meta {
  display: block;
  margin-top: 0.3rem;
  font-size: 0.8125rem;
  letter-spacing: 0.01em;
  color: var(--dim);
}

/* ----------------------------------------------------------- 9. beta */
.act--beta {
  background: var(--paper);
  color: var(--ink);
  padding: var(--s1) 0;
}

.beta__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.92fr);
  gap: clamp(1.75rem, 4.5vw, 4rem);
  align-items: start;
}
@media (max-width: 52rem) { .beta__grid { grid-template-columns: minmax(0, 1fr); } }

.beta h2 { margin-top: var(--s3); }
.beta .lead { margin-top: var(--s3); }

.beta__facts {
  margin-top: var(--s2);
  font-size: 0.8125rem;
  color: var(--faint);
}
/* Two columns on a phone: the four facts read as a tidy block instead of four
   ragged half-lines, and each pair still reads left to right. */
.beta__facts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.15rem 1rem;
  max-width: 26rem;
}
.beta__facts li { padding: 0.35rem 0; }
.beta__facts b { color: var(--body); font-weight: 600; }
@media (min-width: 46rem) {
  .beta__facts {
    display: flex;
    flex-wrap: wrap;
    gap: 0 1.4rem;
    max-width: none;
  }
}

/* --- the action --------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.75rem 1.3rem;
  border-radius: 999px;
  background: var(--ink);
  color: var(--paper);
  font-size: 0.875rem;
  font-weight: 600;
  letter-spacing: -0.008em;
  cursor: pointer;
  border: 1px solid var(--ink);
  transition:
    background var(--quick) var(--ease),
    color var(--quick) var(--ease),
    transform var(--quick) var(--ease);
}
.btn:hover { background: #2c2a27; border-color: #2c2a27; }
.btn:active { transform: translateY(1px); }
.btn svg { width: 14px; height: 10px; flex: none; }
.btn[disabled] { opacity: 0.42; cursor: not-allowed; }

.beta__cta { margin-top: var(--s2); }

/* --- the gate ----------------------------------------------------------- */
.gate {
  margin-top: var(--s3);
  border: 1px solid var(--rule);
  border-radius: 16px;
  background: var(--paper-lift);
  overflow: hidden;
}
.gate[hidden] { display: none; }

.gate__in { padding: clamp(1.25rem, 2.4vw, 1.6rem); }

.gate__label {
  display: block;
  font-size: 0.6875rem; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--faint);
}

.gate__field {
  display: flex;
  gap: 0.55rem;
  margin-top: 0.7rem;
  flex-wrap: wrap;
}

.gate__field input {
  flex: 1 1 14rem;
  min-width: 0;
  min-height: var(--tap);
  padding: 0.7rem 0.85rem;
  border: 1px solid var(--rule);
  border-radius: 10px;
  background: var(--paper);
  color: var(--ink);
  font-size: var(--field);
  transition: border-color var(--quick) var(--ease);
}
.gate__field input::placeholder { color: var(--faint); }
.gate__field input:hover { border-color: var(--ghost); }
.gate__field input:focus { outline: none; border-color: var(--gold-ink); }
.gate__field input[aria-invalid="true"] { border-color: #a8502c; }
.gate__field .btn { flex: none; }

/* On a phone the field and the button stack, and both go full width. A pill
   button that stops halfway across the panel looks unfinished beside a
   full-width input, and a thumb reaching for the far end of a narrow button is a
   miss waiting to happen.

   Declared after the base rules on purpose, and the flex shorthand is reset
   rather than merely overridden: `flex: 1 1 14rem` sizes the MAIN axis, which
   becomes the HEIGHT once the container is a column — left alone it stretched
   the input to 14rem tall. */
@media (max-width: 30rem) {
  .gate__field { flex-direction: column; align-items: stretch; }
  .gate__field input { flex: 0 0 auto; width: 100%; }
  .gate__field .btn { flex: 0 0 auto; width: 100%; justify-content: center; }
}

.gate__msg {
  margin-top: 0.65rem;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: #a8502c;
  min-height: 1.1rem;
}
.gate__msg:empty { min-height: 0; }

/* What actually happens to the address. Said plainly, because it is plain. */
.gate__truth {
  margin-top: var(--s3);
  padding-top: var(--s3);
  border-top: 1px solid var(--rule);
  font-size: 0.8125rem;
  line-height: 1.65;
  color: var(--body);
}
.gate__truth b { color: var(--ink); font-weight: 600; }

/* --- the confirmation -------------------------------------------------- */
.gate__done { padding: clamp(1.25rem, 2.4vw, 1.6rem); }
.gate__done[hidden] { display: none; }
.gate__tick {
  width: 30px; height: 30px; margin-bottom: 0.9rem;
  border-radius: 50%;
  background: var(--gold-wash);
  display: grid; place-items: center;
}
.gate__tick svg { width: 14px; height: 10px; }
.gate__tick path { stroke: var(--gold-ink); stroke-width: 2.1; fill: none; }
.gate__done h3 { font-size: 1rem; }
.gate__done p { margin-top: 0.5rem; font-size: 0.8125rem; line-height: 1.65; color: var(--body); }
.gate__done .gate__truth { margin-top: 0.9rem; }
.gate__again {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  margin-top: 0.5rem;
  background: none; border: 0; padding: 0;
  font-size: 0.8125rem; color: var(--faint);
  cursor: pointer; text-decoration: underline;
  text-underline-offset: 3px;
}
.gate__again:hover { color: var(--ink); }

/* ---------------------------------------------------------- 10. footer */
.footer {
  background: var(--night);
  color: var(--snow);
  padding: var(--s1) 0 clamp(1.25rem, 2.8vh, 1.75rem);
}

.footer__top {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.8fr) minmax(0, 0.8fr);
  gap: clamp(1.5rem, 3.6vw, 3rem);
  padding-bottom: var(--s2);
  border-bottom: 1px solid var(--rule-night);
}
@media (max-width: 48rem) {
  .footer__top { grid-template-columns: minmax(0, 1fr); gap: 1.5rem; }
}

.footer__mark {
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-size: 1rem; font-weight: 600; letter-spacing: -0.014em;
}
.footer__mark svg { width: 18px; height: 18px; overflow: visible; }
.footer__mark svg { fill: var(--gold); }

.footer__say {
  margin-top: 0.7rem;
  font-size: 0.8125rem;
  line-height: 1.6;
  color: var(--mist);
  max-width: 38ch;
}

.footer__h {
  font-size: 0.6875rem; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--dim);
  margin-bottom: 0.55rem;
}
.footer__list li + li { margin-top: 0.1rem; }
.footer__list a, .footer__list button {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  font-size: 0.8125rem;
  color: var(--mist);
  background: none; border: 0; padding: 0; cursor: pointer;
  text-align: left;
  transition: color var(--quick) var(--ease);
}
.footer__list a:hover, .footer__list button:hover { color: var(--gold); }

/* The privacy paragraph. Not a link to a policy that does not exist — the
   facts, stated. */
.footer__privacy {
  padding: var(--s2) 0;
  border-bottom: 1px solid var(--rule-night);
  display: grid;
  grid-template-columns: minmax(0, 0.62fr) minmax(0, 2.38fr);
  gap: clamp(1.25rem, 3.4vw, 3rem);
  align-items: start;
}
@media (max-width: 48rem) { .footer__privacy { grid-template-columns: minmax(0, 1fr); } }
.footer__privacy p { font-size: 0.75rem; line-height: 1.65; color: var(--mist); max-width: 78ch; }
.footer__privacy p + p { margin-top: 0.7rem; }

.footer__base {
  display: flex; flex-wrap: wrap;
  align-items: center; justify-content: space-between;
  gap: 0.5rem 1.5rem;
  padding-top: var(--s2);
  font-size: 0.75rem;
  color: var(--dim);
}
.footer__base a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  color: var(--mist);
}
.footer__base a:hover { color: var(--gold); }
.footer__made { display: inline-flex; align-items: center; gap: 0.45rem; }
.footer__made::before {
  content: ""; width: 3px; height: 3px; border-radius: 50%;
  background: var(--rule-night);
}

/* ------------------------------------------------------- 11. motion */
/* Gated on .js, which an inline script in the head adds. Without that gate a
   browser with scripting switched off gets a page where every paragraph is
   invisible, because the reveal below sets opacity to zero and nothing ever
   adds the class that brings it back. The content is the point; the motion is
   an enhancement to it. */
.js .reveal {
  opacity: 0;
  transform: translateY(12px);
  transition:
    opacity 800ms var(--ease-out),
    transform 800ms var(--ease-out);
}
.js .reveal.is-in { opacity: 1; transform: none; }

.js .reveal[data-d="1"] { transition-delay: 80ms; }
.js .reveal[data-d="2"] { transition-delay: 150ms; }
.js .reveal[data-d="3"] { transition-delay: 220ms; }
.js .reveal[data-d="4"] { transition-delay: 290ms; }
.js .reveal[data-d="5"] { transition-delay: 360ms; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .reveal { opacity: 1; transform: none; }
}

/* Printing: nobody prints this, but a black page is an ugly accident. */
@media print {
  .sky, .weave, .masthead .nav { display: none; }
  body { background: #fff; color: #000; }
}