/* TinyRocket — tinyrocket.app — BEFORE YOU LAUNCH
 *
 * One stylesheet, used by the live site and by the standalone preview, so the
 * two cannot drift. Tokens first; nothing below the token block ever names a
 * raw colour.
 */

:root {
  /* THE LIGHT PALETTE, complete, on the bare root. The lockup's own ground
     is white and the whole brand was sampled against it, so light is the
     canonical statement; the dark theme below is a REDEFINITION OF TOKENS
     and nothing else — no rule outside the token blocks ever branches on
     theme. color-scheme keeps the browser's own chrome (form controls,
     scrollbars, the canvas behind the page) agreeing with the page. */
  color-scheme: light;

  /* Sampled from the lockup, not guessed: --ink from the `Tiny` wordmark and
     the rocket's dark panels, --brand from the `Rocket` wordmark and the
     swoosh, --brand-bright from the nose cone's upper gradient. The navy is
     NOT black — #071532 is a blue-biased near-black, which is what lets the
     blue sit against it without vibrating. Do not substitute #000. */
  --brand-a: #006dff;
  --brand-b: #0080ff;
  /* The gradient a WHITE LABEL sits on: one step darker than the drawn
     brand pair, because white on #0080ff is 3.8:1 and a button label is
     ordinary-size text owed 4.5:1. White measures ≈4.9:1 on #005fe0 and
     ≈4.5:1 on #006dff. Decorative fills keep --brand-a/b as drawn. */
  --btn-grad-a: #005fe0;
  --btn-grad-b: #006dff;

  /* The flame's two stops — the exhaust, warm end to cool end. Used as an
     accent gradient only (the counters' warm dot, the odd underline), never
     as text on white. */
  --flame-a: #ff7a18;
  --flame-b: #ffc72c;

  /* Gradient TEXT stops (.grad): the headline's accent word renders in the
     brand blues. On white: #005fe0 ≈ 4.9:1, #0074f5 ≈ 4.4:1 — display-size
     text only (large-text scale), never body copy. */
  --ink-a: #005fe0;
  --ink-b: #0074f5;
  --line-live-a: #006dff;
  --line-live-b: #0080ff;
  --line-dead: rgb(7 21 50 / 26%);
  --line-box: rgb(7 21 50 / 42%);

  /* Ground and ink. White is the lockup's own ground; the raised and sunken
     grounds tint toward the brand blue rather than toward grey. */
  --bg: #ffffff;
  --bg-raised: #f6f8fc;
  --bg-sunken: #f1f5fb;
  --ink: #071532;
  --ink-soft: #3a4a6b;
  /* Muted text sits on the sunken ground too — eyebrows, captions — so it is
     measured there: #566180 is 5.6:1 on #f1f5fb. */
  --ink-mute: #566180;
  --line: #e2e8f3;
  /* Links and focus: the brand blue taken down one step so text passes
     4.5:1 on white (#005fe0 ≈ 4.9:1); fills still use --brand-a/b as drawn. */
  --accent: #005fe0;
  --accent-ink: #ffffff;
  /* A solid accent FILL with text on it (the ops chart's segmented control).
     In light the accent itself carries white ink; in dark the accent is a
     light blue, so the fill flips to navy ink — the pairing is a token
     because the safe ink depends on the theme. */
  --accent-fill: var(--accent);
  --accent-fill-ink: #ffffff;
  /* A control's edge is a non-text element: 3:1, not the hairline's 1.3:1. */
  --field-line: #97a3bd;
  --card-line: #e2e8f3;
  --card-shadow: 0 1px 2px rgb(7 21 50 / 5%), 0 12px 32px -18px rgb(7 21 50 / 18%);
  --danger: #c62828;
  --warn: #d08b3c;
  /* The good-news family: the app-mock's "Protection on" pill. Green means
     the product is doing its job, and nothing else on the site gets it. */
  --ok-ink: #0b6b2d;
  --ok-dot: #18a34a;
  --ok-wash: #e7f6ec;
  --ok-line: #bfe6cc;
  /* The modal backdrop's dimmer. */
  --scrim: rgb(0 0 0 / 62%);
  --shadow: 0 1px 2px rgb(7 21 50 / 8%), 0 20px 48px -16px rgb(7 21 50 / 18%);

  /* THE FLAME LAW. The flame gradient appears only as a thin rule or dot —
     the hero panel's base strip, the top rule of the honesty family, the top
     edge of the closing bands, the step-04 accent and the current-page nav
     underline. Never a button fill, never a
     background, never raw-flame text: #FF7A18 on white is ~2.5:1 and fails
     even the 3:1 graphics bar. Flame as TEXT or glyph is --flame-ink. */
  --flame-grad: linear-gradient(90deg, var(--flame-a), var(--flame-b));
  --flame-ink: #b34700;
  --flame-wash: color-mix(in srgb, var(--flame-b) 7%, var(--bg));

  /* The dissection's voice: the struck decoy, the registrable root, and the
     trajectory dashes that join the steps. */
  --url-decoy: var(--ink-mute);
  --url-root: var(--accent);
  --rule-dash: var(--line-dead);

  /* THE CHROME. Header, footer, mobile menu and the preview ribbon sit on
     a cool NEAR-BLACK in BOTH themes — deliberately not #000, which would
     read as a hole; #101114 keeps a breath of blue-grey so the brand blues
     and the flame sit on it without vibrating. These are the only colours
     that may appear on it. --chrome-brand is the brand blue lifted until it
     reads on the ground: #5CA8FF is 7.6:1 on #101114 where #006DFF would be
     4.2:1 — text needs the lift, and the gradient PILL keeps the true brand
     blues because a fill only owes 3:1. */
  --chrome-bg: #101114;
  --chrome-bg-raised: #1a1c21;
  --chrome-deep: #0a0b0d;
  --chrome-ink: #f2f6ff;
  --chrome-ink-soft: #e6ecf7;   /* near-white per the owner — 15.9:1 on the near-black; hover still lifts to --chrome-ink */
  --chrome-ink-mute: #8494b8;   /*  6.2:1 on the near-black */
  --chrome-line: rgb(242 246 255 / 16%);
  --chrome-field-line: rgb(242 246 255 / 40%);
  --chrome-brand: #5ca8ff;

  /* THE ARTWORK. Each theme gets its OWN cinematic answer, not a dimmed
     copy of the other's: light heroes fly the sunrise sky, dark flies the
     hostile night. The url primitives (--art-*) are theme-independent and
     width-tiered in their own block below; these tokens are the CHOICE,
     and the dark blocks restate them like any other token. The scrim
     strengths are tokens too — text over artwork always reads through a
     veil of the theme's own ground, and dark needs a heavier pour. */
  --hero-art: var(--art-hero-light);
  --hero-art-pos: 34% 22%;   /* owner: rocket to the RIGHT — show more left sky, the rocket rides the right third */
  --hero-art-pos-m: 40% 26%;
  --dl-art: var(--art-dl-light);
  --dl-art-pos: 42% 38%;
  --lic-art: url('/img/art-launchpad-1280w.jpg');
  --art-scrim-a: 92%;   /* behind the text column */
  --art-scrim-b: 68%;
  --art-scrim-c: 16%;   /* over the artwork's stage */
  --art-veil: 58%;      /* the flat veil under centred band text */
}

/* ------------------------------------------------------ the dark theme -- */
/* Designed, not inverted. The ground is a blue-black DEEPER than the chrome
   navy so header and footer still read as furniture on it; text is
   near-white with the same blue bias the light ink has; the accent family
   brightens because #006DFF sings on white and drowns on near-black — but
   the GRADIENT FILLS keep the true brand blues, since a fill only owes 3:1
   and white ink on it owes 4.5. The flame family stays warm and keeps its
   law; only its text form lightens. Shadows become darkness, not grey haze.

   TWIN BLOCKS, and the twinning is the mechanism: the same palette appears
   twice — once behind the OS preference, guarded :not([data-theme="light"])
   so an explicit light choice beats a dark OS; once behind the manual
   [data-theme="dark"] so the toggle wins in both directions. No token may
   have its ONLY definition here: everything below restates a token the
   light block above already defines. EDIT BOTH BLOCKS OR NEITHER. */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --bg: #050d1f;
    --bg-raised: #0a1730;
    --bg-sunken: #030a18;
    --ink: #e8eefb;
    --ink-soft: #b6c3de;    /* 11.0:1 on the ground */
    --ink-mute: #8494b8;    /*  6.0:1 on the raised ground */
    --line: #16233f;
    --card-line: #1b2a4a;
    --field-line: #556790;  /* 3.2:1 on the raised ground: a control's edge */

    /* Accent as TEXT lifts to #5CA8FF (7.9:1); the brand GRADIENT stays
       #006DFF→#0080FF — as a fill it clears 3:1 against the ground and
       keeps 4.5:1 under its white label, and the pill stays the brand. */
    --accent: #5ca8ff;
    --accent-fill: #5ca8ff;
    --accent-fill-ink: #071532;
    --ink-a: #6cb0ff;
    --ink-b: #8fc4ff;
    --line-live-a: #2f83ff;
    --line-live-b: #55a0ff;
    --line-dead: rgb(232 238 251 / 24%);
    --line-box: rgb(232 238 251 / 40%);

    --flame-ink: #ffa14f;   /* 9.7:1 — the flame speaks up in the dark */

    --danger: #ff7a6e;
    --warn: #e8a55c;
    --ok-ink: #7fd9a0;
    --ok-dot: #2ec164;
    --ok-wash: rgb(24 163 74 / 12%);
    --ok-line: rgb(46 193 100 / 32%);

    --card-shadow: 0 1px 2px rgb(0 0 0 / 35%), 0 12px 32px -18px rgb(0 0 0 / 60%);
    --shadow: 0 1px 2px rgb(0 0 0 / 45%), 0 20px 48px -16px rgb(0 0 0 / 65%);
    --scrim: rgb(0 0 0 / 72%);

    /* Dark's own artwork: the hero becomes the night battle, the download
       pad waits under floodlights. Positions move with the pictures. */
    /* One image across both themes (owner ruling, 14 Aug): dark keeps
       the sky and pours its own darker scrim over the text column. */
    --hero-art-pos: 30% 34%;
    --hero-art-pos-m: 37% 32%;
    /* download keeps the hand in the dark too — same ruling. */
    --dl-art-pos: 50% 42%;
    /* license keeps the launchpad in the dark too — same ruling. */
    --art-scrim-a: 96%;
    --art-scrim-b: 88%;
    --art-scrim-c: 30%;
    --art-veil: 78%;
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;

  --bg: #050d1f;
  --bg-raised: #0a1730;
  --bg-sunken: #030a18;
  --ink: #e8eefb;
  --ink-soft: #b6c3de;
  --ink-mute: #8494b8;
  --line: #16233f;
  --card-line: #1b2a4a;
  --field-line: #556790;

  --accent: #5ca8ff;
  --accent-fill: #5ca8ff;
  --accent-fill-ink: #071532;
  --ink-a: #6cb0ff;
  --ink-b: #8fc4ff;
  --line-live-a: #2f83ff;
  --line-live-b: #55a0ff;
  --line-dead: rgb(232 238 251 / 24%);
  --line-box: rgb(232 238 251 / 40%);

  --flame-ink: #ffa14f;

  --danger: #ff7a6e;
  --warn: #e8a55c;
  --ok-ink: #7fd9a0;
  --ok-dot: #2ec164;
  --ok-wash: rgb(24 163 74 / 12%);
  --ok-line: rgb(46 193 100 / 32%);

  --card-shadow: 0 1px 2px rgb(0 0 0 / 35%), 0 12px 32px -18px rgb(0 0 0 / 60%);
  --shadow: 0 1px 2px rgb(0 0 0 / 45%), 0 20px 48px -16px rgb(0 0 0 / 65%);
  --scrim: rgb(0 0 0 / 72%);

  /* one-image ruling: see the media block above */
  --hero-art-pos: 30% 34%;
  --hero-art-pos-m: 37% 32%;

  --dl-art-pos: 50% 42%;

  --art-scrim-a: 96%;
  --art-scrim-b: 88%;
  --art-scrim-c: 30%;
  --art-veil: 78%;
}
/* The artwork url primitives: theme-independent, so they live once, tiered
   by viewport width. Only the file a page actually paints is ever fetched —
   an unused custom property downloads nothing. The 2MB source PNGs never
   ship; these are the generated web derivatives and nothing else. */
:root {
  --art-hero-light: url('/img/hero-sky-1280w.jpg');
  --art-hero-dark: url('/img/art-threat-space-1280w.jpg');
  --art-dl-light: url('/img/art-tiny-hand-1280w.jpg');
  --art-dl-dark: url('/img/art-pad-night-1280w.jpg');
  --art-ascent: url('/img/art-ascent-1280w.jpg');
}
@media (max-width: 700px) {
  :root {
    --art-hero-light: url('/img/hero-sky-820w.jpg');
    --art-hero-dark: url('/img/art-threat-space-820w.jpg');
    --art-dl-light: url('/img/art-tiny-hand-820w.jpg');
    --art-dl-dark: url('/img/art-pad-night-820w.jpg');
    --art-ascent: url('/img/art-ascent-820w.jpg');
  }
}
@media (min-width: 1440px) {
  :root {
    --art-hero-light: url('/img/hero-sky-2000w.jpg');
    --art-hero-dark: url('/img/art-threat-space-2000w.jpg');
    --art-dl-light: url('/img/art-tiny-hand-2000w.jpg');
    --art-dl-dark: url('/img/art-pad-night-2000w.jpg');
    --art-ascent: url('/img/art-ascent-2000w.jpg');
  }
}
/* --flame-wash, --url-decoy, --url-root and --rule-dash are DERIVED tokens
   (color-mix and var() over the ones above, declared once on :root), so
   both dark blocks re-derive them for free — nothing to restate. */

/* SF Pro on a Mac, which is what this is for. Not a fallback — a page about a
   Mac utility, read on a Mac, should be set in the system's own face. */
:root {
  --sans: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Helvetica Neue',
          system-ui, sans-serif;
  --mono: ui-monospace, 'SF Mono', SFMono-Regular, Menlo, monospace;
  --wrap: 1080px;
  --gutter: clamp(1.25rem, 5vw, 2.5rem);
}

* { box-sizing: border-box; }

/* The document's own ground is the CHROME, not white: rubber-band
   overscroll shows <html> above the header and below the footer, and both
   are near-black in both themes — a white flash there breaks the frame
   (owner-reported, Safari). The body paints the page ground on top. */
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; background: #101114; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  /* Delay is zeroed too: the hero dissection staggers its beats with
     animation-delay, and a collapsed duration behind an intact delay would
     hold each beat's from-state (opacity: 0) for seconds. Reduced motion
     means the finished state, immediately. */
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { text-decoration-thickness: 2px; }

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

img, svg { max-width: 100%; }

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

.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;
}

.skip {
  position: absolute; left: 50%; translate: -50% -120%;
  background: var(--bg-raised); color: var(--ink); padding: .6rem 1rem;
  border-radius: 8px; z-index: 100; transition: translate .15s;
}
.skip:focus { translate: -50% 0.5rem; }

/* ---------------------------------------------------------------- type -- */

h1, h2, h3 { text-wrap: balance; margin: 0; letter-spacing: -0.022em; line-height: 1.06; }

.display {
  font-size: clamp(2.75rem, 7vw, 5rem);
  font-weight: 700;
  letter-spacing: -0.035em;
}
.h2 { font-size: clamp(1.9rem, 3.6vw, 2.9rem); font-weight: 650; }
.h3 { font-size: 1.2rem; font-weight: 620; letter-spacing: -0.012em; }

.lede {
  font-size: clamp(1.1rem, 1.9vw, 1.35rem);
  line-height: 1.5;
  color: var(--ink-soft);
  max-width: 34ch;
  text-wrap: pretty;
}
.prose { max-width: 62ch; color: var(--ink-soft); text-wrap: pretty; }
.prose p { margin: 0 0 1.1em; }

.eyebrow {
  font-family: var(--mono);
  font-size: .75rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-mute);
  margin: 0 0 1rem;
}

/* The dissection voice: a URL treated as physical evidence. Mono, roomy
   line-height so annotations have air, and it may break anywhere — an
   address is not prose and has no words to respect. */
.url {
  font-family: var(--mono);
  font-size: clamp(.9rem, 1.6vw, 1.05rem);
  font-weight: 500;
  letter-spacing: 0;
  line-height: 1.8;
  overflow-wrap: anywhere;
}
/* Annotation labels pinned to the evidence. */
.url-tag {
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .08em;
  text-transform: uppercase;
}

/* Gradient TEXT uses the ink stops, never the brand ones. */
.grad {
  background: linear-gradient(105deg, var(--ink-a), var(--ink-b));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}

kbd {
  font-family: var(--mono); font-size: .82em;
  background: var(--bg-sunken); border: 1px solid var(--line);
  border-bottom-width: 2px; border-radius: 6px;
  padding: .1em .45em; color: var(--ink);
}

/* Copyable commands. The script marks any kbd that holds a runnable command
   (spaces + length — single keys like Shift stay plain text) and this makes
   the affordance visible: a copy cursor, an accent on hover, and a "Copied"
   badge that names what just happened instead of leaving the click mute. */
kbd[data-copy] {
  cursor: copy; position: relative;
  transition: border-color .15s ease;
  -webkit-user-select: all; user-select: all;
}
kbd[data-copy]:hover,
kbd[data-copy]:focus-visible {
  border-color: color-mix(in srgb, var(--accent) 60%, var(--line));
}
kbd[data-copy]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
kbd[data-copy]::after {
  content: attr(data-copied-label); position: absolute; inset-inline-end: 0;
  inset-block-start: -1.9em; padding: .15em .55em;
  font-family: inherit; font-size: .78em; border-radius: 5px;
  background: var(--ink); color: var(--bg);
  opacity: 0; translate: 0 3px; pointer-events: none;
  transition: opacity .18s ease, translate .18s ease;
}
kbd[data-copy].copied::after { opacity: 1; translate: 0 0; }

/* -------------------------------------------------------------- header -- */

/* The chrome scope. Header, footer, mobile menu and the owner's ribbon
   REASSIGN the working tokens to the chrome family (the cool near-black),
   so every component that lands in them — nav links, the flame underline's
   ink, the language picker, focus rings, the hamburger — restyles itself
   without knowing it moved. Nothing inside the chrome may name a page
   colour directly. */
.site-head, .mobile-menu, .site-foot, .preview-ribbon {
  --bg: var(--chrome-bg);
  --bg-raised: var(--chrome-bg-raised);
  --bg-sunken: var(--chrome-deep);
  --ink: var(--chrome-ink);
  --ink-soft: var(--chrome-ink-soft);
  --ink-mute: var(--chrome-ink-mute);
  --line: var(--chrome-line);
  --card-line: var(--chrome-line);
  --field-line: var(--chrome-field-line);
  --accent: var(--chrome-brand);
  color-scheme: dark;
}

.site-head {
  position: sticky; top: 0; z-index: 50;
  backdrop-filter: saturate(180%) blur(20px);
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  border-bottom: 1px solid transparent;
  transition: border-color .2s;
}
.site-head[data-stuck='true'] { border-bottom-color: var(--line); }
.site-head .wrap { display: flex; align-items: center; gap: 1.5rem; height: 56px; }

.brand { display: inline-flex; align-items: center; text-decoration: none; color: inherit; }

/* The wordmark, set in type instead of shipped as pixels. The raster crop is
   navy-on-transparent and would vanish on the navy chrome; drawn in tokens it
   reads on every ground the site has — white, navy, and the dark theme — and
   the chrome carries no PNG weight at all. The raster lockup survives for the
   social card only. */
.wm { display: inline-flex; align-items: center; gap: .6rem; }
.wm-mark { width: 30px; height: 30px; display: block; }
.wm-text { display: flex; flex-direction: column; align-items: flex-start; line-height: 1; }
.wm-word {
  font-size: 1.06rem; font-weight: 700; letter-spacing: -.02em;
  color: var(--ink);
}
.wm-word b { font-weight: 700; color: var(--accent); }
.wm-tag {
  margin-top: .3em;
  font-size: .5rem; font-weight: 620;
  letter-spacing: .33em; text-transform: uppercase;
  color: var(--ink-mute);
  /* The tracking hangs .33em past the final H; nudge so the caps sit flush
     under the T and the ragged right stays inside the wordmark. */
  margin-inline-end: -.33em;
  white-space: nowrap;
}

.nav { margin-inline-start: auto; display: flex; align-items: center; gap: 1.6rem; }
.nav a { color: var(--ink-soft); text-decoration: none; font-size: .99rem; position: relative; top: -1px; }
.nav a:hover { color: var(--ink); }
/* You are here: full ink, and the one flame underline the nav is allowed. */
.nav a[aria-current="page"]:not(.btn),
.mobile-menu a[aria-current="page"],
.site-foot a[aria-current="page"] {
  color: var(--ink);
  background-image: var(--flame-grad);
  background-repeat: no-repeat;
  background-size: 100% 2px;
  background-position: bottom;
  padding-block-end: .15em;
}
.nav .btn { color: var(--accent-ink); font-size: .88rem; padding: .5rem 1.05rem; }
.nav .btn:hover { color: var(--accent-ink); }
@media (max-width: 720px) { .nav .hide-sm { display: none; } }

/* Mobile navigation. The hamburger appears only where the links hide, and the
   panel drops out of the sticky header (which is the positioning context). */
.nav-toggle {
  display: none;
  align-items: center; justify-content: center;
  width: 40px; height: 36px; padding: 0;
  background: transparent; border: 1px solid var(--field-line);
  border-radius: 10px; color: var(--ink); cursor: pointer;
}
.nav-toggle svg { width: 20px; height: 20px; display: block; }

/* The theme toggle: one quiet control in the chrome, present at every
   width (below 720px it stands beside the hamburger, so the mobile menu
   never needs its own copy). Hidden until the script arrives — with JS
   off the OS preference governs through the media query, and a control
   that could not do anything is not shown. Three states, one glyph each;
   the script sets data-mode and the matching glyph shows. */
/* ── The settings cog ─────────────────────────────────────────────────
   Theme and motion from one small panel on the chrome. The panel inherits
   the chrome scope deliberately: near-black glass dropping from a
   near-black header, like a menu bar's own menu, in both themes. */
.settings { position: relative; flex: none; }
.settings-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 36px; padding: 0; flex: none;
  background: transparent; border: 1px solid var(--field-line);
  border-radius: 10px; color: var(--ink-soft); cursor: pointer;
  transition: color .15s ease, border-color .15s ease, rotate .3s ease;
}
.settings-btn:hover { color: var(--ink); border-color: var(--ink-mute); }
.settings-btn[aria-expanded="true"] { color: var(--ink); border-color: var(--ink-mute); rotate: 30deg; }
.settings-btn svg { width: 18px; height: 18px; }
.settings-panel {
  position: absolute; right: 0; top: calc(100% + 12px); z-index: 60;
  min-width: 230px; padding: 14px 16px;
  background: var(--bg-raised); color: var(--ink);
  border: 1px solid var(--line); border-radius: 14px;
  box-shadow: 0 18px 44px -16px rgba(0, 0, 0, .55);
}
[dir='rtl'] .settings-panel { right: auto; left: 0; }
.set-group { border: 0; margin: 0; padding: 0; min-width: 0; }
.set-group + .set-group { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line); }
.set-group legend {
  padding: 0; margin-bottom: 4px;
  font-family: var(--mono); font-size: .68rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-mute);
}
.set-group label {
  display: flex; align-items: center; gap: 9px; padding: 5px 0;
  font-size: .92rem; color: var(--ink-soft); cursor: pointer;
}
.set-group label:hover { color: var(--ink); }
.set-group input { accent-color: var(--accent); margin: 0; }
.set-group input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.set-hint { margin: 2px 0 0 25px; font-size: .78rem; color: var(--ink-mute); }

/* The site-level motion override: the same kill the OS preference gets —
   duration AND delay, so no beat holds its from-state (see the reduced-
   motion block up top). */
:root[data-motion="reduce"] { scroll-behavior: auto; }
[data-motion="reduce"] *, [data-motion="reduce"] *::before, [data-motion="reduce"] *::after {
  animation-duration: .001ms !important;
  animation-delay: 0s !important;
  animation-iteration-count: 1 !important;
  transition-duration: .001ms !important;
}
.mobile-menu {
  position: absolute; inset-inline: 0; top: 100%;
  background: var(--bg-raised); border-bottom: 1px solid var(--line);
  padding: .35rem var(--gutter) .8rem;
  display: flex; flex-direction: column;
}
.mobile-menu a {
  color: var(--ink-soft); text-decoration: none; font-size: 1rem;
  padding: .85rem .2rem; border-bottom: 1px solid var(--card-line);
}
.mobile-menu a:last-child { border-bottom: 0; }
.mobile-menu a:hover, .mobile-menu a:focus-visible { color: var(--ink); }
.mobile-menu[hidden] { display: none; }
@media (max-width: 720px) {
  .nav { gap: .7rem; }
  .nav-toggle { display: inline-flex; }
}
/* Below 540px the header pill does not fit beside the lockup — brand +
   pill + settings + hamburger need ~515px, and 480 left the hamburger
   clipped half off-screen in the band between. The same link moves into
   the menu as its distinguished first row: full ink, the menu's one flame
   rule. Everywhere wider, the pill carries it. */
.mobile-menu .menu-cta { display: none; }
@media (max-width: 539.98px) {
  .nav > .btn { display: none; }
  .mobile-menu .menu-cta {
    display: block;
    color: var(--ink); font-weight: 620;
    border-bottom: 0;
    background-image: var(--flame-grad);
    background-repeat: no-repeat;
    background-size: 100% 2px;
    background-position: bottom;
  }
}
/* Never on desktop, even if a stale toggle left it un-hidden. */
@media (min-width: 721px) { .mobile-menu { display: none; } }

/* ------------------------------------------------------------- buttons -- */

/* Three shapes, one skeleton. Every button is a dark pill; -primary swaps in
   the brand gradient, -danger the destructive edge. A bare .btn IS the dark
   secondary, so a form submit that names no variant still looks like a button. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  font: inherit; font-size: .93rem; font-weight: 560;
  padding: .58rem 1.2rem; border-radius: 980px;
  background: var(--bg-raised); color: var(--ink); border: 1px solid var(--field-line);
  cursor: pointer; text-decoration: none; white-space: nowrap;
  transition: transform .14s ease, filter .18s ease,
              background-color .18s ease, border-color .18s ease, box-shadow .22s ease;
}
/* One motion for all of them: a small lift on hover, settled on press. */
.btn:hover:not(:disabled):not([aria-disabled="true"]) { transform: translateY(-1px); }
.btn:active:not(:disabled) { transform: translateY(0) scale(.975); }
.btn:disabled, .btn[aria-disabled="true"] {
  opacity: .5; cursor: not-allowed; transform: none; box-shadow: none;
}

/* Secondary (the "$10 once" button, and any bare .btn): the edge warms to the
   accent and the fill lifts a shade on hover. */
.btn-quiet { background: var(--bg-raised); color: var(--ink); border-color: var(--field-line); }
.btn:hover:not(:disabled):not(.btn-primary):not(.btn-danger) {
  background: color-mix(in srgb, var(--ink) 8%, var(--bg-raised));
  border-color: color-mix(in srgb, var(--accent) 55%, var(--field-line));
}

/* Primary: the brand gradient, in its label-bearing weights. One per
   view — the thing to actually do. */
.btn-primary {
  background: linear-gradient(105deg, var(--btn-grad-a), var(--btn-grad-b));
  color: var(--accent-ink); font-weight: 620; border-color: transparent;
}
.btn-primary:hover:not(:disabled) {
  filter: brightness(1.05);
  box-shadow: 0 8px 22px -8px color-mix(in srgb, var(--brand-b) 60%, transparent);
}

/* Danger: it moves money or cannot be undone. */
.btn-danger {
  background: transparent; color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 45%, var(--line));
}
.btn-danger:hover:not(:disabled) {
  background: color-mix(in srgb, var(--danger) 14%, var(--bg-raised));
  border-color: var(--danger);
}

.btn-row { display: flex; flex-wrap: wrap; gap: .75rem; align-items: center; }

/* A pill may not push the page wider than the phone: on the narrowest
   screens a long label wraps inside the pill instead of overflowing. */
@media (max-width: 419.98px) {
  .btn { white-space: normal; text-align: center; }
}

/* -------------------------------------------------------------- layout -- */

section { padding-block: clamp(4rem, 9vw, 7rem); }
.section-sunken { background: var(--bg-sunken); }
hr.rule { border: 0; border-top: 1px solid var(--line); margin: 0; }

.hero { padding-top: clamp(3rem, 7vw, 5.5rem); padding-bottom: clamp(2rem, 5vw, 3.5rem); }
/* A wrapper, so the mark sizes without an inline <style> in the template. */
.hero-mark { width: clamp(84px, 12vw, 116px); margin-bottom: 2rem; }
.hero-mark svg { width: 100%; height: auto; display: block; }
.hero .lede { max-width: 40ch; margin-block: 1.25rem 2rem; }
.hero-note { font-size: .88rem; color: var(--ink-mute); margin-top: 1rem; }

.grid { display: grid; gap: clamp(1.25rem, 3vw, 2rem); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(248px, 1fr)); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }

.card {
  background: var(--bg-raised); border: 1px solid var(--card-line);
  border-radius: 18px; padding: clamp(1.35rem, 2.6vw, 1.9rem);
  box-shadow: var(--card-shadow);
}
/* The marked-out card. On the public pages the blue border now belongs to
   pricing alone (the steps' 04 wears the flame instead); the ops dashboard
   still points with it. */
.card-win {
  border-color: color-mix(in srgb, var(--accent) 55%, var(--card-line));
  box-shadow: var(--shadow);
}
.card h3 { margin-bottom: .5rem; }
.card p { margin: 0; color: var(--ink-soft); font-size: .97rem; }

.step-n {
  font-family: var(--mono); font-size: .8rem; letter-spacing: .1em;
  color: var(--accent); margin-bottom: .9rem; display: block;
}

/* ------------------------------------------------------------- pricing -- */

.tick { list-style: none; margin: 1.75rem 0 0; padding: 0; display: grid; gap: .6rem; }
/* The checkmark is a POSITIONED marker, not a flex sibling. With flex, an
   inline <strong> or <a> inside the item became its own flex column and the
   sentence broke into pieces; positioning the tick keeps the text a single
   inline flow, so emphasis mid-sentence wraps like normal prose. */
.tick li {
  position: relative; padding-left: 1.4rem;
  color: var(--ink-soft); font-size: .97rem;
}
.tick li::before {
  content: '✓'; position: absolute; left: 0; top: 0;
  color: var(--accent); font-weight: 700; font-size: .9em;
}
/* A NEGATIVE line ("No Cloud Check") must not wear the same blue check as
   the features around it: greyed hollow marker, muted text, per the mock. */
.tick li.no { color: var(--ink-mute); }
.tick li.no::before { content: '○'; color: var(--ink-mute); font-weight: 400; }

/* A form with a view: content column beside a framed artwork panel that
   flips with the theme through the same choice tokens as the download
   hero. The art is a panel, not a ground — no scrim needed, text never
   crosses it. */
.split { display: grid; gap: 2.5rem; align-items: stretch; }
@media (min-width: 1020px) { .split { grid-template-columns: minmax(0, 34rem) 1fr; gap: 3.5rem; } }
.split-art {
  min-height: 280px; border-radius: 18px;
  border: 1px solid var(--card-line); box-shadow: var(--shadow);
  background-size: cover;
  background-position: 50% 38%;
}
/* My License flies the launchpad (pad-night after dark); privacy holds the
   tiny rocket in hand, both themes — owner-cast. */
.split-art-pad { background-image: var(--lic-art, url('/img/art-launchpad-1280w.jpg')); background-position: 62% 24%; }
.split-art-hand { background-image: url('/img/art-tiny-hand-1280w.jpg'); background-position: 46% 42%; }
/* Contact's header: the click moment full-bleed, lightly veiled, the
   glass panel carrying the words. */
.hero-contact {
  position: relative; isolation: isolate;
  background-image: linear-gradient(rgba(7, 12, 26, .30), rgba(7, 12, 26, .42)), url('/img/art-click-moment-1280w.jpg');
  background-size: cover; background-position: 50% 30%;
  padding-block: clamp(3.5rem, 8vw, 6rem);
}
@media (min-width: 1500px) {
  .hero-contact { background-image: linear-gradient(rgba(7, 12, 26, .30), rgba(7, 12, 26, .42)), url('/img/art-click-moment-2000w.jpg'); }
}
@media (max-width: 640px) {
  .hero-contact { background-image: linear-gradient(rgba(7, 12, 26, .30), rgba(7, 12, 26, .42)), url('/img/art-click-moment-820w.jpg'); }
}
.split-fig { margin: 0; align-self: center; min-width: 0; }
.split-fig img {
  display: block; width: 100%; height: auto; border-radius: 18px;
  border: 1px solid var(--card-line); box-shadow: var(--shadow);
}
/* Download flies vivid: the veil dies, the glass panel carries the words. */
.hero-dl-vivid::before { background: none; }
.hero-dl-vivid .wrap { display: flex; }
.hero-dl-vivid .band-panel { margin-block: auto; }

/* The honesty ledger: the can/can't columns inside the pride box, and the
   pledge line signed off under a hairline. */
.see-grid { display: grid; gap: 1.75rem; margin-top: 1.5rem; }
@media (min-width: 760px) { .see-grid { grid-template-columns: 1fr 1fr; gap: 2.5rem; } }
.see-label {
  margin: 0; font-family: var(--mono); font-size: .72rem;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-mute);
}
.see-grid .tick { margin-top: .9rem; }
.see-note { margin: 1rem 0 0; font-size: .9rem; color: var(--ink-soft); }
.honesty-sig {
  margin: 1.75rem 0 0; padding-top: 1.1rem; border-top: 1px solid var(--line);
  color: var(--ink); font-weight: 560;
}

.stepper { display: inline-flex; align-items: center; border: 1px solid var(--field-line); border-radius: 980px; overflow: hidden; }
.stepper button {
  font: inherit; font-size: 1.1rem; line-height: 1; width: 2.4rem; height: 2.4rem;
  background: var(--bg-raised); color: var(--ink); border: 0; cursor: pointer;
}
.stepper button:hover:not(:disabled) { background: var(--bg-sunken); }
.stepper button:disabled { color: var(--ink-mute); cursor: default; }
/* min-width, not width: the label inside is a translated seat count, and
   the plural repair of 19 Aug 2026 made several of them much longer than
   "5 Macs" — Russian's genitive plural renders "5 компьютеров Mac",
   Polish's "5 komputerów Mac". The pill grows to fit and the row it sits
   in wraps rather than overflowing. */
.stepper output {
  min-width: 4.6rem; padding: 0 .6rem; text-align: center;
  font-variant-numeric: tabular-nums; font-size: .95rem;
}

/* ------------------------------------------------------------- contact -- */

/* The `hidden` attribute has to actually hide things.
   It works through the browser's own stylesheet ([hidden] { display: none }),
   which ANY author rule outranks — so `.field { display: grid }` below quietly
   defeated it, and the "Something else" text box sat there in the open beside
   a perfectly ordinary answer. !important because this is not a style, it is
   the meaning of the attribute. */
[hidden] { display: none !important; }

.field { display: grid; gap: .4rem; margin-bottom: 1.1rem; }
.field label { font-size: .9rem; color: var(--ink-soft); font-weight: 520; }
/* --field-line, not --line.
 *
 * --line is a hairline BETWEEN things, and at 1.30:1 it is right for that and
 * useless as the edge of a control: the contact form rendered as four labels
 * floating over a blank card. A control's boundary is a non-text element and
 * has to clear 3:1. */
.field input, .field textarea, .field select {
  font: inherit; font-size: 1rem; color: var(--ink);
  background: var(--bg-raised); border: 1px solid var(--field-line);
  border-radius: 12px; padding: .7rem .85rem; width: 100%;
}
.field input:hover, .field textarea:hover, .field select:hover { border-color: var(--ink-mute); }

/* The select, stripped of its native chrome.
 *
 * Left alone, a <select> renders in the SYSTEM appearance regardless of the
 * page: a white pill with a blue chevron sitting in the middle of a dark
 * form, looking like a control that belongs to a different application. It is
 * the one form element that does not inherit, so it has to be told. */
.field select {
  appearance: none;
  -webkit-appearance: none;
  padding-inline-end: 2.5rem;
  cursor: pointer;
}
/* The chevron cannot live on the <select> itself — a replaced element has no
   pseudo-elements — so the wrapper draws it, and a mask means it takes its
   colour from the palette instead of being baked into a data URI per theme. */
.select-wrap { position: relative; display: block; }
.select-wrap::after {
  content: '';
  position: absolute; inset-inline-end: 1.05rem; top: 50%;
  width: 8px; height: 8px; pointer-events: none;
  /* Drawn with borders, not a masked image: masks of data-URI SVGs proved
     unreliable across renderers, and two borders at 45° cannot fail. */
  border-right: 1.8px solid var(--ink-mute);
  border-bottom: 1.8px solid var(--ink-mute);
  translate: 0 -70%; rotate: 45deg;
  transition: border-color .15s ease, rotate .15s ease, translate .15s ease;
}
.select-wrap:hover::after { border-color: var(--ink); }
/* The open list is drawn by the OS and cannot be themed, so give its options
   a ground that at least does not flash white on the way. (This is also why
   the script replaces the whole popup below: no CSS reaches a native one.) */
.field select option { background: var(--bg-raised); color: var(--ink); }

/* ------------------------------------------------------ custom dropdown -- */
/* The script hides the native select and drives it through this. The trigger
   copies the input styling above so the form stays one family; the panel is
   the part the native control could never give us. */
.dd-trigger {
  font: inherit; font-size: 1rem; color: var(--ink); text-align: start;
  background: var(--bg-raised); border: 1px solid var(--field-line);
  border-radius: 12px; padding: .7rem 2.5rem .7rem .85rem; width: 100%;
  cursor: pointer; transition: border-color .15s ease;
}
.dd-trigger:hover { border-color: var(--ink-mute); }
.dd-trigger:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.dd-trigger[aria-expanded="true"] { border-color: color-mix(in srgb, var(--accent) 60%, var(--field-line)); }
.field-error .dd-trigger { border-color: var(--danger); }
/* The wrapper's chevron keeps working — rotate it while open. */
.select-wrap:has(.dd-trigger[aria-expanded="true"])::after { rotate: 225deg; translate: 0 -30%; }

.dd-panel {
  position: absolute; inset-inline: 0; top: calc(100% + 6px); z-index: 30;
  margin: 0; padding: .35rem; list-style: none;
  background: var(--bg-raised); border: 1px solid var(--card-line);
  border-radius: 14px; box-shadow: var(--shadow);
  max-height: 16rem; overflow-y: auto;
  opacity: 0; translate: 0 -4px; pointer-events: none;
  transition: opacity .14s ease, translate .14s ease;
}
.dd-panel[data-open] { opacity: 1; translate: 0 0; pointer-events: auto; }

.dd-option {
  display: flex; align-items: center; gap: .6rem;
  padding: .55rem .7rem; border-radius: 9px; cursor: pointer;
  color: var(--ink); font-size: .97rem;
}
/* The tick column holds space for every row, so labels align whether or not
   a row is the chosen one. */
.dd-option::before {
  /* A text glyph, not a masked image, for the same reliability reason as
     the chevron above. The column is reserved on every row so labels align
     whether or not a row is the chosen one. */
  content: '\2713'; width: 1em; flex: 0 0 1em;
  font-weight: 700; color: var(--ink-a); visibility: hidden;
}
.dd-option[aria-selected="true"]::before { visibility: visible; }
.dd-option:hover,
.dd-option[data-active] {
  background: color-mix(in srgb, var(--accent) 14%, var(--bg-raised));
}
.field textarea { min-height: 9rem; resize: vertical; }
.field .hint { font-size: .82rem; color: var(--ink-mute); }
.field-error input, .field-error textarea, .field-error select { border-color: var(--danger); }
.err { color: var(--danger); font-size: .85rem; }

.notice {
  border: 1px solid var(--line); border-radius: 14px;
  padding: 1rem 1.15rem; margin-bottom: 1.5rem; font-size: .95rem;
}
.notice-ok { border-color: color-mix(in srgb, var(--accent) 50%, var(--line)); }

/* honeypot — never shown, never announced */
.hp { position: absolute; inset-inline-start: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* -------------------------------------------------------------- footer -- */

.site-foot {
  background: var(--bg);       /* the chrome near-black, via the scope above */
  color: var(--ink-soft);
  padding-block: 3.5rem 4rem; font-size: .92rem;
}
.foot-grid { display: grid; gap: 2.5rem; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
.site-foot h2 { font-size: .8rem; text-transform: uppercase; letter-spacing: .1em; color: var(--ink-mute); margin: 0 0 .9rem; font-weight: 600; }
.site-foot ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .55rem; }
.site-foot a { color: var(--ink-soft); text-decoration: none; }
.site-foot a:hover { color: var(--ink); text-decoration: underline; }
.foot-base { margin-top: 3rem; padding-top: 1.75rem; border-top: 1px solid var(--line); color: var(--ink-mute); display: flex; flex-wrap: wrap; gap: 1rem 2rem; align-items: center; }

.foot-brand { margin-bottom: .9rem; }
.foot-tagline { color: var(--ink-mute); margin: 0; max-width: 26ch; }

/* The language picker: still the no-JS <details> pattern, but the summary is
   dressed as a control — globe, current language, chevron — instead of a
   bare word with a triangle. */
.lang-picker { margin-top: 3rem; }
.lang-picker summary {
  display: inline-flex; align-items: center; gap: .5rem;
  cursor: pointer; color: var(--ink-soft); list-style: none;
}
.lang-picker summary::-webkit-details-marker { display: none; }
.lang-picker summary:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }
.lang-picker summary svg { width: 16px; height: 16px; display: block; }
/* The chevron, borrowed from .select-wrap: two borders at 45°, which cannot
   fail, and symmetric — so it needs no RTL mirror. */
.lang-chev {
  width: 8px; height: 8px; flex: none;
  border-inline-end: 1.8px solid currentColor;
  border-block-end: 1.8px solid currentColor;
  rotate: 45deg; translate: 0 -2px;
  transition: rotate .15s ease, translate .15s ease;
}
.lang-picker details[open] .lang-chev { rotate: 225deg; translate: 0 2px; }
.lang-picker ul {
  margin-top: .9rem;
  grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
  gap: .55rem 1.5rem;
}
.lang-current { color: var(--ink); }

/* The owner's preview ribbon: micro-chrome, English-only like ops, shown
   only to whoever can already see through the shutter. The chrome's deep
   shade, a step below the header's, so the two bands read as two. */
.preview-ribbon {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: .35rem 1.25rem;
  background: var(--bg-sunken); color: var(--ink-soft);
  font-family: var(--mono); font-size: .78rem;
  padding: .35rem var(--gutter);
}
.preview-ribbon a { color: var(--accent); text-underline-offset: 3px; }

/* --------------------------------------------------------- legal pages -- */

.legal { max-width: 70ch; }
.legal h1 { font-size: clamp(2rem, 4vw, 2.75rem); margin-bottom: 1rem; }
.legal h2 { font-size: 1.25rem; margin: 2.75rem 0 .75rem; }
.legal p, .legal li { color: var(--ink-soft); }
.legal ul { padding-inline-start: 1.15rem; display: grid; gap: .5rem; }
.legal strong { color: var(--ink); font-weight: 600; }

/* ------------------------------------------------------------------ rtl -- */
/* Arabic and Hebrew. The layout above is written in LOGICAL properties, so
   almost everything mirrors on its own once <html dir="rtl"> is set — these
   are only the cases a logical property cannot express. */

/* Borrowed as a chevron by rotating a corner: 45° points down in LTR, and
   the mirror of that is 135°, which a direction-agnostic rotate cannot know. */
[dir="rtl"] .select-wrap::after { rotate: 135deg; }
[dir="rtl"] .select-wrap:has(.dd-trigger[aria-expanded="true"])::after {
  rotate: 225deg; translate: 0 -30%;
}

/* The copy badge and the tick column read against the text they belong to. */
[dir="rtl"] kbd[data-copy]::after { inset-inline-end: auto; inset-inline-start: 0; }

/* Latin runs inside RTL prose — TinyRocket, shell commands, prices — are
   isolated so the bidi algorithm cannot drag surrounding punctuation into
   them. `isolate` is exactly this job. */
[dir="rtl"] kbd, [dir="rtl"] code, [dir="rtl"] .price {
  unicode-bidi: isolate;
}

/* Numbers with units stay LTR internally ("$10", "14 days") while sitting in
   the RTL flow. */
[dir="rtl"] .price { direction: ltr; }

/* --------------------------------------------------------------- ops -- */
/* The owner's dashboard. Not part of the product's design language on
   purpose — it borrows the tokens so it feels like the same house, and
   nothing more, because nobody but the owner will ever see it. */
.ops-n { margin: 0; font-size: 2.6rem; font-weight: 700; letter-spacing: -.03em;
         line-height: 1; font-variant-numeric: tabular-nums; color: var(--ink); }
.ops-l { margin: .35rem 0 0; font-size: .95rem; color: var(--ink-soft); }
.ops-sub { margin: .5rem 0 0; font-size: .85rem; color: var(--ink-mute); }
.ops-money { margin: .2rem 0 0; font-size: 1.6rem; font-weight: 650;
             font-variant-numeric: tabular-nums; color: var(--ink-soft); }

/* The dashboard's own furniture. A tool, not a page: tighter than the site
   because everything here is scanned rather than read. */
.ops-nav {
  display: flex; align-items: center; gap: .3rem;
  border-bottom: 1px solid var(--card-line); margin-bottom: 2rem;
}
.ops-tab {
  padding: .55rem .9rem; font-size: .95rem; font-weight: 550;
  color: var(--ink-mute); text-decoration: none;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.ops-tab:hover { color: var(--ink-soft); }
.ops-tab.is-on { color: var(--ink); border-bottom-color: var(--accent); }
.ops-nav-end { margin-inline-start: auto; font-size: .85rem; color: var(--ink-mute); }

.ops-h1 { font-size: 1.75rem; font-weight: 680; letter-spacing: -.02em; margin: 0 0 1.25rem; }
.ops-h2 { font-size: 1.05rem; font-weight: 620; margin: 2.25rem 0 .9rem; }

.ops-search { display: flex; gap: .6rem; }
.ops-search input {
  flex: 1; font: inherit; font-size: 1rem; color: var(--ink);
  background: var(--bg-raised); border: 1px solid var(--field-line);
  border-radius: 12px; padding: .7rem .85rem;
}

/* A table wants the card's edge, not its padding. */
.ops-flush { padding: 0; overflow-x: auto; }
.ops-table { width: 100%; border-collapse: collapse; font-size: .92rem; }
.ops-table th {
  text-align: start; font-weight: 600; color: var(--ink-mute);
  font-size: .82rem; padding: .8rem 1rem; white-space: nowrap;
}
.ops-table td { padding: .7rem 1rem; color: var(--ink-soft); }
.ops-table tbody tr { border-top: 1px solid var(--card-line); }
/* ── Dialogs ────────────────────────────────────────────────────────────
   Built on <dialog>, not a div pretending to be one. showModal() gives the
   focus trap, the inert background, Escape-to-close and the top-layer
   stacking for free — all of which are laborious to reimplement and easy to
   get subtly wrong, and every one of them is what makes a modal usable with
   a keyboard rather than merely visible. */
.dlg {
  border: 1px solid var(--card-line);
  border-radius: 16px;
  background: var(--bg-raised);
  color: var(--ink);
  padding: 0;
  max-width: min(34rem, calc(100vw - 2rem));
  box-shadow: var(--shadow);
}
.dlg::backdrop { background: var(--scrim); backdrop-filter: blur(3px); }
.dlg-body { padding: 1.6rem 1.6rem 1.2rem; }
.dlg-body h2 { margin: 0 0 .6rem; font-size: 1.12rem; letter-spacing: -.01em; }
.dlg-body p { margin: 0; color: var(--ink-soft); font-size: .94rem; line-height: 1.55; }
.dlg-actions {
  display: flex; justify-content: flex-end; gap: .6rem; flex-wrap: wrap;
  padding: 1.1rem 1.6rem; border-top: 1px solid var(--card-line);
  background: var(--bg-sunken); border-radius: 0 0 15px 15px;
}
/* Wide, for a document rather than a question. */
.dlg.is-wide { max-width: min(46rem, calc(100vw - 2rem)); }
.dlg-scroll { max-height: min(70vh, 40rem); overflow-y: auto; }
/* Scrollbars hidden but scrolling kept — Firefox, then WebKit/Blink. */
.dlg-scroll, .chart-table, .ops-flush { scrollbar-width: none; -ms-overflow-style: none; }
.dlg-scroll::-webkit-scrollbar,
.chart-table::-webkit-scrollbar,
.ops-flush::-webkit-scrollbar { width: 0; height: 0; display: none; }

/* The dialog animates in only when the viewer has not asked it not to. */
@media (prefers-reduced-motion: no-preference) {
  .dlg[open] { animation: dlg-in .16s ease-out; }
  @keyframes dlg-in {
    from { opacity: 0; transform: translateY(6px) scale(.99); }
    to   { opacity: 1; transform: none; }
  }
}

/* A row you can click anywhere on. The pointer and the hover tint are the
   only affordance a table row gets, so both matter — without them nobody
   discovers the row is live. */
/* A sortable column header. The arrow only appears on the active column —
   showing one on every header makes none of them mean anything. */
.ops-sort { color: inherit; text-decoration: none; display: inline-flex; gap: .3rem; align-items: center; }
.ops-sort:hover { color: var(--ink); }
.ops-sort.is-on { color: var(--ink); }
.ops-arrow { font-size: .8em; }
.ops-sort:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

/* A card that is a button. Resets the browser's button styling so it keeps
   the card's own look, and stays left-aligned like the text it contains. */
/* ── The interactive chart ──────────────────────────────────────────── */
.chart { margin: 0 0 1.6rem; }
.chart-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem; margin-bottom: .6rem; }
.chart-value {
  margin: 0; font-size: 1.45rem; font-weight: 600; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; color: var(--ink); line-height: 1.1;
}
.chart-when { margin: .15rem 0 0; font-size: .82rem; color: var(--ink-mute); }
.chart-units { display: flex; gap: .3rem; flex-shrink: 0; }
.chart-unit {
  font: inherit; font-size: .78rem; padding: .25rem 0; border-radius: 980px;
  /* Equal width so 48h / 30d / 26w line up as one segmented control rather
     than three differently sized pills. */
  min-width: 3rem; text-align: center;
  border: 1px solid var(--card-line); background: transparent;
  color: var(--ink-mute); cursor: pointer;
  font-variant-numeric: tabular-nums;
  transition: color .12s, background .12s, border-color .12s;
}
.chart-unit:hover { color: var(--ink-soft); border-color: var(--field-line); }
.chart-unit.is-on { color: var(--accent-fill-ink); background: var(--accent-fill); border-color: var(--accent-fill); }
.chart-unit:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.chart-svg {
  width: 100%; height: 140px; display: block; overflow: visible;
  border-radius: 8px; cursor: crosshair;
}
.chart-svg:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.chart-axis {
  display: flex; justify-content: space-between;
  font-size: .74rem; color: var(--ink-mute); margin-top: .4rem;
}
.chart-table { margin-top: .9rem; }
.chart-table summary { font-size: .82rem; color: var(--ink-mute); cursor: pointer; }
.chart-table summary:hover { color: var(--ink-soft); }
.chart-table .ops-table { margin-top: .8rem; }

.ops-card-btn {
  display: block; width: 100%; text-align: start; font: inherit;
  cursor: pointer; color: inherit;
}
.ops-card-btn:hover { border-color: color-mix(in srgb, var(--accent) 45%, var(--card-line)); }
.ops-card-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.ops-more {
  float: inline-end; font-size: .72rem; letter-spacing: .04em;
  color: var(--ink-mute); text-transform: none;
}
.ops-card-btn:hover .ops-more { color: var(--accent); }

.ops-row { cursor: pointer; }
.ops-row:hover { background: var(--bg-raised); }
.ops-row-link { color: var(--ink); text-decoration: none; }
/* Underline the WORD, not the row's whole first cell. Spanning a link across
   text set in two different inks gave a two-tone underline that read as a
   rendering fault. */
.ops-row:hover .ops-row-link { text-decoration: underline; text-underline-offset: 3px; }
/* A state badge and the date it happened, as one block rather than a pill
   with an orphaned line under it. */
.ops-state { display: flex; flex-direction: column; align-items: flex-end; gap: .35rem; }
.ops-state-when { font-size: .82rem; color: var(--ink-soft); white-space: nowrap; }

.ops-row-when { color: var(--ink); white-space: nowrap; }
.ops-row-when .t { color: var(--ink-mute); margin-inline-start: .35rem; }
/* The focus ring has to survive: keyboard users never see the hover state,
   and this link is their only way into the row. */
.ops-row-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 3px; }

.ops-num { text-align: end; font-variant-numeric: tabular-nums; color: var(--ink); }
/* A numeric HEADER has to follow its column.
   `.ops-table th` is two classes deep and `.ops-num` is one, so the header
   kept text-align:start while the cells below it went end — the column read
   as misaligned and looked like a layout bug, which it was. */
.ops-table th.ops-num { text-align: end; }
.ops-mono { font-family: var(--mono); font-size: .8rem; color: var(--ink-mute); }

.ops-pill {
  display: inline-block; padding: .15rem .5rem; border-radius: 980px;
  font-size: .76rem; font-weight: 600; letter-spacing: .01em;
}
.ops-pill.is-on { background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--accent); }
.ops-pill.is-off { background: color-mix(in srgb, var(--danger) 16%, transparent); color: var(--danger); }

/* A button that reads as a link, for a destructive action inside a row. */
.ops-link-btn {
  font: inherit; font-size: .9rem; background: none; border: 0; padding: 0;
  color: var(--accent); cursor: pointer; text-decoration: underline;
  text-underline-offset: 3px;
}

.ops-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 1.5rem; }
.ops-facts {
  margin-top: 1.25rem; display: grid; gap: 1rem 2rem;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
}
.ops-facts span { display: block; font-size: .78rem; color: var(--ink-mute); margin-bottom: .15rem; }
.ops-facts strong { font-size: .95rem; font-weight: 600; color: var(--ink-soft); }

/* Inputs inside a dashboard card. The site's .field wrapper carries a label
   and a grid; these sit inline beside a button, so they need the same skin
   without the layout. */
.ops-card-input,
.card .ops-actions input[type='text'],
.card .ops-actions input[type='number'] {
  font: inherit; font-size: .95rem; color: var(--ink);
  background: var(--bg-sunken); border: 1px solid var(--field-line);
  border-radius: 10px; padding: .5rem .7rem;
}
.ops-card-input::placeholder { color: var(--ink-mute); }
.ops-card-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The take-back count is the site's custom dropdown (the layout script turns
   any .select-wrap > select into the themed listbox). Sized and skinned to sit
   in the compact ops action row rather than as a full-width form field. */
.ops-actions .select-wrap { flex: 0 0 auto; width: 5.5rem; }
.ops-actions .select-wrap > select { appearance: none; -webkit-appearance: none; }
.ops-actions .dd-trigger {
  padding: .5rem 1.9rem .5rem .7rem;
  font-size: .95rem; border-radius: 10px;
  background: var(--bg-sunken);
}

/* ── Buttons, and there are only three ──────────────────────────────────
   The dashboard had one button style plus ad-hoc modifiers, and once the
   border came off, the quiet one was #08080A on a #0b0b0e page — a control
   indistinguishable from a label. "Find" read as the word Find.

   So: three, and no more. Every button on every ops page is exactly one of
   these, and the choice is a MEANING, not a look.

     .ops-btn              the workhorse. Anything you can do.
     .ops-btn.is-primary   the one thing this form is FOR. One per form.
     .ops-btn.is-danger    it moves money, or it cannot be undone.

   Plus one modifier, .is-pulse, and one non-button:

     .is-pulse             a primary that is WAITING to be pressed.
     .ops-link-btn         a text action inside a table row, where a pill
                           would out-shout the row it belongs to.

   These ARE the site's .btn — same size, border, dark-pill default and
   brand-gradient primary — so a button is one thing across the whole site.
   The dashboard only adds STATE (.is-pulse / .is-inert / .is-busy) on top. */
.ops-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  font: inherit; font-size: .93rem; font-weight: 560; cursor: pointer;
  padding: .58rem 1.2rem; border-radius: 980px; text-decoration: none; white-space: nowrap;
  background: var(--bg-raised); color: var(--ink); border: 1px solid var(--field-line);
  transition: transform .14s ease, filter .18s ease,
              background-color .18s ease, border-color .18s ease, box-shadow .22s ease;
}
/* Secondary/workhorse hover — edge warms to the accent, fill lifts a shade. */
.ops-btn:hover:not(:disabled):not(.is-inert):not(.is-busy):not(.is-primary):not(.is-danger) {
  background: color-mix(in srgb, var(--ink) 8%, var(--bg-raised));
  border-color: color-mix(in srgb, var(--accent) 55%, var(--field-line));
}
/* The same lift-on-hover, settle-on-press as the public .btn, so a button
   behaves the same on the dashboard as on the site. Unavailable and busy
   buttons are excluded — they are not asking to be pressed. */
.ops-btn:hover:not(:disabled):not(.is-inert):not(.is-busy) { transform: translateY(-1px); }
.ops-btn:active:not(:disabled):not(.is-inert) { transform: translateY(0) scale(.975); }
.ops-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.ops-btn.is-primary {
  background: linear-gradient(105deg, var(--btn-grad-a), var(--btn-grad-b));
  color: var(--accent-ink); font-weight: 620; border-color: transparent;
}
.ops-btn.is-primary:not(:disabled):not(.is-inert):hover {
  filter: brightness(1.05);
  box-shadow: 0 8px 22px -8px color-mix(in srgb, var(--brand-b) 60%, transparent);
}

.ops-btn.is-danger {
  background: transparent; color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 45%, var(--line));
}
.ops-btn.is-danger:hover:not(:disabled) {
  background: color-mix(in srgb, var(--danger) 14%, var(--bg-raised));
  border-color: var(--danger);
}

/* Breathing, not blinking. Reserved for a primary that is waiting on the
   operator — the reply box, and nothing else. 2.4s is slower than a resting
   heartbeat on purpose; anything quicker nags. */
.ops-btn.is-pulse:not(:disabled):not(.is-inert) {
  animation: ops-go-pulse 2.4s ease-in-out infinite;
}
@keyframes ops-go-pulse {
  0%, 100% { box-shadow: 0 6px 18px -8px color-mix(in srgb, var(--brand-b) 45%, transparent); }
  50%      { box-shadow: 0 8px 26px -6px color-mix(in srgb, var(--brand-b) 80%, transparent); }
}
/* Pointing at it is already attention; the pulse stops so hover is not
   fighting an animation for the same property. */
.ops-btn.is-pulse:hover, .ops-btn.is-busy { animation: none; }
@media (prefers-reduced-motion: reduce) {
  .ops-btn.is-pulse { animation: none !important; }
}

/* Two ways to be unavailable, one appearance. `.is-inert` is the
   aria-disabled primary — still focusable, still announced, refused by the
   submit handler; `:disabled` is the ordinary kind. */
.ops-btn:disabled,
.ops-btn.is-inert {
  opacity: .5; cursor: not-allowed; box-shadow: none; transform: none; filter: none;
}

/* A button that is AWAY WORKING is not a button that is unavailable, and
   dimming it on top of its muted label put "Drafting…" at 2:1 against the
   card — unreadable exactly while it is the only thing with news. */
.ops-btn.is-busy,
.ops-btn.is-busy:disabled,
.ops-btn.is-busy.is-inert {
  opacity: 1; filter: none; box-shadow: none;
}
/* Muted "working" label on the dark workhorse; a busy PRIMARY keeps its dark
   ink so it stays legible on the gradient. */
.ops-btn.is-busy:not(.is-primary) { color: var(--ink-mute); }

.ops-actions { display: flex; gap: .6rem; align-items: flex-start; flex-wrap: wrap; }

/* A switch, not a checkbox. The browser's own tick is a light square with
   a light border that fights every dark surface it sits on; this is the
   same control wearing the dashboard's accent. The real input stays in
   the DOM (hidden, not display:none) so it still submits, still focuses,
   and still reads as a checkbox to VoiceOver. */
.ops-switch {
  display: inline-flex; align-items: center; gap: .55rem; cursor: pointer;
  font-size: .85rem; color: var(--ink-mute); user-select: none;
}
.ops-switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.ops-switch-track {
  position: relative; flex: none; width: 2.05rem; height: 1.2rem;
  border-radius: 980px; background: var(--bg-sunken);
  border: 1px solid var(--field-line);
  transition: background-color .18s ease, border-color .18s ease;
}
.ops-switch-track::after {
  content: ''; position: absolute; top: 50%; left: 3px;
  width: .8rem; height: .8rem; border-radius: 50%;
  background: var(--ink-mute); transform: translateY(-50%);
  transition: transform .18s ease, background-color .18s ease;
}
.ops-switch input:checked + .ops-switch-track {
  background: color-mix(in srgb, var(--accent) 32%, var(--bg-sunken));
  border-color: color-mix(in srgb, var(--accent) 60%, var(--field-line));
}
.ops-switch input:checked + .ops-switch-track::after {
  transform: translate(.8rem, -50%); background: var(--accent);
}
.ops-switch input:checked ~ .ops-switch-label { color: var(--ink); }
.ops-switch input:focus-visible + .ops-switch-track {
  outline: 2px solid var(--accent); outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
  .ops-switch-track, .ops-switch-track::after { transition: none; }
}



/* ── The support inbox ──────────────────────────────────────────────────
   Conversations, not records: the thread page reads top to bottom like the
   mail it came from, with the owner's replies set apart by the accent the
   rest of the dashboard already means "you" by. */

/* An unread count on the nav tab. Deliberately quiet — the tab is a place
   the eye already goes, and a red circle would make every visit to the
   stats page feel like an emergency. */
.ops-badge {
  display: inline-block; margin-left: .45rem; padding: .05rem .45rem;
  border-radius: 980px; font-size: .72rem; font-weight: 600;
  font-variant-numeric: tabular-nums;
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--accent);
}

/* Could-not-know, which is NOT zero. Deliberately not the accent colour:
   the accent means "here is a fact about your desk", and this badge's
   whole content is that there is no fact. Warm rather than alarming —
   it is usually a deploy window, and an alarm that cries during every
   deploy is an alarm that gets ignored the one time it matters. */
.ops-badge-unknown {
  background: color-mix(in srgb, var(--warn) 20%, transparent);
  color: var(--warn);
  cursor: help;
}

/* The third state a licence never needed: answered, waiting on the other
   side. Muted, because it is the pill that means "nothing to do". */
.ops-pill.is-wait { background: color-mix(in srgb, var(--ink-mute) 16%, transparent); color: var(--ink-soft); }

.msg-list { display: grid; gap: .8rem; }
.msg {
  background: var(--bg-raised); border: 1px solid var(--card-line);
  border-radius: 14px; padding: 1rem 1.2rem;
}
/* The owner's replies carry the accent on their edge — the same "this one
   is yours" cue a chat app uses, without pretending to be one. */
.msg.is-out { border-inline-start: 3px solid color-mix(in srgb, var(--accent) 55%, var(--card-line)); }
.msg-head { margin: 0 0 .45rem; font-size: .85rem; font-weight: 600; color: var(--ink); }
.msg-head span { font-weight: 400; color: var(--ink-mute); }
/* pre-wrap keeps the writer's paragraphs and line breaks without ever
   interpreting a byte of their markup. */
.msg-body {
  font-size: .95rem; line-height: 1.6; color: var(--ink-soft);
  white-space: pre-wrap; overflow-wrap: anywhere;
}

/* The reply box: the card-input skin, sized for sentences. */
.ops-textarea {
  font: inherit; font-size: .95rem; line-height: 1.55; color: var(--ink);
  background: var(--bg-sunken); border: 1px solid var(--field-line);
  border-radius: 10px; padding: .7rem .8rem;
  width: 100%; resize: vertical;
}
.ops-textarea::placeholder { color: var(--ink-mute); }
.ops-textarea:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ====================================================== TinyRocket site -- */

/* (The header lockup used to be a raster here; it is now the .wm type
   wordmark above, which reads on every ground and costs no bytes.) */

/* -------------------------------------------------------------- hero ---- */

.hero-grid {
  display: grid; gap: clamp(2rem, 5vw, 4rem); align-items: center;
  grid-template-columns: 1fr;
}
/* The sitewide .display cap (5rem) was measured against a full-width hero;
   this hero's text column is ~515px at desktop, where 5rem wraps the
   headline into four ragged lines. Capped to what the column can carry. */
.hero-grid .display { font-size: clamp(2.6rem, 4.6vw, 3.75rem); }
@media (min-width: 900px) {
  .hero-grid { grid-template-columns: minmax(0, 11fr) minmax(0, 9fr); }
}

/* The product visual: a drawn panel in the app's own idiom, not a fabricated
   screenshot. It reads as an illustration and is labelled as one. */
/* Inside the panel the dissection is an instrument readout, one step
   smaller than the hero scale, so the whole address holds together. */
.app-mock .dissect .url { font-size: .8rem; }
.app-mock {
  background: var(--bg);
  border: 1px solid var(--card-line);
  border-radius: 18px;
  box-shadow: var(--shadow);
  padding: 1.25rem;
  display: grid; gap: 1rem;
}
.mock-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
/* The panel's own titlebar wordmark, a step smaller than the chrome's. */
.mock-head .wm { gap: .5rem; }
.mock-head .wm-mark { width: 24px; height: 24px; }
.mock-head .wm-word { font-size: .95rem; }
.mock-pill {
  display: inline-flex; align-items: center; gap: .45rem;
  font-size: .78rem; font-weight: 620; color: var(--ok-ink);
  background: var(--ok-wash); border: 1px solid var(--ok-line);
  border-radius: 980px; padding: .25rem .7rem; white-space: nowrap;
}
.mock-pill::before { content: ''; width: .5rem; height: .5rem; border-radius: 50%; background: var(--ok-dot); }
.mock-body {
  border: 1px solid var(--card-line); border-radius: 12px;
  background: var(--bg-raised); padding: 1.25rem;
  display: grid; gap: 1rem; text-align: center;
}
/* ── The dissection ─────────────────────────────────────────────────────
   A URL treated as physical evidence: set in mono, annotated in layers.
   Each segment is an inline-block with identical vertical padding, so the
   pinned labels live INSIDE their segment — when the address wraps, every
   annotation wraps with the piece it belongs to and nothing collides. */
.dissect { direction: ltr; unicode-bidi: isolate; text-align: start; }
.dissect .url { margin: 0; line-height: 1.4; }
.url-seg {
  display: inline-block; position: relative; white-space: nowrap;
  vertical-align: baseline; padding-block: 2.1em 1.9em;
}
.url-dim { color: var(--ink-mute); font-weight: 400; }
.url-decoy { color: var(--url-decoy); }
.url-root { color: var(--ink); font-weight: 600; }
.url-txt { position: relative; display: inline-block; }
/* The strike is a drawn element, not text-decoration — text-decoration
   cannot be animated as a wipe. */
.url-decoy .url-txt::after {
  content: ''; position: absolute; inset-inline: 0;
  top: calc(50% - 1px); height: 2px;
  background: var(--url-decoy); transform-origin: left center;
}
/* The registrable root gets the 2px solid underline: this is the one part
   of the address that cannot lie. */
.url-root .url-txt::after {
  content: ''; position: absolute; inset-inline: 0;
  bottom: -4px; height: 2px;
  background: var(--url-root); transform-origin: left center;
}
.dissect .tag-top {
  position: absolute; top: .35em; inset-inline-start: 0;
  color: var(--ink-mute); white-space: nowrap;
}
/* A thin leader from the label down toward its evidence. */
.dissect .tag-top::after {
  content: ''; position: absolute; top: 100%; inset-inline-start: 3px;
  height: .5em; width: 1px; background: var(--line-box);
}
.dissect .tag-bot {
  position: absolute; bottom: .15em; inset-inline-start: 0;
  color: var(--flame-ink); white-space: nowrap;
}
.url-flag { display: inline-block; font-size: .8em; margin-inline-end: .35em; }
/* The verdict, in the app's own indicator language. */
.url-verdict {
  display: inline-flex; align-items: center; gap: .5rem; margin: .9rem 0 0;
  font-family: var(--mono); font-size: .78rem; font-weight: 600;
  letter-spacing: .02em; color: var(--ink);
  border: 1px solid var(--card-line); border-radius: 980px;
  padding: .3rem .8rem; background: var(--bg);
}
.verdict-dot { width: .5rem; height: .5rem; border-radius: 50%; background: var(--flame-ink); flex: none; }

/* The four-beat reveal, hero only (.dissect-live): URL appears whole →
   decoy strikes → root underlines and the flag drops → verdict fades up.
   Plays once on load, holds forever, never loops. Base styles above are
   the RESTING state; every keyframe only supplies the from-state, and
   fill-mode both holds it through each stagger — the global
   reduced-motion kill (duration + delay zeroed) lands on the finished
   state instantly. */
@keyframes d-rise { from { opacity: 0; translate: 0 6px; } }
@keyframes d-fade { from { opacity: 0; } }
@keyframes d-draw { from { transform: scaleX(0); } }
@keyframes d-drop { from { opacity: 0; translate: 0 -4px; } }
.dissect-live .url { animation: d-rise .35s cubic-bezier(.2, .7, .2, 1) both; }
.dissect-live .url-decoy .tag-top { animation: d-fade .3s cubic-bezier(.2, .7, .2, 1) .6s both; }
.dissect-live .url-decoy .url-txt::after { animation: d-draw .4s cubic-bezier(.2, .7, .2, 1) .6s both; }
.dissect-live .url-root .tag-top { animation: d-fade .3s cubic-bezier(.2, .7, .2, 1) 1.2s both; }
.dissect-live .url-root .url-txt::after { animation: d-draw .4s cubic-bezier(.2, .7, .2, 1) 1.2s both; }
.dissect-live .url-decoy .tag-bot { animation: d-drop .35s cubic-bezier(.2, .7, .2, 1) 1.2s both; }
.dissect-live .url-verdict { animation: d-rise .35s cubic-bezier(.2, .7, .2, 1) 1.8s both; }

/* A framed mount for the static reuses on the content pages. */
.dissect-mount { margin-top: 2rem; }

.mock-stats {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: .75rem;
  border-top: 1px solid var(--card-line); padding-top: 1rem;
}
.mock-stat b { display: block; font-size: 1.35rem; font-variant-numeric: tabular-nums; }
.mock-stat span { font-size: .75rem; color: var(--ink-mute); }
.mock-flame {
  height: 4px; border-radius: 2px;
  background: linear-gradient(90deg, var(--flame-a), var(--flame-b));
}

/* Four mini-features under the hero. */
.minis {
  display: grid; gap: 1rem 1.5rem; margin-top: clamp(2rem, 5vw, 3rem);
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.mini { display: flex; gap: .7rem; align-items: flex-start; }
.mini svg { width: 22px; height: 22px; flex: none; color: var(--accent); margin-top: .15rem; }
.mini b { display: block; font-size: .95rem; }
.mini span { font-size: .85rem; color: var(--ink-mute); }

/* "Works where you click" + privacy, side by side. Start-aligned so no
   card ever stretches past its own content. */
.duo { display: grid; gap: 1.5rem; grid-template-columns: 1fr; align-items: start; }
@media (min-width: 900px) { .duo { grid-template-columns: 3fr 2fr; } }
.works-chips { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: 1rem; }
.chip {
  display: inline-flex; align-items: center; gap: .45rem;
  border: 1px solid var(--card-line); border-radius: 980px;
  background: var(--bg); padding: .4rem .85rem; font-size: .9rem;
}
.chip svg { width: 16px; height: 16px; color: var(--accent); }

/* The four steps. */
.steps { display: grid; gap: 1.25rem; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }

/* The honesty family: the candour wears the same proud frame sitewide.
   Not an admonition — the left border is gone; a 3px flame rule sits on
   the top edge like a headline, over the warm wash. Candour holds still:
   nothing in this family ever animates. */
/* The pride content wears NO box (owner retired the slab in every dress —
   cream and chrome alike): it sits on the page's own ground like any other
   section, and only a short flame rule above the heading marks it as the
   thing we are proudest of. */
.honesty { border: 0; background: none; padding: 0; border-radius: 0; }
.honesty > h2::before, .honesty > .h2::before {
  content: ''; display: block; width: 44px; height: 3px;
  border-radius: 2px; background: var(--flame-grad); margin-bottom: 1rem;
}
/* The pride moment on /how-it-works: promoted to feature scale. */
.honesty-feature { padding: clamp(2rem, 4vw, 2.75rem); }
.honesty-feature h2 { margin-bottom: 1rem; }
.honesty h2, .honesty h3 { margin-bottom: .6rem; }
.honesty p { color: var(--ink-soft); font-size: .97rem; margin: 0 0 .9em; }
.honesty p:last-child { margin-bottom: 0; }

/* ------------------------------------------------------------ pricing --- */

.plans { display: grid; gap: 1.5rem; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); align-items: stretch; }
.plan {
  position: relative; display: flex; flex-direction: column; gap: .4rem;
  background: var(--bg); border: 1px solid var(--card-line);
  border-radius: 16px; padding: 1.75rem; box-shadow: var(--card-shadow);
}
.plan-hot { border-color: color-mix(in srgb, var(--brand-a) 55%, var(--card-line)); box-shadow: var(--shadow); }
.plan-flag {
  position: absolute; top: -0.8rem; left: 50%; translate: -50% 0;
  font-size: .72rem; font-weight: 650; letter-spacing: .04em; white-space: nowrap;
  color: var(--accent-ink); background: linear-gradient(105deg, var(--btn-grad-a), var(--btn-grad-b));
  border-radius: 980px; padding: .25rem .8rem;
}
/* The cards answer the pointer like the rest of the estate now does. */
.plan { transition: transform .22s ease, box-shadow .22s ease, border-color .22s ease; }
.plan:hover {
  transform: translateY(-4px);
  box-shadow: 0 18px 38px -20px rgb(7 21 50 / 30%);
}
/* The one-time card is the page's moment: it rides the chrome, a step
   larger, the neighbours standing back. Full palette restated — a themed
   scope carries its whole palette. */
.plan-hot {
  border-color: var(--chrome-line);
  background: linear-gradient(180deg, #15171d, var(--chrome-bg));
  --bg: var(--chrome-bg); --bg-raised: var(--chrome-bg-raised);
  --bg-sunken: var(--chrome-deep);
  --ink: var(--chrome-ink); --ink-soft: var(--chrome-ink-soft);
  --ink-mute: var(--chrome-ink-mute);
  --line: var(--chrome-line); --card-line: var(--chrome-line);
  --field-line: var(--chrome-field-line); --accent: var(--chrome-brand);
  color: var(--ink); color-scheme: dark;
  transform: scale(1.04); z-index: 1;
  box-shadow: 0 26px 52px -22px rgb(7 21 50 / 45%);
}
.plan-hot:hover {
  transform: scale(1.04) translateY(-4px);
  box-shadow: 0 30px 58px -22px rgb(7 21 50 / 50%);
}
.plan-hot .plan-price { color: var(--ink); }
/* The guarantee band flies the ascent, not the launchpad: its own crop. */
.band-guarantee .band-art { object-position: 74% 40%; }
/* The no-guarantee acknowledgment on the buy forms: reviewed and ticked
   before any money moves — the server refuses without it. */
.ack {
  display: flex; gap: .6rem; align-items: flex-start;
  margin: 0 0 .9rem; font-size: .82rem; line-height: 1.45;
  color: var(--ink-soft); text-align: start; cursor: pointer;
}
.ack input { margin-top: .2rem; accent-color: var(--brand); flex: none; }
.ack:hover { color: var(--ink); }
.plan h2, .plan h3 { font-size: 1.25rem; }
.plan .plan-sub { color: var(--ink-mute); font-size: .9rem; margin: 0; min-height: 2.6em; }
.plan-price { font-size: 2.6rem; font-weight: 700; letter-spacing: -.02em; margin: .5rem 0 0; font-variant-numeric: tabular-nums; }
.plan-price small { font-size: .95rem; font-weight: 500; color: var(--ink-mute); letter-spacing: 0; }
.plan .tick { margin-top: 1.1rem; margin-bottom: 1.4rem; }
.plan .btn { margin-top: auto; width: 100%; }

/* The comparison table. Positioning establishes the scrollport as the
   table's containing block too; without it Chromium includes the clipped
   560px table in the document's scroll width on narrow phones. */
.compare-wrap {
  position: relative;
  overflow-x: auto;
  margin-top: clamp(2rem, 5vw, 3rem);
}
.compare { width: 100%; border-collapse: collapse; min-width: 560px; font-size: .95rem; }
.compare th, .compare td { padding: .8rem 1rem; border-bottom: 1px solid var(--card-line); text-align: center; }
.compare th:first-child, .compare td:first-child { text-align: start; }
.compare thead th { font-size: .85rem; color: var(--ink-mute); font-weight: 620; }
.compare tbody th { font-weight: 500; }
.compare .yes { color: var(--accent); font-weight: 650; }
.compare .no { color: var(--ink-mute); }

/* The money-back strip. */
.strip {
  display: flex; flex-wrap: wrap; gap: 1rem 2.5rem; align-items: center;
  border: 1px solid var(--card-line); border-radius: 14px;
  background: var(--bg-raised); padding: 1.25rem 1.75rem;
}
/* Two-up: each cell takes half the row while it fits, stacks when not. */
.strip > div { flex: 1 1 18rem; }
.strip b { display: block; }
.strip span { color: var(--ink-mute); font-size: .9rem; }

/* ------------------------------------------- the direction's furniture -- */

/* A thin flame rule on a card's top edge. The only gradient a surface may
   wear, and only ever 3px of it. */
.flame-top {
  background-image: var(--flame-grad);
  background-repeat: no-repeat;
  background-size: 100% 3px;
  background-position: top;
}

/* The journey, not a rail: the old dashed rule cut through card borders
   and read as a stray line (owner: "odd line"). Now each step number is a
   node — a circled beacon — and the handoff between steps is an arrow in
   the gap, drawn only where all four share a row. */
.steps { position: relative; }
.steps .card { position: relative; transition: transform .22s ease, box-shadow .22s ease; }
.steps .card:hover {
  transform: translateY(-4px);
  box-shadow: 0 16px 34px -18px rgb(7 21 50 / 28%);
}
.steps .step-chip { display: block; margin: -0.35rem 0 .9rem; }
.steps .step-n {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.1rem; height: 2.1rem; border-radius: 50%;
  border: 1.5px solid color-mix(in srgb, var(--accent) 55%, transparent);
  margin-bottom: 1rem; letter-spacing: .05em;
}
@media (min-width: 1060px) {
  .steps .card:not(:last-child)::after {
    content: '\2192';
    position: absolute; top: 1.55rem; right: -1.35rem; width: 1.45rem;
    text-align: center; color: var(--ink-mute); font-size: 1.05rem;
    z-index: 1;
  }
  [dir='rtl'] .steps .card:not(:last-child)::after {
    content: '\2190'; right: auto; left: -1.35rem;
  }
}
/* Step 04 is the step that is yours: the flame's accent, not the blue
   border — blue borders belong to pricing now. */
.card-final .step-n {
  color: var(--flame-ink);
  border-color: color-mix(in srgb, var(--flame-ink) 60%, transparent);
}

/* The closing bands' send-off signature: a 2px flame rule on the top edge. */
.cta-band { position: relative; }
.cta-band::before {
  content: '';
  position: absolute; top: 0; inset-inline: 0; height: 2px;
  background: var(--flame-grad);
}

/* The hero-note as a row of facts, not a mumble: sentences separated by
   middots, wrapping as facts. */
.hero-note span { white-space: normal; }

/* The privacy mini-ledger: two rows of mono bookkeeping in the product's
   own voice. */
.ledger {
  margin-top: 1.25rem; border-top: 1px solid var(--card-line);
  font-family: var(--mono); font-size: .82rem;
}
.ledger-row {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 1rem; padding-block: .6rem;
  border-bottom: 1px solid var(--card-line);
}
.ledger-k { color: var(--ink); font-weight: 600; white-space: nowrap; }
.ledger-k .ledger-g { color: var(--accent); font-weight: 700; margin-inline-end: .35em; }
.ledger-v { color: var(--ink-mute); text-align: end; }

/* ----------------------------------------------- how-it-works pipeline -- */

/* Sources → TinyRocket → your browser, as furniture beside the hero copy.
   HTML flex (so RTL mirrors the flow for free) with two small connector
   SVGs — live trajectory in, dashed continuation out — that are the only
   parts needing an explicit mirror. */
.pipeline {
  display: flex; align-items: center; justify-content: center; gap: .25rem;
}
.pipe-sources { display: grid; gap: .8rem; }
.pipe-chip {
  display: flex; align-items: center; gap: .55rem;
  border: 1px solid var(--card-line); border-radius: 12px;
  background: var(--bg); box-shadow: var(--card-shadow);
  padding: .5rem .75rem;
}
.pipe-chip svg { width: 18px; height: 18px; color: var(--accent); flex: none; }
.pipe-chip .url-tag { color: var(--ink-mute); }
.pipe-link { flex: none; display: block; }
.pipe-link path { fill: none; stroke-width: 1.5; }
.pipe-live path { stroke: var(--line-live-a); }
.pipe-dash path { stroke: var(--rule-dash); stroke-dasharray: 4 4; }
[dir='rtl'] .pipe-link { transform: scaleX(-1); }
.pipe-rocket {
  position: relative; flex: none;
  width: 72px; height: 72px; border-radius: 50%;
  border: 1px solid var(--card-line); background: var(--bg);
  box-shadow: var(--shadow);
  display: grid; place-items: center;
}
.pipe-rocket img { width: 44px; height: 44px; }
.pipe-check {
  position: absolute; inset-block-end: -2px; inset-inline-end: -2px;
  width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--bg); border: 1px solid var(--card-line);
  color: var(--flame-ink); font-size: .8rem; font-weight: 700;
}
.pipe-browser {
  flex: none; display: grid; place-items: center;
  width: 52px; height: 52px; border-radius: 14px;
  border: 1px solid var(--card-line); background: var(--bg);
  box-shadow: var(--card-shadow);
}
.pipe-browser svg { width: 26px; height: 26px; color: var(--ink-soft); }
/* The pipeline compacts on the narrowest screens rather than overflowing. */
@media (max-width: 479.98px) {
  .pipeline { gap: .15rem; }
  .pipe-link { width: 28px; }
  .pipe-rocket { width: 56px; height: 56px; }
  .pipe-rocket img { width: 34px; height: 34px; }
  .pipe-browser { width: 44px; height: 44px; }
  .pipe-chip { padding: .4rem .55rem; }
}

/* --------------------------------------------------- pricing furniture -- */

/* One thing is pointed at. The Plus card's flag steps back to an outlined
   chip so the brand-gradient pill on the one-time card stands alone. */
.plan-flag-quiet {
  background: var(--bg); color: var(--ink-mute);
  border: 1px solid var(--card-line);
}
/* The table agrees with the card highlight: the TinyRocket column sits on
   the raised ground. Semantics untouched — this is paint. */
.compare th:nth-child(3), .compare td:nth-child(3) { background: var(--bg-raised); }
/* The hero's fact line, in mono. */
.fact-line { font-family: var(--mono); font-size: .82rem; }

/* -------------------------------------------------- download furniture -- */

/* The rocket mark rides a dashed quarter-arc: almost there points up. */
.launch-arc { position: relative; height: 7.25rem; margin-bottom: .75rem; }
.launch-arc::before {
  content: '';
  position: absolute; left: 50%; top: 2.9rem;
  width: 9.5rem; height: 9.5rem;
  translate: -92% 0; rotate: -45deg;
  border: 1px dashed var(--rule-dash); border-radius: 50%;
  border-color: var(--rule-dash) transparent transparent transparent;
}
.launch-arc img { position: absolute; left: 50%; top: 0; translate: -50% 0; }
.dl-card { max-width: 30rem; margin-inline: auto; text-align: start; }
/* The no-database promise lives inside the product's frame: it is a
   feature, set in the ledger's mono voice. */
.dl-promise {
  margin: 1rem 0 0; font-family: var(--mono);
  font-size: .78rem; line-height: 1.7; color: var(--ink-mute);
}
.dl-note {
  margin-top: 2.5rem; font-size: .95rem; color: var(--ink-mute);
  max-width: 44ch; margin-inline: auto;
}
.dl-note + .dl-note { margin-top: 1.5rem; }

/* ------------------------------------------- legal family typography -- */

/* Section numbers: a mono counter in the accent, set before each heading —
   the checklist voice applied to the small print. Cards inside a .legal
   wrap (the guides' in-the-works grid) opt out: they carry .step-n. */
.legal { counter-reset: legal-sec; }
.legal h2::before {
  counter-increment: legal-sec;
  content: counter(legal-sec, decimal-leading-zero);
  font-family: var(--mono); font-size: .8rem; font-weight: 500;
  letter-spacing: .08em; color: var(--accent);
  margin-inline-end: .6rem;
}
.legal .card h2::before { content: none; }

/* The designed meta strip: one hairline card instead of two grey
   paragraphs — mono date, governing-language note beside it. */
.legal-meta {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem 1.5rem;
  border: 1px solid var(--card-line); border-radius: 12px;
  background: var(--bg-raised);
  padding: .7rem 1rem; margin: 0 0 3rem;
  font-size: .85rem; color: var(--ink-mute);
}
.legal-meta .legal-date { font-family: var(--mono); font-size: .8rem; }

/* The terms contents: a mono jump list whose link text is the exact
   headings, numbered by the same counter the headings use. */
.toc {
  list-style: none; margin: 0 0 2.75rem; padding: 0;
  counter-reset: toc; display: grid; gap: .5rem;
  font-family: var(--mono); font-size: .85rem;
}
.toc li::before {
  counter-increment: toc;
  content: counter(toc, decimal-leading-zero);
  font-size: .8rem; letter-spacing: .08em; color: var(--ink-mute);
  margin-inline-end: .6rem;
}
.toc a { text-decoration: none; }
.toc a:hover { text-decoration: underline; }

/* The refunds promise, at the scale of a price: the figure is the policy. */
.refund-promise { margin-bottom: 2.5rem; }
.refund-promise strong {
  display: block; font-size: clamp(2rem, 4vw, 2.6rem); font-weight: 700;
  letter-spacing: -.02em; line-height: 1.15;
  font-variant-numeric: tabular-nums; color: var(--ink);
  margin-bottom: .6rem;
}

/* ----------------------------------------------------- the license page -- */

/* The key a paying customer stares at, in the house style. */
.key-box {
  width: 100%; font-family: var(--mono); font-size: .75rem; line-height: 1.5;
  color: var(--ink); background: var(--bg-sunken);
  border: 1px solid var(--field-line); border-radius: 12px;
  padding: .75rem; resize: vertical;
  -webkit-user-select: all; user-select: all;
}

/* ---------------------------------------------------- the cinematic hero -- */

/* Full-bleed artwork behind the hero. The picture is a token (--hero-art),
   so the theme system swaps it like any colour: light flies the sunrise,
   dark flies the night battle — no dimmed compromise. Text never sits on
   raw pixels: the ::before scrim pours the theme's own ground over the
   text column and thins toward the artwork's stage. Page-specific casts
   (.hero-how, .hero-dl) only re-point the tokens. */
.hero-cine {
  position: relative;
  background-image: var(--hero-art);
  background-size: cover;
  background-position: var(--hero-art-pos);
}
.hero-cine::before {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(100deg,
    color-mix(in srgb, var(--bg) var(--art-scrim-a), transparent) 0%,
    color-mix(in srgb, var(--bg) var(--art-scrim-b), transparent) 42%,
    color-mix(in srgb, var(--bg) var(--art-scrim-c), transparent) 58%,
    transparent 64%);
}
/* The scrim leans the way the text does. */
[dir='rtl'] .hero-cine::before { transform: scaleX(-1); }
.hero-cine > .wrap { position: relative; }

/* Small print over artwork gets its own frosted plate — a scrim tuned for
   a headline is never enough for .88rem text over sunrise clouds. Measured
   on rendered pixels: the veil takes the worst spot past 4.5:1. */
.hero-cine .hero-note {
  display: inline-block;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  border-radius: 12px; padding: .55rem .85rem; margin-inline-start: -.85rem;
}

/* /how-it-works opens on the calm climb — the same art in both themes;
   the scrim tokens already flip with the theme. */
.hero-how { --hero-art: var(--art-ascent); --hero-art-pos: 62% 30%; }
/* The ascent's sunlit cloud bank reaches further under this hero's copy
   column, so its scrim reaches further too. Same tokens, longer pour. */
.hero-how::before {
  background: linear-gradient(100deg,
    color-mix(in srgb, var(--bg) var(--art-scrim-a), transparent) 0%,
    color-mix(in srgb, var(--bg) var(--art-scrim-b), transparent) 52%,
    color-mix(in srgb, var(--bg) var(--art-scrim-c), transparent) 72%,
    transparent 86%);
}

/* /download: the pad is still being built. Light gets the tiny rocket
   placed by hand; dark gets the night before launch (both via --dl-art).
   Centred copy wants a flat veil rather than the leaning gradient. */
.hero-dl { --hero-art: var(--dl-art); --hero-art-pos: var(--dl-art-pos); }
/* Full ink for the one line over the pad: the lighter veil that lets the
   charm through cannot carry ink-soft past 4.5:1, and full ink can. */
.hero-dl .lede { color: var(--ink); }
.hero-dl::before {
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--bg) var(--art-scrim-c), transparent) 0%,
      transparent 30%),
    radial-gradient(115% 90% at 50% 62%,
      color-mix(in srgb, var(--bg) var(--art-veil), transparent) 0%,
      color-mix(in srgb, var(--bg) var(--art-scrim-b), transparent) 58%,
      color-mix(in srgb, var(--bg) var(--art-scrim-c), transparent) 100%);
}

/* One permitted motion: the dissection panel floats, slow as a balloon.
   The global reduced-motion kill collapses it to stillness. */
@media (prefers-reduced-motion: no-preference) {
  .hero-cine .app-mock { animation: mock-float 8s ease-in-out infinite alternate; }
  @keyframes mock-float {
    from { translate: 0 5px; }
    to   { translate: 0 -6px; }
  }
}

/* On the phone the artwork crops to its focal point and the panel stacks
   below the copy; the scrim goes vertical so the stacked column reads. */
@media (max-width: 899.98px) {
  .hero-cine::before {
    background: linear-gradient(180deg,
      color-mix(in srgb, var(--bg) var(--art-scrim-a), transparent) 0%,
      color-mix(in srgb, var(--bg) var(--art-scrim-b), transparent) 55%,
      color-mix(in srgb, var(--bg) var(--art-scrim-c), transparent) 100%);
  }
  /* The phone crop recentres on the rocket: each theme states where its
     rocket is (--hero-art-pos-m), so it stays in frame or exits on purpose. */
  .hero-cine { background-position: var(--hero-art-pos-m, var(--hero-art-pos)); }
  .hero-how { background-position: 72% 30%; }
  .hero-dl { background-position: var(--dl-art-pos); }
}

/* --------------------------------------------------- the threat section -- */

/* "The attack is a link": concrete attack shapes as compact dissection
   cards. The evidence language is the site's own decoration — disguise,
   the URL as it actually is, and the cost in one flat sentence. Calm
   expert showing the trick; the only red is the word that names the harm. */
/* Two by two: four shapes read as a matched set, never a row of three
   with an orphan. */
.threat-grid { display: grid; gap: 1.25rem; margin-top: 2.5rem; }
@media (min-width: 760px) { .threat-grid { grid-template-columns: 1fr 1fr; } }
.threat-card { display: flex; flex-direction: column; }
/* The plain-sentence contrast: dressed-as vs really-opens, then a verdict
   that names the borrowed brand. Sentence-case labels on purpose — the
   smallcaps diff idiom read as a terminal, and the owner asked for human. */
.tc-contrast { margin-top: .9rem; padding-top: .95rem; border-top: 1px solid var(--line); }
.tc-row { display: flex; align-items: baseline; gap: .75rem; margin: .4rem 0; }
.tc-k { flex: none; min-width: 7.2rem; font-size: .82rem; color: var(--ink-mute); }
.tc-v { font-family: var(--mono); font-size: .88rem; white-space: nowrap; min-width: 0; }
/* An address is one word: it never breaks. When the row can't hold both,
   the label steps above the value instead. */
@media (max-width: 1250px) and (min-width: 760px) { .tc-row { flex-direction: column; gap: .05rem; } }
@media (max-width: 470px) { .tc-row { flex-direction: column; gap: .05rem; } }
.tc-decoy { color: var(--url-decoy, var(--ink-soft)); }
.tc-root { color: var(--ink); font-weight: 700; }
.tc-root { border-bottom: 2px solid var(--accent); padding-bottom: 1px; }
.tc-path { color: var(--ink-mute); font-weight: 400; }
.tc-verdict { margin: .7rem 0 0; font-size: .86rem; color: var(--flame-ink); }
.tc-verdict .url-flag { margin-right: .4rem; font-size: .7em; vertical-align: 1px; }
/* The cost, called out: a solid danger rule down the left and a red wash
   that fades as it goes — the price of the click is the one thing the eye
   is pulled to, without shouting. */
.tc-cost {
  margin-top: .9rem; padding: .85rem 1rem;
  border-left: 3px solid var(--danger);
  background: linear-gradient(90deg, rgba(255, 90, 70, .15), transparent 75%);
}
.threat-card .tc-cost .threat-label-cost { margin-top: 0; }
.threat-card .threat-label {
  margin: 0 0 .3rem;
  font-family: var(--mono); font-size: .72rem;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-mute);
}
.threat-card .threat-say {
  margin: 0; color: var(--ink); font-weight: 560;
  font-size: 1.02rem; line-height: 1.45; text-wrap: pretty;
}
/* The dissected address sits between hairlines, like the evidence tray
   it is; the segments' own tag padding gives it air. */
.threat-card .dissect {
  margin-top: 1rem;
  border-block: 1px solid var(--card-line);
}
.threat-card .url { font-size: .86rem; }
/* The evidence breaks in the same place in every card — decoy line, then
   the domain that answers — instead of wherever each width happens to
   snap. A block spacer inside the inline flow is the whole trick. */
.threat-card .url-brk { display: block; height: 0; }
.threat-card .threat-label-cost { margin-top: 1rem; color: var(--danger); }
.threat-card .threat-cost { margin: 0; color: var(--ink-soft); font-size: .95rem; text-wrap: pretty; }
/* The one-sentence bridge under the cards: the framing law, verbatim
   shape — the attack arrives as a link; TinyRocket reads the link first. */
.threat-bridge {
  margin: 2rem 0 0; max-width: 62ch;
  color: var(--ink-soft); font-size: 1.02rem; text-wrap: pretty;
}

/* ------------------------------------------------- the flow, performed -- */

/* The journey of one click: five stations on a rail, a link-token passing
   through, checks stamping as it goes, the verdict forking to "opens
   quietly" or "held for review — you decide", and the browser only after.

   THE RESTING STATE IS THE FINISHED FRAME. Every rule below describes the
   completed journey; .is-play (added once by IntersectionObserver) runs
   the performance, and every keyframe only supplies from-states. No JS,
   no IO, or reduced motion all land here: journey done, facts visible. */
.flow { margin-top: 1.25rem; padding: clamp(1.25rem, 3vw, 2rem) clamp(1rem, 2.5vw, 1.75rem); }
.flow-rail { display: flex; align-items: center; gap: .5rem; }
.flow-st {
  position: relative; flex: 0 1 auto;
  display: grid; justify-items: center; gap: .5rem;
  text-align: center; padding-top: 1.9rem;
}
/* The travelling link-token: one small piece of evidence, shown at the
   station it is passing. At rest it sits at the last station only. */
.flow-pill {
  position: absolute; top: 0; inset-inline-start: 50%; translate: -50% 0;
  font-family: var(--mono); font-size: .68rem; white-space: nowrap;
  border: 1px solid var(--card-line); border-radius: 980px;
  background: var(--bg); color: var(--ink-mute); padding: .08rem .5rem;
  opacity: 0;
}
[dir='rtl'] .flow-pill { translate: 50% 0; }
.flow-pill-last { opacity: 1; color: var(--ink-soft); }
.flow-ico {
  width: 52px; height: 52px; border-radius: 50%;
  border: 1px solid var(--card-line); background: var(--bg);
  box-shadow: var(--card-shadow);
  display: grid; place-items: center;
}
.flow-ico svg { width: 24px; height: 24px; color: var(--ink-soft); }
.flow-ico-rocket { border-color: color-mix(in srgb, var(--accent) 45%, var(--card-line)); }
.flow-cap {
  font-size: .78rem; font-weight: 600; color: var(--ink-soft);
  max-width: 15ch; line-height: 1.35;
}
/* The two working stations are mini-panels in the app's own idiom. Their
   padding override collapses the station's pill lane, so the travelling
   token floats ABOVE the panel instead of striking its top border. */
.flow-box .flow-pill { top: -1.55rem; }
.flow-box {
  border: 1px solid var(--card-line); border-radius: 12px;
  background: var(--bg); box-shadow: var(--card-shadow);
  padding: .85rem 1rem 1rem; justify-items: start; text-align: start;
}
.flow-box .flow-cap {
  max-width: none; font-family: var(--mono); font-weight: 500;
  font-size: .68rem; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-mute);
}
.flow-checks ul { list-style: none; margin: .45rem 0 0; padding: 0; display: grid; gap: .4rem; }
.flow-checks li { display: flex; gap: .5rem; align-items: baseline; font-size: .85rem; color: var(--ink-soft); }
.flow-tick { color: var(--accent); font-weight: 700; font-size: .9em; }
.flow-chip {
  display: inline-flex; align-items: center; gap: .45rem; margin-top: .55rem;
  font-family: var(--mono); font-size: .72rem; font-weight: 600; letter-spacing: .02em;
  border: 1px solid var(--card-line); border-radius: 980px;
  background: var(--bg-raised); color: var(--ink); padding: .22rem .7rem;
  white-space: nowrap;
}
.flow-dot { width: .5rem; height: .5rem; border-radius: 50%; flex: none; }
.flow-chip-ok .flow-dot { background: var(--accent); }
.flow-chip-hold .flow-dot { background: var(--flame-ink); }
/* The held branch ends with a person, because that is the product. */
.flow-you { margin-top: .55rem; font-size: .8rem; line-height: 1.45; color: var(--ink-soft); max-width: 26ch; }
.flow-you b { color: var(--ink); font-weight: 650; }
/* The rail segments: live trajectory, drawn in the brand's stroke. */
.flow-seg {
  flex: 1 0 20px; height: 2px; border-radius: 1px; align-self: center;
  background: linear-gradient(90deg, var(--line-live-a), var(--line-live-b));
  transform-origin: left center;
}
[dir='rtl'] .flow-seg { transform-origin: right center; }

/* The performance. Order on the rail: st seg st seg st seg st seg st. */
@keyframes flow-lite { from { opacity: .2; } }
@keyframes flow-drawx { from { scale: 0 1; } }
@keyframes flow-drawy { from { scale: 1 0; } }
@keyframes flow-pop  { from { opacity: 0; translate: 0 5px; } }
@keyframes flow-pass { 0% { opacity: 0; } 12% { opacity: 1; } 86% { opacity: 1; } 100% { opacity: 0; } }
@keyframes flow-stay { from { opacity: 0; } }
.flow.is-play .flow-st { animation: flow-lite .45s ease both; }
.flow.is-play .flow-st:nth-child(1) { animation-delay: 0s; }
.flow.is-play .flow-st:nth-child(3) { animation-delay: .8s; }
.flow.is-play .flow-st:nth-child(5) { animation-delay: 1.7s; }
.flow.is-play .flow-st:nth-child(7) { animation-delay: 3.4s; }
.flow.is-play .flow-st:nth-child(9) { animation-delay: 4.5s; }
.flow.is-play .flow-seg { animation: flow-drawx .35s ease both; }
.flow.is-play .fs1 { animation-delay: .5s; }
.flow.is-play .fs2 { animation-delay: 1.4s; }
.flow.is-play .fs3 { animation-delay: 3.15s; }
.flow.is-play .fs4 { animation-delay: 4.3s; }
.flow.is-play .flow-pill { animation: flow-pass linear both; }
.flow.is-play .flow-st:nth-child(1) .flow-pill { animation-duration: .9s; animation-delay: 0s; }
.flow.is-play .flow-st:nth-child(3) .flow-pill { animation-duration: 1.1s; animation-delay: .9s; }
.flow.is-play .flow-st:nth-child(5) .flow-pill { animation-duration: 1.4s; animation-delay: 2s; }
.flow.is-play .flow-st:nth-child(7) .flow-pill { animation-duration: 1.1s; animation-delay: 3.4s; }
.flow.is-play .flow-st:nth-child(9) .flow-pill { animation: flow-stay .3s ease 4.6s both; }
.flow.is-play .fc1 { animation: flow-pop .3s ease 2.1s both; }
.flow.is-play .fc2 { animation: flow-pop .3s ease 2.5s both; }
.flow.is-play .fc3 { animation: flow-pop .3s ease 2.9s both; }
.flow.is-play .flow-chip-ok { animation: flow-pop .3s ease 3.7s both; }
.flow.is-play .flow-chip-hold { animation: flow-pop .3s ease 3.9s both; }
.flow.is-play .flow-you { animation: flow-pop .35s ease 4.15s both; }

/* On the phone the rail turns vertical and the segments draw downward. */
@media (max-width: 899.98px) {
  .flow-rail { flex-direction: column; align-items: stretch; gap: .6rem; }
  .flow-st { justify-items: center; }
  .flow-box { justify-items: start; width: 100%; }
  .flow-seg {
    flex: none; width: 2px; height: 26px; align-self: center;
    background: linear-gradient(180deg, var(--line-live-a), var(--line-live-b));
    transform-origin: center top;
  }
  .flow.is-play .flow-seg { animation-name: flow-drawy; }
}

/* ------------------------------------------------------- the art bands -- */

/* The closing "get notified" band lifts off: art-launchpad in both themes,
   as a lazy <img> under a token veil. The 2px flame rule the band already
   wears is the ignition — the one moment the flame law licenses. */
.cta-band-art { position: relative; overflow: hidden; }
.cta-band-art .band-art {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%; object-fit: cover;
  /* Anchored to the ROCKET, not the picture's center: at 71% the liftoff
     stays in frame at every viewport width — cover crops toward the anchor
     as the band narrows, so the rocket never leaves the stage. */
  object-position: 71% 12%;
}
/* The band scrim is RETIRED (owner: "not a fan of this layout" — the
   half-poured wash read as a seam). The artwork runs vivid edge to edge;
   the copy rides a dark glass panel instead, the same iOS material as the
   threat cards. The element stays in the markup as a no-op. */
.cta-band-art .band-scrim { display: none; }
.band-panel {
  max-width: 34rem; padding: 2rem 2.25rem; border-radius: 18px;
  background: rgba(10, 16, 32, .55);
  -webkit-backdrop-filter: blur(12px) saturate(1.4);
  backdrop-filter: blur(12px) saturate(1.4);
  border: 1px solid rgba(190, 205, 240, .22);
  box-shadow: 0 18px 40px -18px rgba(2, 5, 14, .6);
  --bg: #0a0f1e; --bg-raised: #131a30; --ink: #f2f6ff; --ink-soft: #d5deef;
  --ink-mute: #a8b4cf; --line: rgba(180,200,240,.16); --field-line: rgba(180,200,240,.3);
  color: var(--ink); color-scheme: dark;
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .band-panel { background: rgba(10, 16, 32, .9); }
}
.cta-band-art .wrap { position: relative; z-index: 2; }
/* The band's copy sits on the scrimmed side, not across the rocket. */
.cta-band-art .wrap { text-align: start; }
.cta-band-art .h2 { max-width: 24ch; }
.cta-band-art .lede { margin-inline: 0; max-width: 44ch; }
.cta-band-art .btn-row { justify-content: flex-start; }
/* The flame rule must stay above the picture. */
.cta-band-art::before { z-index: 2; }


/* THE THREAT BAND. The page darkens when it talks about what is out there —
   the space-battle artwork (the attacks incoming, the rocket unharmed) as a
   full-bleed ground in BOTH themes. Owner-directed: the art stays VISIBLE —
   a light veil only, just enough for the heading to read — and the attack
   cards ride on frosted white glass so the evidence is lit while the war
   goes on behind it. Lazy by position (below the fold). */
.threat-band {
  position: relative; isolation: isolate;
  background-color: #0a0f1e;
  background-image: linear-gradient(rgba(7,12,26,.42), rgba(7,12,26,.5)), url('/img/art-threat-space-1280w.jpg');
  background-size: cover; background-position: center 30%;
  --bg: #0a0f1e; --bg-raised: #111731; --ink: #f2f6ff; --ink-soft: #d5deef;
  --ink-mute: #a8b4cf; --line: rgba(180,200,240,.16); --card-line: rgba(180,200,240,.2);
  --danger: #ff7a6e; --flame-ink: #ffa14f; --accent: #5ca8ff;
  --url-decoy: #c3cde4;
  color: var(--ink); color-scheme: dark;
}
/* The heading rides the art bare — a text shadow alone carries the
   contrast, owner's call: no pool, no box. */
.threat-band .threat-head { text-shadow: 0 1px 18px rgba(4, 8, 20, .8), 0 1px 4px rgba(4, 8, 20, .55); }
@media (max-width: 640px) {
  .threat-band { background-image: linear-gradient(rgba(7,12,26,.42), rgba(7,12,26,.5)), url('/img/art-threat-space-820w.jpg'); }
}
@media (min-width: 1500px) {
  .threat-band { background-image: linear-gradient(rgba(7,12,26,.42), rgba(7,12,26,.5)), url('/img/art-threat-space-2000w.jpg'); }
}
/* The war stays watchable: content holds the left column and the right
   third of the band is open sky — the UFO, the beams, the planet, uncovered.
   Wide screens only; below 1100px the art returns to backdrop duty. */
@media (min-width: 1100px) {
  .threat-band { background-position: 74% 30%; padding-block: 5.5rem 6.5rem; }
  .threat-band .threat-head { max-width: 54rem; }
  .threat-band .threat-grid, .threat-band .threat-bridge { max-width: 54rem; }
}
/* Dark glass evidence cards, the iOS material: translucent, heavily
   blurred, so the war stays visible THROUGH the evidence — the card is a
   window wearing a report, not a wall. The band's dark palette carries the
   text; the dissection colors are restated at band scope. */
.threat-band .threat-bridge { text-shadow: 0 1px 14px rgba(4, 8, 20, .6); }
.threat-band .card {
  background: rgba(10, 16, 32, .52);
  -webkit-backdrop-filter: blur(12px) saturate(1.4);
  backdrop-filter: blur(12px) saturate(1.4);
  border-color: rgba(190, 205, 240, .22);
  box-shadow: 0 18px 40px -18px rgba(2, 5, 14, .6);
  transition: background-color .25s ease, border-color .25s ease, transform .25s ease;
}
/* Pointing at the evidence brings it forward: the glass darkens so the
   text sharpens, and the card lifts a breath. Reduced motion stills the
   lift via the global kill; the darkening is instant and harmless. */
.threat-band .card:hover {
  background: rgba(7, 11, 23, .74);
  border-color: rgba(190, 205, 240, .34);
  transform: translateY(-3px);
}
/* No blur support = no window: the card goes near-solid so text never sits
   on raw artwork. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .threat-band .card { background: rgba(10, 16, 32, .9); }
}
