/* Framekeep — shared stylesheet for every page on framekeep.app.
 *
 * It exists because the site stopped being one page. privacy.html was still
 * carrying the app's old light Warm Stone palette while the landing had moved
 * to the dark one, so opening a footer link landed you on what looked like a
 * different product. One file now decides the tokens, the background, the
 * floating nav, the buttons and the footer for all of them.
 *
 * The landing uses more of this than the text pages do; they simply do not
 * reference the hero, marquee or walkthrough rules. Keeping one file rather
 * than splitting per page is deliberate -- the split is exactly where the two
 * palettes drifted apart last time.
 *
 * CSP note: served from the same origin, which `style-src 'self'` allows.
 */

/* ===================================================================
   TOKENS — landing only. Measured in landing-palette-check.py.
   =================================================================== */
:root {
  --void:        #0E0918;
  --surface:     #1A1624;
  --panel:       #1B1728;
  --shell:       #2C2834;
  --hairline:    rgba(255, 255, 255, .09);
  --hairline-hi: rgba(255, 255, 255, .16);

  --white: #FFFFFF;   /* 19.61 on void */
  --ash:   #D1CECE;   /* 12.54 on void */
  --fog:   #9D9797;   /*  6.82 on void */

  --accent:    #9C8FCB;  /* 6.73 on void — the app's own violet */
  --accent-hi: #B3A6DE;
  --accent-dim: rgba(156, 143, 203, .30);
  --success:   #8FBF9D;

  --ember-a: #FD8925;
  --ember-b: #FF0C00;
  --ember:   linear-gradient(30deg, #FD8925, #FF0C00);
  --ink:     #1A0B02;    /* 8.04 / 4.85 on the gradient — never white */

  --r-control: 8px;
  --r-node:   12px;
  --r-card:   16px;
  --r-large:  24px;
  --r-bar:    18px;
  --r-pill:   9999px;

  /* v3 used 1200px, which left 37% of a 1890px screen as empty margin; 1360px
     still left about 30%. Fluid now: it tracks the viewport up to 1520px, so a
     wide screen gets a wide page instead of a column with two dark gutters. */
  --wrap: min(1600px, 94vw);
  --gut: 40px;
  --nav-h: 62px;

  --t-fast:   120ms;
  --t-normal: 180ms;
  --t-slow:   320ms;
  --ease: cubic-bezier(.2, .8, .2, 1);

  --scroll: 0;   /* 0..1, written by script; drives the background drift */
}

*, *::before, *::after { box-sizing: border-box; }

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--void);
  color: var(--ash);
  font-family: "Segoe UI Variable Text", "Segoe UI", ui-sans-serif, system-ui,
               -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", Arial, sans-serif;
  font-size: 15.5px;
  line-height: 1.6;
  letter-spacing: -.011em;
  -webkit-font-smoothing: antialiased;
  /* `clip`, not `hidden`. `overflow-x: hidden` on an ancestor makes it a
     scroll container, which is the classic way a sticky nav silently stops
     sticking. `clip` trims the marquee overhang without that side effect. */
  overflow-x: clip;
}

h1, h2, h3, h4 {
  font-family: "Segoe UI Variable Display", "Segoe UI", ui-sans-serif, system-ui,
               -apple-system, BlinkMacSystemFont, Roboto, sans-serif;
  color: var(--white);
  margin: 0;
  font-weight: 300;
  letter-spacing: -.026em;
}
p { margin: 0; }
a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-hi); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }

.mono {
  font-family: ui-monospace, "Cascadia Mono", "Cascadia Code", Consolas, "SF Mono", Menlo, monospace;
  font-size: .92em; color: var(--accent); letter-spacing: 0;
}

.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--gut); }
@media (max-width: 720px) { :root { --gut: 20px; } }

/* ===================================================================
   MOTION
   Hidden state exists only under html.motion. Blur is part of the
   reveal now -- it is what makes the entrance read as "nuot" rather
   than as a step change in opacity.
   =================================================================== */
.motion [data-reveal] {
  opacity: 0;
  transform: translateY(30px) scale(.985);
  filter: blur(7px);
  transition: opacity 780ms var(--ease), transform 780ms var(--ease), filter 620ms var(--ease);
  transition-delay: var(--d, 0ms);
  will-change: opacity, transform, filter;
}
.motion [data-reveal].in { opacity: 1; transform: none; filter: none; }

.motion [data-stagger] > * { --d: 0ms; }
.motion [data-stagger] > *:nth-child(2) { --d:  80ms; }
.motion [data-stagger] > *:nth-child(3) { --d: 160ms; }
.motion [data-stagger] > *:nth-child(4) { --d: 240ms; }
.motion [data-stagger] > *:nth-child(5) { --d: 320ms; }
.motion [data-stagger] > *:nth-child(6) { --d: 400ms; }
.motion [data-stagger] > *:nth-child(7) { --d: 480ms; }
.motion [data-stagger] > *:nth-child(8) { --d: 560ms; }

/* Headline arrives word by word. */
.motion .word { display: inline-block; opacity: 0; transform: translateY(.5em) rotateX(-40deg); }
.motion [data-reveal].in .word {
  opacity: 1; transform: none;
  transition: opacity 620ms var(--ease), transform 700ms var(--ease);
  transition-delay: calc(var(--w, 0) * 65ms);
}

@media (prefers-reduced-motion: reduce) {
  .motion [data-reveal], .motion .word { opacity: 1; transform: none; filter: none; transition: none; }
  .marquee-track { animation: none !important; }
}

/* ===================================================================
   BACKGROUND — three stacked layers, and they move
   =================================================================== */
.aura {
  position: fixed; inset: -20% 0 0; pointer-events: none; z-index: 0;
  /* Weighted toward the edges on purpose. The page has margins, as every page
     does; what it must not have is dead black in them. These put light where
     the container is not. */
  background:
    radial-gradient(64rem 38rem at 50% -8%,  rgba(156, 143, 203, .18), transparent 62%),
    radial-gradient(46rem 40rem at -4% 14%,  rgba(107, 33, 239, .26), transparent 66%),
    radial-gradient(40rem 34rem at 104% 8%,  rgba(253, 137, 37, .16), transparent 62%),
    radial-gradient(44rem 38rem at -6% 62%,  rgba(156, 143, 203, .14), transparent 64%),
    radial-gradient(52rem 34rem at 104% 70%, rgba(156, 143, 203, .13), transparent 64%);
  transform: translate3d(0, calc(var(--scroll) * -8vh), 0);
  transition: transform 60ms linear;
}
/* The dot grid the reference uses to stop large dark areas reading as flat. */
.grid-dots {
  position: fixed; inset: 0; pointer-events: none; z-index: 0;
  background-image: radial-gradient(rgba(255, 255, 255, .055) 1px, transparent 1px);
  background-size: 26px 26px;
  mask-image: radial-gradient(72rem 52rem at 50% 26%, #000 35%, transparent 78%);
  transform: translate3d(0, calc(var(--scroll) * -14vh), 0);
}

/* Scroll progress. The only ember that is not the CTA or atmosphere -- it is
   a position readout, and it is 2px tall. */
.progress {
  position: fixed; left: 0; top: 0; height: 2px; z-index: 90;
  width: 100%; transform-origin: 0 50%;
  transform: scaleX(var(--scroll));
  background: var(--ember);
  opacity: .9;
}

/* ===================================================================
   NAV — a floating rounded bar, not a full-width rule
   =================================================================== */
.nav {
  position: sticky; top: 14px; z-index: 60;
  padding-inline: var(--gut);
  width: 100%; max-width: var(--wrap); margin: 14px auto 0;
}
.nav-inner {
  height: var(--nav-h);
  display: flex; align-items: center; gap: 14px;
  padding: 0 10px 0 20px;
  border-radius: var(--r-bar);
  background: rgba(19, 14, 32, .58);
  box-shadow: inset 0 0 0 1px var(--hairline-hi);
  backdrop-filter: blur(18px) saturate(150%);
  transition: background var(--t-slow) var(--ease), box-shadow var(--t-slow) var(--ease);
}
.nav.is-stuck .nav-inner {
  background: rgba(14, 9, 24, .88);
  box-shadow: inset 0 0 0 1px var(--hairline-hi), 0 10px 30px -14px rgba(0, 0, 0, .8);
}

.brand { display: inline-flex; align-items: center; gap: 9px; color: var(--white); font-size: 16.5px; letter-spacing: -.02em; }
.brand svg { width: 20px; height: 20px; fill: none; stroke: var(--accent); stroke-width: 1.5; stroke-linecap: round; }

.pill-local {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px 3px 8px; border-radius: var(--r-pill);
  background: rgba(143, 191, 157, .12); color: var(--success); font-size: 12px;
}
.pill-local .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--success); }

.nav-links { margin-left: auto; display: flex; gap: 26px; }
.nav-links a { color: var(--fog); font-size: 13.5px; transition: color var(--t-normal) var(--ease); }
.nav-links a:hover { color: var(--white); }
.nav-right { margin-left: auto; display: flex; align-items: center; gap: 8px; }
.nav-links + .nav-right { margin-left: 22px; }
@media (max-width: 1040px) { .nav-links { display: none; } }
@media (max-width: 560px) { .nav-gh span { display: none; } .nav-gh { padding-inline: 11px; } }

/* ===================================================================
   BUTTONS
   =================================================================== */
.btn {
  display: inline-flex; align-items: center; gap: 8px; border: 0;
  border-radius: var(--r-control); padding: 10px 18px;
  font: inherit; font-size: 14px; letter-spacing: -.008em;
  cursor: pointer; white-space: nowrap;
  transition: transform var(--t-normal) var(--ease), box-shadow var(--t-normal) var(--ease),
              background var(--t-normal) var(--ease), color var(--t-normal) var(--ease);
}
.btn-ember { background: var(--ember); color: var(--ink); }   /* never #FFFFFF */
.btn-ember:hover { transform: translateY(-1px); box-shadow: 0 10px 28px -8px rgba(255, 78, 20, .6); }
.btn-ember:active { transform: none; }
.btn-ghost {
  background: rgba(255, 255, 255, .04); color: var(--ash);
  box-shadow: inset 0 0 0 1px var(--hairline-hi);
}
.btn-ghost:hover { background: var(--shell); color: var(--white); }
.btn svg { width: 16px; height: 16px; flex: none; }

/* ===================================================================
   SECTIONS
   =================================================================== */
main { position: relative; z-index: 1; }
section { padding-block: 128px; scroll-margin-top: 96px; }
@media (max-width: 720px) { section { padding-block: 84px; } }

.eyebrow {
  display: inline-flex; align-items: center; gap: 9px;
  color: var(--accent); font-size: 12px; letter-spacing: .09em;
  text-transform: uppercase; margin-bottom: 20px;
}
.eyebrow::before { content: ""; width: 20px; height: 1px; background: currentColor; opacity: .55; }
.head-center { max-width: 820px; margin-inline: auto; text-align: center; }
.head-center .eyebrow::before { display: none; }
.head-left { max-width: 600px; }

h2 { font-size: 40px; line-height: 1.1; }
@media (max-width: 720px) { h2 { font-size: 30px; } }
.lede { margin-top: 20px; color: var(--fog); font-size: 18px; line-height: 1.62; }

/* Full-bleed panels. This is the answer to "the background is flat": three
   sections sit on their own field instead of on the page. */
.field { position: relative; }
.field::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
}
.field-cool::before {
  background:
    radial-gradient(70rem 40rem at 50% 0%, rgba(107, 33, 239, .16), transparent 70%),
    linear-gradient(180deg, rgba(27, 23, 40, .9), rgba(14, 9, 24, 0) 70%);
  border-top: 1px solid var(--hairline);
}
.field-deep::before {
  background:
    radial-gradient(60rem 34rem at 12% 20%, rgba(156, 143, 203, .14), transparent 66%),
    radial-gradient(50rem 30rem at 90% 80%, rgba(253, 137, 37, .07), transparent 64%),
    linear-gradient(180deg, rgba(26, 22, 36, .75), rgba(14, 9, 24, 0));
  border-top: 1px solid var(--hairline);
}

/* ===================================================================
   HERO
   =================================================================== */
/* ===================================================================
   HERO — two columns. v4's first pass centred the copy and parked the
   product shot underneath it, which left a dead band under the nav and
   pushed the only picture of the app off the first screen entirely.
   Copy left, app right, bleeding past the container like the reference.
   =================================================================== */
.hero { padding-top: 52px; padding-bottom: 0; }
/* Symmetric. An earlier pass ran the right column out to the window edge to
   kill the gutter on that side; the owner's call was that it looks bad and the
   two margins should simply be equal. So the hero stays inside the centred
   container, the image ends where the container ends, and the gutters are cut
   by widening the container instead of by cropping the picture. */
.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.06fr);
  gap: 56px;
  align-items: center;
}
.hero-copy { max-width: 620px; }
.hero-visual { position: relative; }
.hero-visual .shot-frame { width: 100%; max-width: none; }
@media (max-width: 1100px) {
  .hero-grid { grid-template-columns: 1fr; gap: 44px; }
  .hero-copy { max-width: none; }
}

.hero-badge {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 7px 17px 7px 12px; border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .035);
  box-shadow: inset 0 0 0 1px var(--hairline-hi);
  color: var(--ash); font-size: 13px;
}
.hero-badge .spark { width: 7px; height: 7px; border-radius: 50%; background: var(--ember); }

.hero h1 { margin-top: 26px; font-size: 64px; line-height: 1.0; letter-spacing: -.032em; }
@media (max-width: 1400px) { .hero h1 { font-size: 56px; } }
@media (max-width: 720px)  { .hero h1 { font-size: 42px; } }
@media (max-width: 460px)  { .hero h1 { font-size: 34px; } }

.hero-sub { max-width: 600px; margin: 22px 0 0; color: var(--ash); font-size: 18px; line-height: 1.62; }

.cta { margin-top: 34px; display: flex; flex-direction: column; align-items: flex-start; gap: 14px; }
.cmd-row { display: flex; align-items: stretch; gap: 10px; flex-wrap: wrap; }
.cmd {
  display: inline-flex; align-items: center; gap: 12px;
  padding: 11px 18px; border-radius: var(--r-control);
  background: rgba(0, 0, 0, .34); box-shadow: inset 0 0 0 1px var(--hairline-hi);
  font-family: ui-monospace, "Cascadia Mono", Consolas, "SF Mono", Menlo, monospace;
  font-size: 14.5px; color: var(--white); letter-spacing: 0;
}
.cmd .prompt { color: var(--accent); }
.cta-note { margin-top: 2px; color: var(--fog); font-size: 13.5px; max-width: 580px; }
.cta-note strong { color: var(--ash); font-weight: 400; }
.copy-btn { padding: 11px 16px; }
.copy-btn.done { background: var(--shell); color: var(--success); box-shadow: inset 0 0 0 1px var(--hairline-hi); }

/* Microsoft Store — a badge, deliberately not a link. See the head comment. */
.store-badge {
  display: inline-flex; align-items: center; gap: 11px;
  padding: 9px 16px 9px 13px; border-radius: var(--r-node);
  background: rgba(255, 255, 255, .035);
  box-shadow: inset 0 0 0 1px var(--hairline-hi);
  text-align: left;
}
.store-badge .ms { display: grid; grid-template-columns: 9px 9px; gap: 2px; flex: none; }
.store-badge .ms i { width: 9px; height: 9px; display: block; }
.store-badge b { display: block; color: var(--white); font-weight: 400; font-size: 13.5px; letter-spacing: -.01em; }
/* Scoped to the second line only. Unscoped, `text-transform` on the wrapper
   inherited into <b> and shouted the product's own name back as
   "MICROSOFT STORE". */
.store-badge .soon { display: block; color: var(--fog); font-size: 11.5px; letter-spacing: .06em; text-transform: uppercase; }
/* It became a link on 24/08, so it has to behave like one: something must
   happen on hover, and the keyboard must be able to see where it is. */
a.store-badge { text-decoration: none; transition: background var(--t-fast), box-shadow var(--t-fast); }
a.store-badge:hover { background: rgba(255, 255, 255, .07); box-shadow: inset 0 0 0 1px var(--accent); }
a.store-badge:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* Three rows, not a wrapping strip. Side by side the three needed about 780px
   in a 640px column, so the third dropped alone onto a second line and read as
   a layout accident rather than as a set. Stacked, they also sit against the
   app window beside them instead of running under it. */
.trust { margin-top: 38px; display: grid; gap: 8px; max-width: 545px; }
.trust-item {
  display: flex; align-items: center; gap: 12px;
  padding: 11px 16px; border-radius: var(--r-node);
  background: rgba(255, 255, 255, .025);
  box-shadow: inset 0 0 0 1px var(--hairline);
  transition: box-shadow var(--t-slow) var(--ease), background var(--t-slow) var(--ease);
}
.trust-item:hover { background: rgba(255, 255, 255, .05); box-shadow: inset 0 0 0 1px var(--hairline-hi); }
.trust-item svg { width: 17px; height: 17px; flex: none; fill: none; stroke: var(--accent); stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }
.trust-item > div { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
.trust-item b { color: var(--white); font-weight: 400; font-size: 13.5px; }
.trust-item span { color: var(--fog); font-size: 12.5px; }
.trust-item span::before { content: "·"; margin-right: 9px; opacity: .55; }

/* Hero shot: turned away from the reader, and it squares up as you scroll. */
.hero-shot { perspective: 2200px; }
.hero-shot-inner {
  transform:
    rotateY(calc((1 - var(--rise, 1)) * -7deg))
    rotateX(calc((1 - var(--rise, 1)) * 3deg))
    translate3d(0, calc(var(--lift, 0) * 1px), 0);
  transform-origin: 0% 50%;
  transition: transform 90ms linear;
}
.shot-frame {
  position: relative; border-radius: var(--r-large); padding: 8px;
  background: linear-gradient(180deg, rgba(255, 255, 255, .08), rgba(255, 255, 255, .012));
  box-shadow: inset 0 0 0 1px var(--hairline-hi), 0 40px 90px -40px rgba(0, 0, 0, .9);
}
.shot-frame::before {
  content: ""; position: absolute; inset: -12% -6% -18%; border-radius: 50%;
  background: radial-gradient(closest-side, rgba(156, 143, 203, .30), transparent 72%);
  filter: blur(28px); z-index: -1;
}
.shot-frame img { display: block; width: 100%; height: auto; border-radius: 17px; }
.shot-caption { margin-top: 18px; text-align: center; color: var(--fog); font-size: 13.5px; }

/* ===================================================================
   HERO WALKTHROUGH
   The four steps, played over the product's own screens.
   Every frame here is a real render of the real UI, produced by
   tray/ui-dev/shoot.ps1 with the standard demo dataset -- the rule in
   _design_system/screenshots.md, which exists so product imagery is
   never hand-drawn. What this file adds is only the pointer and the
   click; it never redraws a control.
   The whole thing is gated on html.motion, so with scripting off or
   motion reduced it is the first screen, sitting still.
   =================================================================== */
/* No `overflow: hidden` here, deliberately: the frame's glow is a ::before
   with a negative inset, and clipping the stage clips the glow off with it.
   The images are rounded by .demo-frames instead. */
.demo-stage { position: relative; }
.demo-frame {
  display: block; width: 100%; height: auto; border-radius: 17px;
  grid-area: 1 / 1;
  opacity: 0;
  transition: opacity 420ms var(--ease);
}
.demo-frames { display: grid; border-radius: 17px; overflow: hidden; }
.demo-frame.is-on { opacity: 1; }
/* Without motion exactly one frame is painted and nothing moves. */
html:not(.motion) .demo-frame:first-child { opacity: 1; }
html:not(.motion) .demo-cursor,
html:not(.motion) .demo-ping { display: none; }

/* Positioned in PIXELS the script derives from the stage's own size, not in
   percentages: a percentage inside translate() resolves against the element's
   own 26px box, which would put the pointer within a few pixels of the corner
   at every beat. */
.demo-cursor {
  position: absolute; left: 0; top: 0; width: 26px; height: 26px;
  transform: translate3d(var(--x, 0px), var(--y, 0px), 0);
  pointer-events: none; z-index: 3;
  transition: transform 900ms cubic-bezier(.33, .02, .18, 1), scale 140ms var(--ease);
  filter: drop-shadow(0 3px 7px rgba(0, 0, 0, .75));
}
.demo-cursor svg { width: 100%; height: 100%; display: block; }
.demo-cursor.is-press { scale: .82; }

/* The click. A ring that leaves from under the pointer. */
.demo-ping {
  position: absolute; left: 0; top: 0; width: 22px; height: 22px; margin: -11px 0 0 -11px;
  border-radius: 50%; pointer-events: none; z-index: 2; opacity: 0;
  box-shadow: 0 0 0 2px rgba(179, 166, 222, .9), 0 0 22px 6px rgba(156, 143, 203, .5);
}
.demo-ping.is-fire { animation: ping 620ms var(--ease); }
@keyframes ping {
  from { opacity: .95; transform: translate3d(var(--x, 0px), var(--y, 0px), 0) scale(.35); }
  to   { opacity: 0;   transform: translate3d(var(--x, 0px), var(--y, 0px), 0) scale(3.4); }
}

/* Caption under the stage: which of the four steps is playing. Fixed height,
   so a longer line never nudges the layout while it swaps. */
.demo-cap {
  display: flex; align-items: flex-start; gap: 11px;
  margin-top: 18px; min-height: 42px;
  color: var(--fog); font-size: 14px; line-height: 1.45;
}
.demo-cap-n {
  flex: none; width: 22px; height: 22px; margin-top: 1px;
  display: grid; place-items: center; border-radius: 50%;
  background: rgba(156, 143, 203, .16); box-shadow: inset 0 0 0 1px var(--accent-dim);
  color: var(--accent-hi); font-size: 11.5px; font-variant-numeric: tabular-nums;
}
.demo-cap-t { transition: opacity 260ms var(--ease); }
.demo-cap.is-swap .demo-cap-t { opacity: 0; }

/* Four ticks showing where the loop is. */
.demo-track { display: flex; gap: 6px; margin-top: 14px; }
.demo-tick { height: 3px; flex: 1; border-radius: 2px; background: rgba(255, 255, 255, .10); overflow: hidden; }
.demo-tick i { display: block; height: 100%; width: 100%; border-radius: 2px; background: var(--accent); transform: scaleX(0); transform-origin: 0 50%; }
.demo-tick.is-done i { transform: scaleX(1); }
.demo-tick.is-live i { transform: scaleX(1); transition: transform var(--beat, 2600ms) linear; }

@media (prefers-reduced-motion: reduce) {
  .demo-frame { transition: none; }
  .demo-cursor, .demo-ping { display: none; }
  .demo-tick i { transition: none; }
}

/* ===================================================================
   MARQUEE — brand marks in their own colours, both directions
   =================================================================== */
.marquee { padding-block: 74px; overflow: hidden; }
.marquee-head { text-align: center; color: var(--fog); font-size: 13px; letter-spacing: .1em; text-transform: uppercase; margin-bottom: 30px; }
.marquee-rail { position: relative; display: flex; overflow: hidden; mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent); }
.marquee-rail + .marquee-rail { margin-top: 14px; }
.marquee-track { display: flex; gap: 14px; padding-right: 14px; flex: none; animation: slide 42s linear infinite; }
.marquee-rail.rev .marquee-track { animation-direction: reverse; animation-duration: 52s; }
.marquee-rail:hover .marquee-track { animation-play-state: paused; }
@keyframes slide { from { transform: translateX(0); } to { transform: translateX(-100%); } }

.chip {
  display: inline-flex; align-items: center; gap: 11px; flex: none;
  padding: 13px 22px 13px 16px; border-radius: var(--r-node);
  background: rgba(255, 255, 255, .028);
  box-shadow: inset 0 0 0 1px var(--hairline);
  color: var(--ash); font-size: 15px; white-space: nowrap;
  transition: box-shadow var(--t-normal) var(--ease), background var(--t-normal) var(--ease);
}
.chip:hover { background: rgba(255, 255, 255, .06); box-shadow: inset 0 0 0 1px var(--hairline-hi); }
.chip svg { width: 22px; height: 22px; flex: none; }

/* ===================================================================
   CARDS
   =================================================================== */
.grid { display: grid; gap: 20px; margin-top: 66px; }
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 1040px) { .grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 680px)  { .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; } }

.card {
  position: relative; background: var(--surface); border-radius: var(--r-card);
  padding: 34px 30px; box-shadow: inset 0 0 0 1px var(--hairline);
  transition: background var(--t-slow) var(--ease), box-shadow var(--t-slow) var(--ease), transform var(--t-slow) var(--ease);
}
.card:hover {
  background: var(--shell); transform: translateY(-3px);
  box-shadow: inset 0 0 0 1px var(--hairline-hi), inset 0 1px 0 0 var(--accent-dim), 0 20px 40px -26px rgba(0, 0, 0, .9);
}
.card h4 { font-size: 17.5px; line-height: 1.35; letter-spacing: -.018em; margin-bottom: 10px; }
.card p { color: var(--fog); font-size: 14.5px; }
.card-icon { width: 42px; height: 42px; display: grid; place-items: center; border-radius: var(--r-node); background: rgba(156, 143, 203, .13); margin-bottom: 22px; }
.card-icon svg { width: 19px; height: 19px; fill: none; stroke: var(--accent); stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }

/* ===================================================================
   STEPS
   =================================================================== */
.steps { margin: 70px auto 0; max-width: 940px; position: relative; display: grid; gap: 4px; }
.steps::before {
  content: ""; position: absolute; left: 28px; top: 36px; bottom: 36px; width: 1px;
  background: linear-gradient(180deg, var(--accent-dim), rgba(156, 143, 203, .06));
  transform-origin: 50% 0;
}
.motion .steps::before { transform: scaleY(0); transition: transform 1200ms var(--ease) 140ms; }
.motion .steps.in::before { transform: scaleY(1); }
@media (prefers-reduced-motion: reduce) { .motion .steps::before { transform: none; transition: none; } }

.step { display: flex; gap: 26px; padding: 22px 0; align-items: flex-start; }
.step-n {
  flex: none; width: 56px; height: 56px; display: grid; place-items: center; border-radius: 50%;
  background: var(--void); box-shadow: inset 0 0 0 1px var(--hairline-hi);
  color: var(--accent); font-size: 19px; font-variant-numeric: tabular-nums;
  transition: box-shadow var(--t-slow) var(--ease), color var(--t-slow) var(--ease), background var(--t-slow) var(--ease);
}
.step:hover .step-n { color: var(--accent-hi); background: rgba(156, 143, 203, .12); box-shadow: inset 0 0 0 1px var(--accent-dim); }
.step-body { padding-top: 10px; }
.step-body h4 { font-size: 20px; line-height: 1.35; letter-spacing: -.02em; }
.step-body p { margin-top: 9px; color: var(--fog); font-size: 15.5px; max-width: 760px; }
@media (max-width: 620px) { .steps::before { display: none; } .step { gap: 16px; } .step-n { width: 44px; height: 44px; font-size: 16px; } }

.kbd {
  display: inline-block; padding: 1px 7px; border-radius: 5px;
  background: var(--shell); box-shadow: inset 0 0 0 1px var(--hairline-hi);
  color: var(--accent-hi); font-family: ui-monospace, Consolas, monospace; font-size: 12.5px; letter-spacing: 0;
}

/* ===================================================================
   PIPELINE — with a dot that travels the whole rail
   =================================================================== */
.pipe-wrap { position: relative; margin-top: 66px; }
.pipe-line { position: absolute; left: 0; right: 0; top: 50%; height: 1px; background: var(--hairline); overflow: hidden; }
.pipe-line::after {
  content: ""; position: absolute; top: -1px; left: 0; width: 22%; height: 3px;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  filter: blur(.5px);
}
.motion .pipe-line::after { animation: travel 4.6s var(--ease) infinite; }
@keyframes travel { 0% { transform: translateX(-30%); opacity: 0; } 12% { opacity: 1; } 88% { opacity: 1; } 100% { transform: translateX(480%); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .motion .pipe-line::after { animation: none; opacity: .5; } }

.pipe { position: relative; display: grid; grid-template-columns: repeat(5, 1fr); gap: 14px; }
@media (max-width: 1040px) { .pipe { grid-template-columns: repeat(2, 1fr); } .pipe-line { display: none; } }
@media (max-width: 620px)  { .pipe { grid-template-columns: 1fr; } }

.pipe-node {
  padding: 24px 22px; border-radius: var(--r-node);
  background: var(--surface); box-shadow: inset 0 0 0 1px var(--hairline);
  transition: box-shadow var(--t-slow) var(--ease), transform var(--t-slow) var(--ease);
}
.pipe-node:hover { transform: translateY(-3px); box-shadow: inset 0 0 0 1px var(--hairline-hi); }
.pipe-node b { display: block; color: var(--white); font-weight: 400; font-size: 15px; letter-spacing: -.015em; }
.pipe-node span { display: block; margin-top: 8px; color: var(--fog); font-size: 13px; line-height: 1.5; }
.pipe-node .n { display: block; margin-bottom: 13px; color: var(--accent); font-size: 11px; letter-spacing: .1em; font-variant-numeric: tabular-nums; }
.pipe-node.is-final { background: rgba(14, 9, 24, .8); box-shadow: inset 0 0 0 1px var(--hairline-hi), inset 0 1px 0 0 var(--accent-dim); }
.pipe-node.is-final .n { color: var(--accent-hi); }

.pipe-foot { margin-top: 30px; display: flex; align-items: center; justify-content: center; gap: 10px; color: var(--fog); font-size: 14px; }
.pipe-foot svg { width: 16px; height: 16px; fill: none; stroke: var(--success); stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }

/* ===================================================================
   SPLITS
   =================================================================== */
.split { display: grid; grid-template-columns: 1fr 1.18fr; gap: 72px; align-items: center; }
/* `order` moves the grid TRACK, not just the paint order -- without this the
   flipped section hands its screenshot the smaller half. */
.split-flip { grid-template-columns: 1.18fr 1fr; }
.split-flip .split-media { order: -1; }
@media (max-width: 1040px) {
  .split, .split-flip { grid-template-columns: 1fr; gap: 48px; }
  .split-flip .split-media { order: 0; }
}
.privacy-grid { margin-top: 38px; display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 560px) { .privacy-grid { grid-template-columns: 1fr; } }
.privacy-grid .card { padding: 26px 24px; }
.privacy-grid .card-icon { width: 36px; height: 36px; margin-bottom: 16px; }
.privacy-grid .card-icon svg { width: 16px; height: 16px; }
.privacy-grid h4 { font-size: 16px; }
.privacy-grid p { font-size: 14px; }

.bullets { margin: 36px 0 0; padding: 0; list-style: none; display: grid; gap: 22px; }
.bullets li { display: flex; gap: 14px; align-items: flex-start; }
.bullets svg { width: 17px; height: 17px; flex: none; margin-top: 4px; fill: none; stroke: var(--accent); stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.bullets span { color: var(--fog); font-size: 15px; }
.bullets b { color: var(--white); font-weight: 400; }

/* ===================================================================
   CLOSING PANEL — the one full ember field on the page
   =================================================================== */
.final { padding-block: 40px 128px; }
/* The gradient is capped at #B4380F on purpose, and this is the second time
   the same measurement decided something on this page. The first draft ran up
   to #FF7A18, which reads gorgeous and gives cream text 2.44 -- the exact
   failure this page rejected the reference for, reintroduced by me one section
   later. Every stop below clears 5.60 for #FFF6EE and 4.78 for #FFE0CC.
   Numbers live in _design_system/landing-palette-check.py, group [PANEL]. */
.final-panel {
  position: relative; overflow: hidden; text-align: center;
  border-radius: 28px; padding: 104px 40px;
  background:
    radial-gradient(52rem 26rem at 84% 6%, rgba(255, 190, 120, .18), transparent 62%),
    linear-gradient(122deg, #3A1508 0%, #7A2A0C 20%, #A8330E 42%, #B4380F 54%, #6E2A55 80%, #241338 100%);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .14), inset 0 1px 0 0 rgba(255, 176, 106, .55);
}
/* Starfield. Static dots, plus a slow drift so the panel is never quite still. */
.final-panel::before {
  content: ""; position: absolute; inset: -40%; pointer-events: none;
  background-image:
    radial-gradient(rgba(255, 255, 255, .9) 1px, transparent 1.4px),
    radial-gradient(rgba(255, 255, 255, .5) 1px, transparent 1.4px);
  background-size: 150px 130px, 90px 110px;
  background-position: 0 0, 40px 60px;
  opacity: .45;
}
.motion .final-panel::before { animation: drift 34s linear infinite; }
@keyframes drift { to { transform: translate3d(-90px, -110px, 0); } }
@media (prefers-reduced-motion: reduce) { .motion .final-panel::before { animation: none; } }

.final-panel > * { position: relative; }
.final-panel h2 { font-size: 60px; line-height: 1.02; letter-spacing: -.032em; color: #FFF6EE; }  /* worst stop 5.60 */
.final-panel .lede { color: #FFE0CC; font-size: 19px; }                                          /* worst stop 4.78 */
@media (max-width: 720px) { .final-panel { padding: 64px 22px; } .final-panel h2 { font-size: 36px; } }
.final-panel .cmd { background: rgba(20, 8, 2, .72); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .24); color: #FFF6EE; }
.final-panel .cmd .prompt { color: #FFC48C; }
/* The copy button keeps the page's one ember treatment rather than inverting.
   Against a panel that now tops out at #B4380F it is the brightest thing
   present, which is what a primary action should be. */
.final-meta { margin-top: 24px; color: #FFE0CC; font-size: 13px; }

/* ===================================================================
   FOOTER
   =================================================================== */
footer { border-top: 1px solid var(--hairline); padding-block: 38px; position: relative; z-index: 1; }
.foot-row { display: flex; justify-content: space-between; align-items: center; gap: 18px; flex-wrap: wrap; color: var(--fog); font-size: 13px; }
.foot-links { display: flex; gap: 22px; flex-wrap: wrap; align-items: center; }
.foot-links svg { width: 15px; height: 15px; vertical-align: -2px; }

.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* ===================================================================
   TEXT PAGES — privacy, security, docs, changelog
   The landing is the only page with a hero; everything else is one
   readable column on the same field, so a footer link never lands you
   on something that looks like a different product.
   =================================================================== */
.page { padding-block: 84px 110px; }
.page-head { max-width: 780px; margin-inline: auto; text-align: center; margin-bottom: 66px; }
.page-head h1 { font-size: 52px; line-height: 1.04; letter-spacing: -.03em; }
@media (max-width: 720px) { .page-head h1 { font-size: 34px; } .page { padding-block: 56px 76px; } }
.page-head .sub { margin-top: 16px; color: var(--fog); font-size: 14px; }

.prose { max-width: 780px; margin-inline: auto; }
.prose h2 {
  font-size: 25px; line-height: 1.25; letter-spacing: -.022em;
  margin-top: 58px; padding-top: 30px; border-top: 1px solid var(--hairline);
}
.prose h2:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.prose h3 { font-size: 17.5px; line-height: 1.35; letter-spacing: -.018em; margin-top: 34px; }
.prose p { margin-top: 16px; color: var(--ash); font-size: 16px; line-height: 1.72; }
.prose p.lede { color: var(--fog); font-size: 18px; }
.prose ul, .prose ol { margin: 18px 0 0; padding-left: 22px; color: var(--ash); font-size: 16px; line-height: 1.72; }
.prose li { margin-top: 9px; }
.prose li::marker { color: var(--accent); }
.prose strong { color: var(--white); font-weight: 400; }
.prose a { text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--accent-dim); }
.prose a:hover { text-decoration-color: currentColor; }

/* A statement that carries the page's whole promise. */
.callout {
  margin-top: 26px; padding: 24px 26px; border-radius: var(--r-card);
  background: var(--surface); box-shadow: inset 0 0 0 1px var(--hairline), inset 0 1px 0 0 var(--accent-dim);
}
.callout p { margin-top: 0; color: var(--white); font-size: 17px; line-height: 1.6; }
.callout p + p { margin-top: 12px; color: var(--ash); font-size: 15.5px; }

/* Command blocks on the docs page. */
.pre {
  display: block; margin-top: 18px; padding: 16px 18px; border-radius: var(--r-control);
  background: rgba(0, 0, 0, .34); box-shadow: inset 0 0 0 1px var(--hairline-hi);
  color: var(--white); font-family: ui-monospace, "Cascadia Mono", Consolas, "SF Mono", Menlo, monospace;
  font-size: 14px; line-height: 1.7; letter-spacing: 0; overflow-x: auto; white-space: pre;
}
.pre .c { color: var(--fog); }

/* Changelog entries. */
.rel { display: grid; grid-template-columns: 168px 1fr; gap: 28px; padding-top: 30px; margin-top: 34px; border-top: 1px solid var(--hairline); }
.rel:first-of-type { border-top: 0; padding-top: 0; margin-top: 22px; }
@media (max-width: 640px) { .rel { grid-template-columns: 1fr; gap: 10px; } }
.rel-meta { padding-top: 3px; }
.rel-v {
  display: inline-block; padding: 3px 11px; border-radius: var(--r-pill);
  background: rgba(156, 143, 203, .14); box-shadow: inset 0 0 0 1px var(--accent-dim);
  color: var(--accent-hi); font-size: 13px; font-variant-numeric: tabular-nums;
}
.rel-d { display: block; margin-top: 9px; color: var(--fog); font-size: 13px; }
.rel-body p:first-child, .rel-body ul:first-child { margin-top: 0; }

/* Footer, expanded now that there is somewhere to go. */
.foot-grid {
  display: grid; grid-template-columns: 1.4fr repeat(2, 1fr); gap: 40px;
  padding-bottom: 36px; margin-bottom: 30px; border-bottom: 1px solid var(--hairline);
}
@media (max-width: 780px) { .foot-grid { grid-template-columns: 1fr 1fr; gap: 30px; } }
.foot-brand p { margin-top: 14px; color: var(--fog); font-size: 13.5px; line-height: 1.6; max-width: 300px; }
.foot-col h4 { font-size: 12px; letter-spacing: .09em; text-transform: uppercase; color: var(--fog); font-weight: 400; margin-bottom: 14px; }
.foot-col a { display: block; margin-top: 10px; color: var(--ash); font-size: 14px; }
.foot-col a:first-of-type { margin-top: 0; }
.foot-col a:hover { color: var(--white); }
.foot-col a svg { width: 14px; height: 14px; vertical-align: -2px; margin-right: 7px; }

/* ===================================================================
   NAV MENUS
   The footer had Docs, Changelog, Privacy and Security and the top bar
   did not, so reaching a page meant scrolling past the whole landing
   first. These two menus mirror the footer's two columns exactly.

   Built on <details>/<summary>, not on a script: they open by click and
   by keyboard with nothing loaded, so the pages that carry no script of
   their own still have a working menu. nav.js only adds the two
   courtesies the element does not give you -- Escape, and closing when
   you click elsewhere.
   =================================================================== */
.menu { position: relative; }
.menu > summary {
  display: inline-flex; align-items: center; gap: 6px;
  list-style: none; cursor: pointer;
  color: var(--fog); font-size: 13.5px; white-space: nowrap;
  transition: color var(--t-normal) var(--ease);
}
.menu > summary::-webkit-details-marker { display: none; }
.menu > summary::after {
  content: ""; width: 6px; height: 6px; margin-top: -3px;
  border-right: 1.4px solid currentColor; border-bottom: 1.4px solid currentColor;
  transform: rotate(45deg);
  transition: transform var(--t-normal) var(--ease);
}
.menu > summary:hover { color: var(--white); }
.menu[open] > summary { color: var(--white); }
.menu[open] > summary::after { transform: rotate(-135deg); margin-top: 2px; }

.menu-pop {
  position: absolute; top: calc(100% + 16px); left: -14px; z-index: 70;
  min-width: 232px; padding: 8px;
  border-radius: var(--r-card);
  /* Opaque, not translucent. At .96 with a backdrop blur the page still read
     through clearly enough to compete with the menu's own labels -- a dropdown
     is a thing to read, not a place for atmosphere. */
  background: #17122A;
  box-shadow: inset 0 0 0 1px var(--hairline-hi), 0 24px 48px -20px rgba(0, 0, 0, .92);
}
.menu-pop a {
  display: block; padding: 9px 12px; border-radius: 9px;
  color: var(--ash); font-size: 14px;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.menu-pop a:hover { background: rgba(255, 255, 255, .06); color: var(--white); }
.menu-pop a span { display: block; margin-top: 2px; color: var(--fog); font-size: 12.5px; }
.menu-pop a:hover span { color: var(--fog); }

.motion .menu[open] .menu-pop { animation: pop 180ms var(--ease); }
@keyframes pop { from { opacity: 0; transform: translateY(-6px); } }
@media (prefers-reduced-motion: reduce) { .motion .menu[open] .menu-pop { animation: none; } }

/* Below the desktop breakpoint the same links live behind one button. */
.menu-mobile { display: none; }
.menu-mobile > summary { gap: 0; width: 38px; height: 38px; justify-content: center; border-radius: var(--r-control); box-shadow: inset 0 0 0 1px var(--hairline-hi); }
.menu-mobile > summary::after { display: none; }
.menu-mobile > summary svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; }
.menu-mobile .menu-pop { left: auto; right: -6px; min-width: 246px; }
.menu-mobile .menu-pop h4 {
  margin: 12px 12px 6px; font-size: 11px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--fog); font-weight: 400;
}
.menu-mobile .menu-pop h4:first-child { margin-top: 4px; }
@media (max-width: 1040px) {
  .menu-mobile { display: block; }
  /* .nav-links is display:none here but still an element, so the adjacent
     sibling rule that gives .nav-right a 22px margin keeps matching and beats
     the auto that should push the group right. */
  .nav-links + .nav-right { margin-left: auto; }
}
/* Narrow: the GitHub button folds into the menu rather than off the page.
   The primary action never folds anywhere. */
@media (max-width: 560px) { .nav-gh { display: none; } }
