/* ==========================================================================
   Tical design system
   Derived from the Tical.Backend landing page, rebuilt with semantic tokens,
   an elevation scale, and a real motion layer.
   ========================================================================== */

/* Every face this site uses is served from our own origin. That is a privacy decision
   before it is a performance one: a Google Fonts <link> hands the visitor's IP address
   to a third party on every page load, which the privacy policy would then have to
   disclose. Nothing here is fetched from anyone else.

   Both families are variable fonts, so one file covers the whole weight range and the
   `font-weight: A B` descriptor tells the browser so — four files, not sixteen. Only the
   latin and latin-ext subsets are shipped; latin-ext is not optional, because the legal
   pages carry Polish diacritics. */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
    U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
    U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url('/fonts/jetbrains-mono-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
    U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url('/fonts/jetbrains-mono-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
    U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* The app's wordmark face, self-hosted from src/Tical/Assets. Used in exactly one
   place — the app mockup's title bar — so it loads lazily via font-display:swap. */
@font-face {
  font-family: 'Saira Stencil One';
  src: url('/fonts/SairaStencilOne-Regular.ttf') format('truetype');
  font-weight: 400;
  font-display: swap;
}

:root {
  color-scheme: dark;

  /* --- palette ------------------------------------------------------- */
  --lime-300: #bef264;
  --lime-400: #a3e635;
  --lime-500: #84cc16;
  --n-950: #0a0a0a;
  --n-900: #171717;
  --n-850: #1f1f1f;
  --n-800: #262626;
  --n-700: #404040;

  /* --- semantic ------------------------------------------------------- */
  --bg: var(--n-950);
  --surface: var(--n-900);
  --surface-2: var(--n-850);
  --border: var(--n-800);
  --border-strong: var(--n-700);
  --accent: var(--lime-400);
  --accent-hover: var(--lime-300);
  --accent-deep: var(--lime-500);
  --text: #f8fafc;
  --text-dim: #94a3b8;
  /* Was #64748b, which is 4.16:1 on --bg and 3.77:1 on --surface — under AA for the .hint,
     .stat-label, thead and placeholder text this colour exists for, all of which are set below
     16px. This value is not a new one: it is --a-t2 from the app-mockup scale below, so the two
     palettes still name the same grey. 5.42:1 on --bg, 4.90:1 on --surface. */
  --text-faint: #7b8794;
  --danger: #ff6b6b;
  --warn: #fbbf24;
  --ok: var(--lime-400);

  /* --- elevation ------------------------------------------------------ */
  /* The source design had none — depth came only from 1px borders. */
  --sh-1: 0 1px 2px rgb(0 0 0 / .4);
  --sh-2: 0 8px 24px -8px rgb(0 0 0 / .6);
  --sh-3: 0 24px 64px -16px rgb(0 0 0 / .8);
  --sh-glow: 0 0 0 1px rgb(163 230 53 / .25), 0 8px 32px -8px rgb(163 230 53 / .25);

  /* --- motion --------------------------------------------------------- */
  --e-out: cubic-bezier(.16, 1, .3, 1);        /* expo-out: the signature easing */
  --e-spring: cubic-bezier(.34, 1.56, .64, 1); /* overshoot for badges/toasts */
  --e-in-out: cubic-bezier(.65, 0, .35, 1);
  --t-fast: .18s;
  --t-base: .32s;
  --t-slow: .6s;

  /* --- shape / type --------------------------------------------------- */
  --r-sm: .5rem;
  --r-md: 1rem;
  --r-lg: 1.5rem;
  --r-pill: 999px;
  --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, 'Cascadia Code', Consolas, monospace;
  --header-h: 4.5rem;
}

/* ==========================================================================
   Reset & base
   ========================================================================== */

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

html {
  scroll-behavior: smooth;
  /* Anchor targets clear the sticky header instead of hiding under it. */
  scroll-padding-top: calc(var(--header-h) + 1rem);
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background-color: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 1rem;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

h1, h2, h3, h4 {
  font-family: var(--font-mono);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -.02em;
  margin: 0 0 1rem;
}

h1 { font-size: clamp(2.5rem, 7vw, 4.5rem); }
h2 { font-size: clamp(1.75rem, 4vw, 2.5rem); }
h3 { font-size: 1.25rem; }

p { margin: 0 0 1rem; }
a { color: var(--accent); text-decoration: none; }

code, kbd, samp, pre, .mono { font-family: var(--font-mono); }

::selection { background: rgb(163 230 53 / .3); color: var(--text); }

hr { border: 0; border-top: 1px solid var(--border); margin: 2rem 0; }

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

/* Every interactive element gets a visible keyboard ring. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}
:focus:not(:focus-visible) { outline: none; }

.container {
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 1.5rem;
}
.container-narrow { max-width: 640px; }

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

[hidden] { display: none !important; }

/* Skip link — the source design had none. */
.skip-link {
  position: absolute; top: -100%; left: 1rem; z-index: 2000;
  background: var(--accent); color: var(--n-950);
  padding: .75rem 1.25rem; border-radius: 0 0 var(--r-sm) var(--r-sm);
  font-weight: 700;
  transition: top var(--t-base) var(--e-out);
}
.skip-link:focus { top: 0; }

/* ==========================================================================
   Ambient background — aurora blobs + drifting grid
   ========================================================================== */

.bg-fx {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
}

/* Faint technical grid, masked to fade toward the edges. */
.bg-grid {
  position: absolute;
  inset: -50%;
  background-image:
    linear-gradient(to right, rgb(255 255 255 / .028) 1px, transparent 1px),
    linear-gradient(to bottom, rgb(255 255 255 / .028) 1px, transparent 1px);
  background-size: 64px 64px;
  mask-image: radial-gradient(ellipse 70% 60% at 50% 30%, #000 20%, transparent 75%);
  -webkit-mask-image: radial-gradient(ellipse 70% 60% at 50% 30%, #000 20%, transparent 75%);
  animation: grid-drift 60s linear infinite;
}

@keyframes grid-drift {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(64px, 64px, 0); }
}

.aurora {
  position: absolute;
  border-radius: 50%;
  filter: blur(90px);
  opacity: .5;
  will-change: transform;
}
.aurora-1 {
  width: 46vw; height: 46vw; min-width: 320px; min-height: 320px;
  top: -12vw; left: -6vw;
  background: radial-gradient(circle, rgb(163 230 53 / .38), transparent 68%);
  animation: drift-a 20s var(--e-in-out) infinite alternate;
}
.aurora-2 {
  width: 40vw; height: 40vw; min-width: 280px; min-height: 280px;
  top: 8vw; right: -10vw;
  background: radial-gradient(circle, rgb(16 185 129 / .28), transparent 68%);
  animation: drift-b 28s var(--e-in-out) infinite alternate;
}
.aurora-3 {
  width: 34vw; height: 34vw; min-width: 240px; min-height: 240px;
  top: 62vh; left: 28vw;
  background: radial-gradient(circle, rgb(132 204 22 / .18), transparent 70%);
  animation: drift-a 34s var(--e-in-out) infinite alternate-reverse;
}

/* Translate only, deliberately — these used to animate scale() as well.
   `.aurora` carries filter: blur(90px), and a filtered element that changes
   scale cannot reuse its cached blurred texture: the browser re-rasterises and
   re-blurs ~0.78 megapixels per blob, per frame, forever, on every page. Pure
   translation lets the compositor blur once and then just move the finished
   layer. The blobs still drift; they no longer breathe in and out.

   If the breathing is wanted back, restore the scale() here and accept the
   per-frame re-blur — but raise the translate distances instead first, which
   buys back the sense of movement for free. */
@keyframes drift-a {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(8vw, 6vh, 0); }
}
@keyframes drift-b {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-7vw, 9vh, 0); }
}

/* ==========================================================================
   Header
   ========================================================================== */

.site-header {
  position: sticky;
  top: 0;
  z-index: 1000;
  background-color: rgb(10 10 10 / .72);
  backdrop-filter: blur(14px) saturate(160%);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  border-bottom: 1px solid var(--border);
  /* Explicit properties, not `transition: all` — the source transitioned
     everything, which forces needless repaints on a blurred sticky element. */
  transition: padding var(--t-base) var(--e-out),
              background-color var(--t-base) var(--e-out),
              box-shadow var(--t-base) var(--e-out);
  padding: 1.15rem 0;
}
.site-header.scrolled {
  padding: .6rem 0;
  background-color: rgb(10 10 10 / .92);
  box-shadow: var(--sh-2);
}

/* Reading-progress hairline pinned to the header's bottom edge.
   Driven by scaleX rather than width: this element is a child of a sticky
   backdrop-filter header, and `width` is a layout property — writing it every
   scroll frame relaid out the header and dirtied its backdrop snapshot. A
   transform is composited, so the bar moves without layout or paint. The
   `transition` that used to sit here is gone with it: motion.js writes this
   once per animation frame already, so the transition only kept layout
   animating for a further 100ms after each write. */
.scroll-progress {
  position: absolute;
  left: 0; bottom: -1px;
  height: 2px;
  width: 100%;
  transform: scaleX(0);
  transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--accent-deep), var(--accent), var(--accent-hover));
  box-shadow: 0 0 10px rgb(163 230 53 / .7);
}

.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
}

.logo {
  display: inline-flex;
  align-items: center;
  gap: .7rem;
  font-family: var(--font-mono);
  font-size: 1.2rem;
  font-weight: 800;
  color: var(--accent);
  letter-spacing: -.03em;
  transition: transform var(--t-fast) var(--e-out);
}
.logo:hover { transform: translateX(2px); }
/* The brand mark itself — Tical's app icon. */
.logo-mark {
  width: 30px; height: 30px;
  flex: 0 0 auto;
  display: block;
  border-radius: 50%;
  box-shadow: 0 4px 14px -6px rgb(0 0 0 / .8);
  transition: transform var(--t-base) var(--e-spring);
}
.logo:hover .logo-mark { transform: scale(1.08); }

.site-nav ul {
  display: flex;
  align-items: center;
  gap: 2rem;
  list-style: none;
  margin: 0;
  padding: 0;
}
.site-nav a {
  position: relative;
  color: var(--text-dim);
  font-size: .9rem;
  font-weight: 500;
  padding: .25rem 0;
  transition: color var(--t-fast) var(--e-out);
}
/* Underline wipes out from the centre rather than just recolouring. */
.site-nav a::after {
  content: "";
  position: absolute;
  left: 50%; bottom: 0;
  width: 0; height: 1.5px;
  background: var(--accent);
  transform: translateX(-50%);
  transition: width var(--t-base) var(--e-out);
}
.site-nav a:hover { color: var(--text); }
.site-nav a:hover::after { width: 100%; }

/* The current page. Every page has always set aria-current on its own nav item; nothing ever
   drew it. Reuse the hover underline held open rather than inventing a second treatment, so
   "you are here" and "you are pointing at this" read as one idea in two states. */
.site-nav a[aria-current="page"] { color: var(--text); }
.site-nav a[aria-current="page"]::after { width: 100%; }

/* The waitlist CTA, carried inside the drawer. Hidden here and shown only inside the 860px
   block below, which is the mirror of .header-cta: above the breakpoint the button lives in the
   header and this is off, below it the header's is off and this is the one. Exactly one of the
   two is ever on screen. */
.nav-cta { display: none; }

/* Hamburger — bars morph into an X. */
.nav-toggle {
  display: none;
  width: 42px; height: 42px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  cursor: pointer;
  padding: 0;
  position: relative;
  transition: border-color var(--t-fast) var(--e-out);
}
.nav-toggle:hover { border-color: var(--border-strong); }
.nav-toggle span {
  position: absolute;
  left: 50%; top: 50%;
  width: 18px; height: 2px;
  margin-left: -9px;
  background: var(--text);
  border-radius: 2px;
  transition: transform var(--t-base) var(--e-out), opacity var(--t-fast) linear;
}
.nav-toggle span:nth-child(1) { transform: translateY(-6px); }
.nav-toggle span:nth-child(2) { transform: translateY(0); }
.nav-toggle span:nth-child(3) { transform: translateY(6px); }
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; transform: scaleX(0); }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: rotate(-45deg); }

/* ==========================================================================
   Buttons
   ========================================================================== */

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  padding: .8rem 1.75rem;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  font-family: var(--font-sans);
  font-size: .95rem;
  font-weight: 600;
  line-height: 1.2;
  cursor: pointer;
  overflow: hidden;
  white-space: nowrap;
  transition: background-color var(--t-fast) var(--e-out),
              border-color var(--t-fast) var(--e-out),
              color var(--t-fast) var(--e-out),
              box-shadow var(--t-base) var(--e-out),
              transform var(--t-base) var(--e-out);
}

/* Shine sweep — a skewed highlight crossing the face on hover. */
.btn::after {
  content: "";
  position: absolute;
  top: -50%; left: -80%;
  width: 45%; height: 200%;
  background: linear-gradient(90deg, transparent, rgb(255 255 255 / .35), transparent);
  transform: skewX(-22deg);
  transition: left var(--t-slow) var(--e-out);
  pointer-events: none;
}
.btn:hover::after { left: 130%; }

.btn-primary {
  background-color: var(--accent);
  color: var(--n-950);
  box-shadow: 0 4px 16px -6px rgb(163 230 53 / .7);
}
.btn-primary:hover {
  background-color: var(--accent-hover);
  box-shadow: 0 10px 28px -8px rgb(163 230 53 / .8);
}

.btn-secondary {
  background-color: rgb(255 255 255 / .03);
  color: var(--text);
  border-color: var(--border);
}
.btn-secondary:hover {
  background-color: var(--surface-2);
  border-color: var(--border-strong);
}
.btn-secondary::after { background: linear-gradient(90deg, transparent, rgb(255 255 255 / .1), transparent); }

.btn-ghost {
  background: transparent;
  color: var(--text-dim);
  border-color: transparent;
  padding: .5rem .9rem;
}
.btn-ghost:hover { color: var(--text); background: rgb(255 255 255 / .05); }

.btn-danger { background-color: rgb(255 107 107 / .12); color: var(--danger); border-color: rgb(255 107 107 / .35); }
.btn-danger:hover { background-color: rgb(255 107 107 / .2); border-color: var(--danger); }

.btn-sm { padding: .5rem 1rem; font-size: .82rem; }
.btn-block { width: 100%; }

.btn:disabled, .btn[aria-disabled="true"] {
  opacity: .45;
  cursor: not-allowed;
  transform: none !important;
  box-shadow: none;
}
.btn:disabled::after { display: none; }

/* Spinner shown while a button is in its busy state. */
.btn.is-busy { color: transparent !important; pointer-events: none; }
.btn.is-busy::before {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  width: 16px; height: 16px;
  margin: -8px 0 0 -8px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  color: var(--n-950);
  animation: spin .6s linear infinite;
}
.btn-secondary.is-busy::before, .btn-ghost.is-busy::before { color: var(--text); }

@keyframes spin { to { transform: rotate(360deg); } }

/* ==========================================================================
   Surfaces & cards
   ========================================================================== */

.card {
  position: relative;
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 2rem;
  overflow: hidden;
  transition: transform var(--t-base) var(--e-out),
              border-color var(--t-base) var(--e-out),
              box-shadow var(--t-base) var(--e-out);
}

/* Cursor-tracking spotlight. `--mx`/`--my` are written by motion.js. */
.card::before {
  content: "";
  position: absolute;
  inset: 0;
  /* The card's own `overflow: hidden` normally keeps this inside the rounded corners.
     Cards that opt out of clipping (.pricing-card) still need the gradient to follow
     the radius, so it clips itself. */
  border-radius: inherit;
  background: radial-gradient(400px circle at var(--mx, 50%) var(--my, 50%),
              rgb(163 230 53 / .09), transparent 65%);
  opacity: 0;
  transition: opacity var(--t-base) var(--e-out);
  pointer-events: none;
}
.card:hover::before { opacity: 1; }

.card-hover:hover {
  transform: translateY(-4px);
  border-color: var(--border-strong);
  box-shadow: var(--sh-2);
}

.card > * { position: relative; z-index: 1; }

.panel {
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 1.5rem;
}

.inset {
  background-color: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 1rem;
}

/* ==========================================================================
   Scroll reveal
   ========================================================================== */

.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity var(--t-slow) var(--e-out), transform var(--t-slow) var(--e-out);
  transition-delay: calc(var(--i, 0) * 70ms);
}
.reveal.in-view { opacity: 1; transform: none; }

.reveal-scale { transform: translateY(16px) scale(.97); }
.reveal-left  { transform: translateX(-28px); }
.reveal-right { transform: translateX(28px); }

/* ==========================================================================
   Hero
   ========================================================================== */

.hero {
  position: relative;
  padding: clamp(3.5rem, 10vh, 7rem) 0 clamp(3rem, 8vh, 5rem);
}

/* Copy on the left, the live clock on the right. Stacks and re-centres below 860px. */
.hero-grid {
  display: grid;
  grid-template-columns: 1.1fr .9fr;
  align-items: center;
  gap: clamp(2rem, 5vw, 4.5rem);
}
.hero-copy .hero-sub,
.hero-copy .hero-offer { margin-left: 0; margin-right: 0; }

.hero-visual {
  display: grid;
  justify-items: center;
  gap: 1.5rem;
  animation: fade-up 1s var(--e-out) .35s backwards;
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  padding: .4rem 1rem;
  margin-bottom: 1.75rem;
  border: 1px solid rgb(163 230 53 / .3);
  border-radius: var(--r-pill);
  background: rgb(163 230 53 / .07);
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.eyebrow .dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--accent);
  animation: pulse-dot 2s var(--e-in-out) infinite;
}
@keyframes pulse-dot {
  0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgb(163 230 53 / .6); }
  50%      { opacity: .6; box-shadow: 0 0 0 6px rgb(163 230 53 / 0); }
}

/* Two hand-set lines. The longest is "One to rule" (11 chars); the size below is picked so it
   always fits .hero-copy, otherwise the browser re-wraps and we get orphan words. */
.hero h1 .line { display: block; }

/* The global h1 clamp is 7vw, which overflows the 1.1fr column between 860px and ~1160px. */
.hero h1 { font-size: clamp(2rem, 6.1vw, 4.5rem); }

/* Word-by-word headline entrance. Spans are emitted by the markup. */
.hero h1 .word {
  display: inline-block;
  opacity: 0;
  transform: translateY(1.1em) rotate(3deg);
  animation: word-rise .9s var(--e-out) forwards;
  animation-delay: calc(var(--i) * 80ms);
}
@keyframes word-rise {
  to { opacity: 1; transform: none; }
}

.hero .accent {
  background: linear-gradient(115deg, var(--accent-hover), var(--accent), var(--accent-deep));
  background-size: 200% auto;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: word-rise .9s var(--e-out) forwards, shimmer 6s linear infinite 1s;
  animation-delay: calc(var(--i) * 80ms), 1s;
}
@keyframes shimmer {
  to { background-position: 200% center; }
}

/* The bottom margin was 2.5rem when this paragraph was the last thing before the buttons. There
   are three blocks between it and the form now, so what it owns is a paragraph break; the space
   before the form belongs to the form. */
.hero-sub {
  max-width: 34rem;
  margin: 0 auto .9rem;
  font-size: clamp(1rem, 2.2vw, 1.2rem);
  color: var(--text-dim);
  animation: fade-up .8s var(--e-out) .5s backwards;
}

/* Everything below is one cascade, .5s to .9s, in reading order: claim, who it is for, the offer,
   the field, the fine print. The delays are hand-set rather than staggered by an index because
   these are five different elements, not a list.

   `backwards`, not `forwards`, and no `opacity: 0` in any of these base rules. The backwards fill
   supplies the hidden state for the length of the delay, and the state each element rests on
   afterwards is simply its own rule - visually identical, and it fails the right way round: an
   animation that gets cancelled leaves its element visible instead of invisible for good. That
   is not hypothetical. Browser extensions cancel animations on elements they decorate, and this
   hero used to need a fill to be readable at all. */
.hero-sub2 {
  font-size: clamp(.95rem, 2vw, 1.05rem);
  animation-delay: .58s;
}

/* The offer itself, and the one line in the hero that is allowed to shout. Accent on the figures
   only — the rest of the sentence is ordinary text so the prices are what the eye lands on. */
.hero-offer {
  max-width: 34rem;
  margin: 0 auto 1.4rem;
  font-size: .95rem;
  color: var(--text);
  animation: fade-up .8s var(--e-out) .66s backwards;
}
.hero-offer strong { color: var(--accent); }

/* The animation is on the wrapper and never on the form itself. A password manager that
   recognises the field writes `animation: none !important` onto the <form>, then removes it
   again, and that cancel-and-recreate replayed this fade from zero roughly every two seconds:
   the whole block went invisible for the 740ms delay, over and over, while somebody was typing
   into it. Nothing writes inline styles on a plain div, so here the animation stays put. Do not
   move it back onto .waitlist. */
.hero-copy .hero-form {
  max-width: 34rem;
  animation: fade-up .8s var(--e-out) .74s backwards;
}
.hero-copy .waitlist { margin-top: 0; }

.hero-note {
  margin-top: 1.25rem;
  font-size: .8rem;
  color: var(--text-faint);
  animation: fade-up .8s var(--e-out) .9s backwards;
}

@keyframes fade-up {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: none; }
}

/* ==========================================================================
   Sections & grids
   ========================================================================== */

.section { padding: clamp(3.5rem, 9vh, 6rem) 0; }
.section-head { text-align: center; max-width: 40rem; margin: 0 auto 3rem; }
.section-head p { color: var(--text-dim); margin: 0; }
.section-label {
  display: block;
  font-family: var(--font-mono);
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: .75rem;
}

.grid { display: grid; gap: 1.5rem; }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }

/* Product screenshots. Real captures of the Windows app, 602x975 each, so the tiles are
   portrait — minmax is tuned narrower than .grid-3 to keep three of them on one row. */
.shots { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }

.shot { margin: 0; }

.shot img {
  display: block;
  width: 100%;
  /* The base reset caps img width but says nothing about height. The markup carries real
     width/height attributes (so the layout reserves the right box before the image lands and
     nothing shifts), and without height:auto those attributes win and squash the capture. */
  height: auto;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}

.shot figcaption {
  margin-top: .85rem;
  font-size: .85rem;
  line-height: 1.5;
  color: var(--text-dim);
}

/* Compact mode is a 602x102 strip — nothing like the portrait captures, so it sits on its own
   row below the grid rather than being stretched to a column. Capped at its native width:
   blown up to the full container it would be a 2x upscale of a 602px source and look soft. */
.shot-wide {
  max-width: 602px;
  margin: 2.5rem auto 0;
}

.shot-wide figcaption { text-align: center; }

/* The demo slot under the hero. Deliberately sized as the 16/9 box a screen recording will
   occupy, with a portrait screenshot letterboxed inside it until that recording exists — so
   dropping the video in moves nothing on the page. contain, not cover: cropping a 602-wide
   capture to 16/9 would cut away the part of the window the caption is pointing at. The surface
   fill is what makes the letterboxing read as a frame rather than as a mistake. */
.demo {
  margin: 0 auto;
  max-width: 880px;
}

.demo > img,
.demo > video {
  display: block;
  width: 100%;
  /* height:auto is not optional here, and not the same thing as leaving it out. The markup carries
     real width/height attributes so the box is reserved before the file lands, and those
     attributes are a presentational hint that beats aspect-ratio — leave this off and the frame
     is 975px tall instead of 16/9. Same trap as .shot img above. */
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: contain;
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}

.demo figcaption {
  margin-top: .85rem;
  text-align: center;
  font-size: .85rem;
  line-height: 1.5;
  color: var(--text-dim);
}

.feature-icon {
  width: 44px; height: 44px;
  display: grid; place-items: center;
  margin-bottom: 1.25rem;
  border-radius: 12px;
  background: rgb(163 230 53 / .1);
  border: 1px solid rgb(163 230 53 / .22);
  color: var(--accent);
  transition: transform var(--t-base) var(--e-spring),
              background-color var(--t-base) var(--e-out);
}
.card-hover:hover .feature-icon {
  transform: scale(1.1) rotate(-6deg);
  background: rgb(163 230 53 / .18);
}
.feature-card h3 { color: var(--text); margin-bottom: .5rem; font-size: 1.05rem; }
.feature-card p { color: var(--text-dim); font-size: .92rem; margin: 0; }

/* ==========================================================================
   Pricing
   ========================================================================== */

.pricing-grid {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 1.5rem;
}
.pricing-card {
  flex: 1 1 300px;
  max-width: 360px;
  display: flex;
  flex-direction: column;
  padding: 2.5rem 2rem;
  /* The RECOMMENDED badge straddles the top border on purpose, so this surface must not
     clip the way every other .card does — `overflow: hidden` there cut it in half. */
  overflow: visible;
}
.pricing-card.featured {
  border-color: rgb(163 230 53 / .5);
  box-shadow: var(--sh-glow);
}
.pricing-card.featured::after {
  content: "RECOMMENDED";
  position: absolute;
  top: 0; left: 50%;
  transform: translate(-50%, -50%);
  background-color: var(--accent);
  color: var(--n-950);
  font-family: var(--font-mono);
  font-size: .68rem;
  font-weight: 800;
  letter-spacing: .1em;
  padding: .3rem .9rem;
  border-radius: var(--r-pill);
  z-index: 2;
  animation: badge-drop .7s var(--e-spring) .3s backwards;
}
@keyframes badge-drop {
  from { opacity: 0; transform: translate(-50%, -180%) scale(.8); }
}

.price {
  display: flex;
  align-items: baseline;
  gap: .4rem;
  font-family: var(--font-mono);
  font-size: 2.75rem;
  font-weight: 700;
  margin: 1.25rem 0 .25rem;
  letter-spacing: -.04em;
}
.price small { font-size: .9rem; font-weight: 400; color: var(--text-dim); }

/* The regular price, crossed out in front of the launch price. Faint and much smaller than the
   figure being charged: an anchor that competes with the real number for attention makes the
   card harder to read, not more persuasive. .price is a baseline-aligned flex row, so this needs
   no positioning of its own. */
.price-was {
  font-size: 1.35rem;
  font-weight: 400;
  color: var(--text-faint);
  text-decoration-thickness: 2px;
}

.feature-list { list-style: none; padding: 0; margin: 1.75rem 0; flex: 1; }

/* The two pricing cards are stretched to the same height by the grid, and
   `flex: 1` above already drops each card's call to action to the bottom -
   so the only thing left that can knock the two buttons out of line is the
   hint underneath them. Free's is one line ("Windows · free forever") and
   Pro's is two, which floated the Pro button a line and a bit above the
   Free one.

   Reserving two lines on both is the fix rather than shortening the copy,
   because landing.js:35 replaces Pro's hint with a different two-line
   sentence the day checkout opens - a fix that depended on the wording
   would come undone on exactly the day it matters. */
.pricing-card .hint { min-height: 2.6rem; }
.feature-list li {
  display: flex;
  align-items: flex-start;
  gap: .65rem;
  margin-bottom: .7rem;
  font-size: .92rem;
  color: var(--text-dim);
}
.feature-list li::before {
  content: "";
  flex: 0 0 auto;
  width: 18px; height: 18px;
  margin-top: .18rem;
  border-radius: 50%;
  background: rgb(163 230 53 / .14) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a3e635' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E") center / 11px no-repeat;
}
.feature-list li.muted::before {
  background: rgb(148 163 184 / .12) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='3.5' stroke-linecap='round'%3E%3Cline x1='6' y1='12' x2='18' y2='12'/%3E%3C/svg%3E") center / 11px no-repeat;
}

/* ==========================================================================
   FAQ accordion

   Seventeen answers, all open, ran to about three screenfuls between the price and the
   download - so somebody looking for the refund policy scrolled past sixteen things they had
   not asked. Collapsed, the whole list of questions is one screen and the section reads as an
   index instead of an essay.

   <details>, not a scripted accordion. It keeps every answer in the DOM, which is what the
   FAQPage JSON-LD in the head depends on (that schema must stay word-for-word identical to what
   the page says, and would be a manual action if it were not); it survives this page's own
   scripting being off, unlike anything driven from motion.js; and the open/close state, the
   keyboard handling and the ARIA all come from the browser rather than from us.
   ========================================================================== */

.faq-item {
  /* .card's 2rem is right for a block of prose and too generous for a row of headings. */
  padding: 1.25rem 1.5rem;
}

.faq-item summary {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  cursor: pointer;
  /* Both are needed: `list-style` is the standards marker, the pseudo-element is Safari's. */
  list-style: none;
}
.faq-item summary::-webkit-details-marker { display: none; }

/* The heading keeps its type and gives up its block layout - it is the summary's label now, not
   a heading with a control beside it. Still an <h3>, so the document outline is unchanged and
   a screen reader's heading list still walks the questions. */
.faq-item summary h3 {
  flex: 1;
  margin: 0;
  font-size: 1.05rem;
  line-height: 1.4;
  transition: color var(--t-fast) var(--e-out);
}
.faq-item summary:hover h3 { color: var(--accent); }

/* The chevron, drawn the same way .feature-list's checkmark is: a data URI, so it costs no
   request and inherits nothing it should not. */
.faq-item summary::after {
  content: "";
  flex: 0 0 auto;
  width: 20px; height: 20px;
  margin-top: .15rem;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a3e635' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E") center / 14px no-repeat;
  transition: transform var(--t-base) var(--e-out);
}
.faq-item[open] summary::after { transform: rotate(180deg); }

/* The answer. The margin only exists once there is an answer on screen, so a closed row is
   exactly its own heading. */
.faq-item > p { margin: .85rem 0 0; }
.faq-item > p:last-child { margin-bottom: 0; }

/* The focus ring belongs to the row, not to the text inside it. */
.faq-item summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
  border-radius: var(--r-sm);
}

/* Grid rows are as tall as their tallest item, so a two-column row holding one open answer and
   one closed question leaves the closed one stretched to match. Aligning to the start lets each
   card be its own height and the column close up. */
.grid-2:has(> .faq-item) { align-items: start; }

/* ==========================================================================
   Forms
   ========================================================================== */

.field { margin-bottom: 1.25rem; }
.label {
  display: block;
  margin-bottom: .5rem;
  font-size: .82rem;
  font-weight: 600;
  color: var(--text-dim);
  letter-spacing: .02em;
}
.hint { font-size: .78rem; color: var(--text-faint); margin: .4rem 0 0; }

.input, .textarea, .select {
  width: 100%;
  background-color: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: .75rem .9rem;
  font-family: var(--font-sans);
  font-size: .92rem;
  line-height: 1.5;
  transition: border-color var(--t-fast) var(--e-out),
              box-shadow var(--t-base) var(--e-out),
              background-color var(--t-fast) var(--e-out);
}
.input:hover, .textarea:hover, .select:hover { border-color: var(--border-strong); }
.input:focus, .textarea:focus, .select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgb(163 230 53 / .15);
  background-color: var(--surface);
}
.textarea {
  min-height: 100px;
  resize: vertical;
  font-family: var(--font-mono);
  font-size: .82rem;
  letter-spacing: .01em;
}
.input.mono { font-family: var(--font-mono); font-size: .85rem; }
.input::placeholder, .textarea::placeholder { color: var(--text-faint); }

.input.invalid, .textarea.invalid {
  border-color: var(--danger);
  animation: shake .4s var(--e-in-out);
}
@keyframes shake {
  10%, 90% { transform: translateX(-2px); }
  20%, 80% { transform: translateX(4px); }
  30%, 50%, 70% { transform: translateX(-6px); }
  40%, 60% { transform: translateX(6px); }
}

/* The waitlist forms: one field and one button, side by side where there is room and stacked
   where there is not. flex-wrap does that on its own, and flex-basis on the input is what decides
   when — below 14rem of field the two would be unusable side by side, so they break instead.
   The error and the hint claim a whole row so neither ever shares one with the input. */
.waitlist {
  display: flex;
  flex-wrap: wrap;
  gap: .6rem;
  margin-top: 1rem;
}

.waitlist .input { flex: 1 1 14rem; }
.waitlist .form-error,
.waitlist .hint { flex-basis: 100%; margin-top: 0; }

/* In the Pro card the button is .btn-block, so it takes its own row and the field takes the one
   above it — the card is too narrow for anything else, and the row it sits in is the card's own
   full width. */
.waitlist .btn-block { flex-basis: 100%; }

.form-error {
  display: flex;
  align-items: center;
  gap: .5rem;
  color: var(--danger);
  font-size: .85rem;
  margin-top: .6rem;
  animation: fade-up .3s var(--e-out);
}

/* ==========================================================================
   Code / key display
   ========================================================================== */

.keybox {
  position: relative;
  background-color: var(--bg);
  border: 1px dashed rgb(163 230 53 / .45);
  border-radius: var(--r-sm);
  padding: 1rem;
  font-family: var(--font-mono);
  font-size: .78rem;
  line-height: 1.7;
  word-break: break-all;
  color: var(--accent-hover);
  overflow: hidden;
}
/* One-shot shimmer when the key first appears. */
.keybox.fresh::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(105deg, transparent 30%, rgb(163 230 53 / .18) 50%, transparent 70%);
  transform: translateX(-100%);
  animation: sweep 1.4s var(--e-out) .2s;
}
@keyframes sweep { to { transform: translateX(100%); } }

/* ==========================================================================
   Badges & pills
   ========================================================================== */

.badge {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .2rem .6rem;
  border-radius: var(--r-pill);
  font-family: var(--font-mono);
  font-size: .7rem;
  font-weight: 600;
  letter-spacing: .04em;
  border: 1px solid transparent;
  white-space: nowrap;
}
.badge-ok      { background: rgb(163 230 53 / .12); color: var(--accent); border-color: rgb(163 230 53 / .3); }
.badge-danger  { background: rgb(255 107 107 / .12); color: var(--danger); border-color: rgb(255 107 107 / .3); }
.badge-warn    { background: rgb(251 191 36 / .12); color: var(--warn); border-color: rgb(251 191 36 / .3); }
.badge-muted   { background: rgb(148 163 184 / .1); color: var(--text-dim); border-color: rgb(148 163 184 / .22); }

/* ==========================================================================
   Success moment — self-drawing checkmark
   ========================================================================== */

.success-mark {
  width: 60px; height: 60px;
  margin: 0 auto 1.25rem;
  display: block;
}
.success-mark circle {
  stroke: var(--accent);
  stroke-width: 2;
  fill: rgb(163 230 53 / .08);
  stroke-dasharray: 166;
  stroke-dashoffset: 166;
  animation: draw .7s var(--e-out) forwards;
}
.success-mark path {
  stroke: var(--accent);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
  stroke-dasharray: 48;
  stroke-dashoffset: 48;
  animation: draw .45s var(--e-out) .55s forwards;
}
@keyframes draw { to { stroke-dashoffset: 0; } }

.pulse-border { animation: pulse-border 1.6s var(--e-out) .2s; }
@keyframes pulse-border {
  0%   { box-shadow: 0 0 0 0 rgb(163 230 53 / .5); }
  100% { box-shadow: 0 0 0 22px rgb(163 230 53 / 0); }
}

/* ==========================================================================
   Loading — indeterminate bar & skeletons
   ========================================================================== */

.progress-indeterminate {
  position: relative;
  height: 3px;
  width: 100%;
  border-radius: var(--r-pill);
  background: var(--border);
  overflow: hidden;
}
.progress-indeterminate::after {
  content: "";
  position: absolute;
  inset: 0;
  width: 40%;
  border-radius: inherit;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  animation: indeterminate 1.4s var(--e-in-out) infinite;
}
@keyframes indeterminate {
  from { transform: translateX(-100%); }
  to   { transform: translateX(350%); }
}

.skeleton {
  position: relative;
  overflow: hidden;
  background: var(--surface-2);
  border-radius: var(--r-sm);
  height: 1rem;
}
.skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgb(255 255 255 / .06), transparent);
  transform: translateX(-100%);
  animation: skeleton-sweep 1.5s ease-in-out infinite;
}
@keyframes skeleton-sweep { to { transform: translateX(100%); } }

.dots::after {
  content: "";
  animation: dots 1.4s steps(4, end) infinite;
}
@keyframes dots {
  0%   { content: ""; }
  25%  { content: "."; }
  50%  { content: ".."; }
  75%  { content: "..."; }
}

/* ==========================================================================
   Stepped state machine (redeem portal)
   ========================================================================== */

/* The wrapper animates its height so the card grows rather than jumping. */
.stage {
  position: relative;
  transition: height var(--t-slow) var(--e-out);
  overflow: hidden;
}
.step {
  transition: opacity var(--t-base) var(--e-out), transform var(--t-base) var(--e-out);
}
/* Steps toggle via `.is-hidden`, never the `hidden` attribute — the element must
   stay measurable (scrollHeight) while it fades so the stage can size to it. */
.step.leaving { opacity: 0; transform: translateY(-10px); pointer-events: none; }
.step.entering { opacity: 0; transform: translateY(14px); }
.step.is-hidden { display: none !important; }

/* ==========================================================================
   Toasts
   ========================================================================== */

.toast-stack {
  position: fixed;
  right: 1.25rem; bottom: 1.25rem;
  z-index: 3000;
  display: flex;
  flex-direction: column-reverse;
  gap: .6rem;
  pointer-events: none;
}
.toast {
  display: flex;
  align-items: center;
  gap: .65rem;
  min-width: 240px;
  max-width: min(90vw, 380px);
  padding: .8rem 1rem;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-left: 3px solid var(--accent);
  border-radius: var(--r-sm);
  box-shadow: var(--sh-3);
  font-size: .88rem;
  pointer-events: auto;
  animation: toast-in .45s var(--e-spring);
}
.toast.leaving { animation: toast-out .3s var(--e-out) forwards; }
.toast-danger { border-left-color: var(--danger); }
.toast-warn { border-left-color: var(--warn); }

@keyframes toast-in {
  from { opacity: 0; transform: translateX(120%) scale(.9); }
  to   { opacity: 1; transform: none; }
}
@keyframes toast-out {
  to { opacity: 0; transform: translateX(30%) scale(.95); }
}

/* ==========================================================================
   Modal
   ========================================================================== */

.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 2500;
  display: grid;
  place-items: center;
  padding: 1.5rem;
  background: rgb(0 0 0 / .7);
  backdrop-filter: blur(5px);
  -webkit-backdrop-filter: blur(5px);
  opacity: 0;
  transition: opacity var(--t-base) var(--e-out);
}
.modal-backdrop.open { opacity: 1; }
.modal-backdrop[hidden] { display: none; }

.modal {
  width: 100%;
  max-width: 480px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  padding: 2rem;
  box-shadow: var(--sh-3);
  transform: scale(.96) translateY(12px);
  opacity: 0;
  transition: transform var(--t-base) var(--e-out), opacity var(--t-base) var(--e-out);
}
.modal-backdrop.open .modal { transform: none; opacity: 1; }
.modal h3 { margin-bottom: .5rem; }
.modal p { color: var(--text-dim); font-size: .9rem; }
.modal-actions { display: flex; gap: .75rem; justify-content: flex-end; margin-top: 1.75rem; }

/* ==========================================================================
   Stats
   ========================================================================== */

.stat-grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
.stat {
  padding: 1.25rem 1.4rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  transition: transform var(--t-base) var(--e-out), border-color var(--t-base) var(--e-out);
}
.stat:hover { transform: translateY(-3px); border-color: var(--border-strong); }
.stat-label {
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.stat-value {
  font-family: var(--font-mono);
  font-size: 2rem;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -.03em;
  margin-top: .35rem;
  font-variant-numeric: tabular-nums;
}
.stat-value.accent { color: var(--accent); }
.stat-value.danger { color: var(--danger); }
.stat-value.warn { color: var(--warn); }

/* ==========================================================================
   Tables
   ========================================================================== */

.table-wrap {
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  overflow-x: auto;
  background: var(--surface);
}
table { width: 100%; border-collapse: collapse; font-size: .86rem; }
thead th {
  position: sticky;
  top: 0;
  background: var(--surface-2);
  text-align: left;
  padding: .8rem 1rem;
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--text-faint);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
tbody td {
  padding: .8rem 1rem;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
tbody tr {
  transition: background-color var(--t-fast) var(--e-out);
  animation: row-in .4s var(--e-out) backwards;
  animation-delay: calc(var(--i, 0) * 35ms);
}
tbody tr:hover { background: rgb(255 255 255 / .025); }
tbody tr:last-child td { border-bottom: 0; }
@keyframes row-in {
  from { opacity: 0; transform: translateY(8px); }
}
td.mono, th.mono { font-family: var(--font-mono); font-size: .8rem; }
.td-actions { text-align: right; white-space: nowrap; }

.empty-state {
  padding: 3rem 1.5rem;
  text-align: center;
  color: var(--text-faint);
  font-size: .9rem;
}

/* ==========================================================================
   Tabs
   ========================================================================== */

.tabs {
  position: relative;
  display: flex;
  gap: .25rem;
  border-bottom: 1px solid var(--border);
  margin-bottom: 1.5rem;
}
.tab {
  position: relative;
  background: none;
  border: 0;
  padding: .75rem 1.1rem;
  color: var(--text-dim);
  font-family: var(--font-sans);
  font-size: .9rem;
  font-weight: 500;
  cursor: pointer;
  transition: color var(--t-fast) var(--e-out);
}
.tab:hover { color: var(--text); }
.tab[aria-selected="true"] { color: var(--accent); }
/* Sliding indicator; motion.js writes --tab-x / --tab-w. */
.tab-indicator {
  position: absolute;
  bottom: -1px;
  left: 0;
  height: 2px;
  width: var(--tab-w, 0);
  transform: translateX(var(--tab-x, 0));
  background: var(--accent);
  border-radius: 2px;
  transition: transform var(--t-base) var(--e-out), width var(--t-base) var(--e-out);
}

/* ==========================================================================
   Footer
   ========================================================================== */

.site-footer {
  padding: 3rem 0;
  margin-top: 4rem;
  border-top: 1px solid var(--border);
  color: var(--text-faint);
  font-size: .85rem;
}
.footer-inner {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  align-items: center;
  justify-content: space-between;
}
.site-footer a { color: var(--text-dim); transition: color var(--t-fast) var(--e-out); }
.site-footer a:hover { color: var(--accent); }
/* Privacy and Terms have no nav item of their own, so the footer is the only place their
   visitors get a "you are here" at all. */
.site-footer a[aria-current="page"] { color: var(--text); }

/* ==========================================================================
   Status pages (404 / 429 / error)
   ========================================================================== */

.status-page {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 2rem 1.5rem;
  text-align: center;
}
.status-code {
  font-family: var(--font-mono);
  font-size: clamp(5rem, 22vw, 11rem);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -.06em;
  background: linear-gradient(180deg, var(--text) 10%, rgb(163 230 53 / .35) 90%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: fade-up .8s var(--e-out) backwards;
}
.status-code.glitch { position: relative; animation: fade-up .8s var(--e-out) backwards, glitch 5s steps(1) infinite 1.5s; }
@keyframes glitch {
  0%, 96%, 100% { transform: none; opacity: 1; }
  97% { transform: translate(-3px, 1px) skewX(4deg); opacity: .85; }
  98% { transform: translate(3px, -1px) skewX(-3deg); }
  99% { transform: none; }
}

/* ==========================================================================
   Utilities
   ========================================================================== */

.stack > * + * { margin-top: var(--gap, 1rem); }
.row { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.row-center { display: flex; align-items: center; justify-content: center; gap: .75rem; flex-wrap: wrap; }
.text-dim { color: var(--text-dim); }
.text-faint { color: var(--text-faint); }
.text-accent { color: var(--accent); }
.text-danger { color: var(--danger); }
.text-center { text-align: center; }
.text-sm { font-size: .85rem; }
.text-xs { font-size: .78rem; }
.mt-0 { margin-top: 0; } .mt-1 { margin-top: .5rem; } .mt-2 { margin-top: 1rem; }
.mt-3 { margin-top: 1.5rem; } .mt-4 { margin-top: 2rem; }
.mb-0 { margin-bottom: 0; } .mb-2 { margin-bottom: 1rem; } .mb-3 { margin-bottom: 1.5rem; }
.flex-1 { flex: 1; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }

.callout {
  display: flex;
  gap: .75rem;
  padding: .9rem 1rem;
  border-radius: var(--r-sm);
  font-size: .85rem;
  line-height: 1.55;
  border: 1px solid;
}
.callout-warn { background: rgb(251 191 36 / .07); border-color: rgb(251 191 36 / .3); color: #fde68a; }
.callout-info { background: rgb(163 230 53 / .06); border-color: rgb(163 230 53 / .25); color: var(--text-dim); }

/* ==========================================================================
   Clocks & time
   ========================================================================== */

/* --- the hero clock: real time, real ticking --- */
.clock {
  position: relative;
  width: min(220px, 46vw);
  aspect-ratio: 1;
  margin: 0 auto;
  filter: drop-shadow(0 12px 32px rgb(163 230 53 / .18));
}
.clock svg { width: 100%; height: 100%; overflow: visible; }

.clock-face {
  fill: rgb(23 23 23 / .7);
  stroke: var(--border-strong);
  stroke-width: 1.5;
}
/* Elapsed-minutes arc, swept by JS via stroke-dashoffset. */
.clock-arc {
  fill: none;
  stroke: var(--accent);
  stroke-width: 3;
  stroke-linecap: round;
  opacity: .85;
  transform: rotate(-90deg);
  transform-origin: 100px 100px;
  transition: stroke-dashoffset 1s linear;
}
.clock-tick { stroke: var(--text-faint); stroke-width: 2; stroke-linecap: round; }
.clock-tick.major { stroke: var(--text-dim); stroke-width: 3; }

.clock .hand {
  stroke-linecap: round;
  transform-origin: 100px 100px;
}
.clock .hand-hour { stroke: var(--text); stroke-width: 6; }
.clock .hand-min  { stroke: var(--text); stroke-width: 4; }
/* The second hand steps, then settles with a touch of overshoot — like a real one. */
.clock .hand-sec  {
  stroke: var(--accent);
  stroke-width: 2;
  transition: transform .14s var(--e-spring);
}
.clock-pin { fill: var(--accent); }
.clock-pin-inner { fill: var(--n-950); }

/* Ring that pulses outward once per second. */
.clock-pulse {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 1px solid var(--accent);
  opacity: 0;
  pointer-events: none;
}
.clock-pulse.beat { animation: clock-beat 1s var(--e-out); }
@keyframes clock-beat {
  0%   { opacity: .5; transform: scale(.94); }
  100% { opacity: 0;  transform: scale(1.12); }
}

/* --- running session readout --- */
.live-timer {
  display: inline-flex;
  /* Two boxes only: the dot and the text group, both centred on the pill's middle. */
  align-items: center;
  gap: .6rem;
  padding: .5rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: rgb(23 23 23 / .6);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  font-family: var(--font-mono);
  font-size: .95rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: .04em;
}
/* The label and the readout are different faces at different sizes, so centring their
   boxes against each other leaves "Tracking" ~5px above the digits: equal line boxes are
   not equal baselines, since each font puts its baseline at a different height inside the
   em box. Align them on the real baseline here, and let the parent centre the group — with
   line-height 1 this box is exactly the readout's line box, whose ink is already centred
   in it, so the text lands on the dot's centre line. */
/* Deliberately NOT a flex container. The label and the readout are different faces at
   different sizes, and plain inline layout is what puts two such runs on a shared
   baseline — flex `align-items: baseline` adds slack below the line and leaves the text
   riding high. As a flex item of the pill this blockifies to a single line box whose
   height is the readout's, so centring it lands the text on the dot's centre line. */
.live-timer-text { line-height: 1; }
.live-timer .rec {
  flex: 0 0 auto;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--danger);
  animation: rec-blink 2s var(--e-in-out) infinite;
}
/* Collapse the inherited body line-height (1.65) so the pill hugs the text, now that
   the two runs are aligned on their baseline rather than by box. */
.live-timer .label,
.live-timer [data-live-timer] { line-height: 1; }
.live-timer .label {
  /* `.label` is the form-label rule (display:block; margin-bottom:.5rem) — inside the
     pill it has to be an ordinary inline run so it shares the readout's line box. */
  display: inline;
  margin-bottom: 0;
  /* The pill's `gap` spaces the dot from the text; inside the text this does it. */
  margin-right: .6rem;
  font-family: var(--font-sans);
  font-size: .75rem;
  font-weight: 500;
  color: var(--text-faint);
  letter-spacing: 0;
  /* Baseline-aligned by the rules above, which is correct — but the two faces have
     different cap heights, so a shared baseline still leaves this run's ink sitting about
     a pixel low against the digits. Optical nudge only; the line box does not move. */
  vertical-align: .08em;
}
@keyframes rec-blink {
  0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgb(255 107 107 / .5); }
  50%      { opacity: .45; box-shadow: 0 0 0 5px rgb(255 107 107 / 0); }
}

/* ==========================================================================
   App mockup — the real Tical window, in the app's own tokens

   These are deliberately a separate, locally-scoped scale rather than the site's
   semantic vars: this block is a picture of the product, so it has to match the
   desktop app's palette exactly (src/Tical/App.axaml) and must not drift when the
   site's theme changes.
   ========================================================================== */

.app-mock {
  --a-bg: #0A0C10;
  --a-bg-soft: #0F1318;
  --a-sunken: #0D1016;
  --a-surface: #141820;
  --a-hover: #1E2433;
  --a-active: #2A3245;
  --a-border: #1E2433;
  --a-t1: #F0F4F8;
  --a-t2: #7B8794;
  --a-t3: #525E6B;
  --a-accent: #9BE64D;
  --a-danger: #FF6B6B;
  --a-warn: #FFB84D;

  /* The desktop app sets JetBrains Mono at Window level, so everything inherits it. */
  font-family: var(--font-mono);
  /* The window itself is 600x700 (MainWindow.axaml), so the picture is drawn at 1:1. */
  max-width: 37.5rem;
  margin: 0 auto;
  border: 1px solid var(--a-hover);
  border-radius: 12px;
  overflow: hidden;
  background: linear-gradient(180deg, var(--a-bg) 0%, var(--a-bg-soft) 50%, var(--a-bg) 100%);
  box-shadow: var(--sh-3), 0 0 0 1px rgb(0 0 0 / .5);
  position: relative;
  isolation: isolate;
}
/* AuroraGlow: the app paints a 260px-tall lime radial behind the shell, anchored to the
   top of the window. It is the only light source in the picture. */
.app-mock::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 260px;
  z-index: -1;
  background: radial-gradient(90% 90% at 50% 0%, rgb(155 230 77 / .102), transparent 70%);
  pointer-events: none;
}

/* Every glyph in here is the app's own PathIcon geometry on a 24x24 viewBox, so they
   are filled paths rather than the stroked icon set the rest of the site uses. */
.app-mock svg { fill: currentColor; flex: 0 0 auto; }

/* --- title bar (MainWindow.axaml: 40px, icon at 16, buttons 36x36 spaced 2) --- */
.am-titlebar {
  display: flex;
  align-items: center;
  gap: 2px;
  height: 40px;
  padding: 0 4px 0 16px;
  background: var(--a-bg);
}
.am-titlebar .am-icon { width: 20px; height: 20px; flex: 0 0 auto; }
.am-wordmark {
  font-family: 'Saira Stencil One', var(--font-mono);
  font-size: 1rem;
  color: var(--a-accent);
  opacity: .9;
  letter-spacing: .02em;
  /* margin-right:auto is the draggable spacer the app puts between the wordmark and
     its window buttons. Long-hand on purpose: the 3-value shorthand sets `bottom`. */
  margin-left: 6px;
  margin-right: auto;
}
.am-pill {
  font-size: .5625rem;      /* 9px */
  font-weight: 700;
  padding: 3px 8px;
  border-radius: 10px;
  background: var(--a-accent);
  color: var(--a-bg);
  letter-spacing: .06em;
  margin-right: 8px;
}
/* Button.icon: chromeless until hover — no border at rest, which is what the title bar
   actually draws. Only the hover wash and the glyph colour change. */
.am-btn {
  width: 36px; height: 36px;
  display: grid;
  place-items: center;
  border-radius: 8px;
  border: 0;
  color: var(--a-t2);
  background: none;
  transition: background var(--t-fast) var(--e-out), color var(--t-fast) var(--e-out);
}
.am-btn:hover { background: var(--a-hover); color: var(--a-t1); }
/* The close button is plain too; it washes to the muted danger surface on hover. */
.am-btn.danger:hover { background: #3D1A1A; color: var(--a-danger); }

/* --- tracker panel (MainView.axaml: StackPanel Margin 16,8,16,16 / Spacing 10) --- */
.am-top {
  display: grid;
  gap: 10px;
  padding: 8px 16px 16px;
}

/* --- context row -----------------------------------------------------------
   MainView.axaml lays this out as Grid ColumnDefinitions="Auto,*": the switcher
   pill, then the active context's MainBar slot 8px to its right. Both sit at
   ContextRowControlHeight (40px); Pomodoro's taller panel stretches the row. */
.am-ctxrow {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 8px;
  align-items: stretch;
}

.am-ctxswitch { position: relative; }

/* ToggleButton.contextTrigger */
.am-ctx-trigger {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: 40px;
  padding: 7px 10px;
  border-radius: 8px;
  border: 1px solid var(--a-border);
  background: var(--a-surface);
  color: var(--a-t1);
  font-family: inherit;
  font-size: .75rem;          /* 12px */
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--t-base) var(--e-out), border-color var(--t-base) var(--e-out);
}
.am-ctx-trigger:hover { background: var(--a-hover); border-color: var(--a-active); }
.am-ctx-trigger > svg:first-child { width: 13px; height: 13px; color: var(--a-accent); }
.am-ctx-caret {
  width: 11px; height: 11px;
  color: var(--a-t2);
  transition: transform var(--t-base) var(--e-out);
}
/* :checked in the app — the pill borders itself in the accent while its popup is up. */
.am-ctx-trigger[aria-expanded="true"] {
  background: var(--a-hover);
  border-color: var(--a-accent);
}
.am-ctx-trigger[aria-expanded="true"] .am-ctx-caret { transform: rotate(180deg); }

/* Border.contextDropdownCard — a Popup, so it floats over the composer rather than
   pushing it down. */
.am-ctx-menu {
  position: absolute;
  z-index: 5;
  top: 100%;
  left: 0;
  margin-top: 8px;
  min-width: 220px;
  padding: 4px;
  border-radius: 10px;
  border: 1px solid var(--a-border);
  background: var(--a-surface);
  box-shadow: 0 12px 32px -6px rgb(0 0 0 / .7);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-8px) scale(.985);
  transform-origin: top left;
  /* visibility is switched, not interpolated: held until the fade-out finishes on close,
     flipped immediately on open. Transitioning it directly leaves the card unclickable
     if the transition is ever interrupted. */
  transition: opacity var(--t-base) var(--e-out), transform var(--t-base) var(--e-out),
              visibility 0s linear var(--t-base);
}
.am-ctx-menu.open {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition-delay: 0s;
}

/* Button.contextRow — Grid ColumnDefinitions="3,Auto,*,Auto,Auto" */
.am-ctx-item {
  display: grid;
  grid-template-columns: 3px auto minmax(0, 1fr) auto;
  align-items: center;
  width: 100%;
  margin: 1px 0;
  padding: 9px 8px;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--a-t1);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: background var(--t-fast) var(--e-out);
}
.am-ctx-item:hover  { background: var(--a-hover); }
.am-ctx-item:active { background: var(--a-active); }
.am-ctx-item > svg {
  width: 13px; height: 13px;
  margin-left: 10px;
  color: var(--a-accent);
}
.am-ctx-itemname {
  margin: 0 8px 0 9px;
  font-size: .75rem;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The 3px accent rail and the check both belong to the active row only. */
.am-ctx-rail {
  align-self: stretch;
  margin: 1px 0;
  border-radius: 2px;
  background: transparent;
}
.am-ctx-item.on .am-ctx-rail { background: var(--a-accent); }
.am-ctx-check { font-size: .75rem; color: var(--a-accent); opacity: 0; }
.am-ctx-item.on .am-ctx-check { opacity: 1; }

/* --- MainBar slot ---------------------------------------------------------- */
.am-mainbar {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

/* ComboBox / TextBox: sunken fill, 8px radius, at the shared row height. */
.am-picker {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  min-height: 40px;
  padding: 10px 12px;
  border-radius: 8px;
  border: 1px solid var(--a-border);
  background: var(--a-sunken);
  color: var(--a-t1);
  font-size: .8125rem;        /* 13px */
}
.am-picker.grow { flex: 1 1 0; }
.am-picker > span {
  min-width: 0;
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.am-picker.empty > span { color: var(--a-t2); }
.am-picker svg { width: 12px; height: 12px; color: var(--a-t2); }

/* Button.icon — chromeless until hover, like the title bar's. */
.am-iconbtn {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 28px; height: 28px;
  border-radius: 6px;
  color: var(--a-t2);
  transition: background var(--t-fast) var(--e-out), color var(--t-fast) var(--e-out);
}
.am-iconbtn:hover { background: var(--a-hover); color: var(--a-t1); }
.am-iconbtn svg { width: 14px; height: 14px; }

.am-status { font-size: .6875rem; color: var(--a-t3); }
.am-status.ok   { color: var(--a-accent); }
.am-status.warn { color: var(--a-warn); }

/* Pomodoro's MainBar is a panel rather than a picker row, so it gets the card
   treatment and sets the row's height. */
.am-pomo {
  padding: 8px 12px;
  border-radius: 8px;
  border: 1px solid var(--a-border);
  background: var(--a-surface);
}
.am-pomo-phase {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex: 0 0 auto;
  font-size: .8125rem;
  font-weight: 600;
  color: var(--a-t1);
}
.am-pomo-phase i {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--a-t2);
}
.am-pomo-cycles { font-size: .6875rem; letter-spacing: 2px; color: var(--a-accent); }
.am-pomo .am-status { margin-left: auto; }

/* --- composer (Border.card.composer) ---------------------------------------
   One surface holding the description, the ProjectInput slot and the timer row —
   not four floating controls. CornerRadius 14, Padding 14,12, ShadowMd. */
.am-composer {
  position: relative;
  display: grid;
  gap: 10px;
  padding: 12px 14px;
  border-radius: 14px;
  border: 1px solid rgb(255 255 255 / .06);
  background: var(--a-surface);
  box-shadow: 0 2px 8px -1px rgb(0 0 0 / .35);
}
/* GlassEdgeBrush: a hairline brightest along the top edge, gone by a third of the way
   down. Drawn as an inset overlay because a border can't carry a gradient on its own. */
.am-composer::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(180deg, rgb(255 255 255 / .2) 0%, rgb(255 255 255 / .05) 35%, transparent 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask-composite: exclude;
  pointer-events: none;
}
/* The description box has no surface of its own inside the card. */
.am-input {
  min-width: 0;
  font-size: .9375rem;        /* 15px */
  color: var(--a-t1);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.am-input.empty { color: var(--a-t2); }   /* the "What are you working on?" watermark */

/* --- ProjectInput slot + tag editor ---------------------------------------- */
.am-projrow { display: grid; gap: 10px; }
.am-hair { height: 1px; background: var(--a-border); }
.am-field {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border-radius: 8px;
  background: var(--a-sunken);
  color: var(--a-t1);
  font-size: .8125rem;
}
.am-field > svg { width: 12px; height: 12px; margin-left: auto; color: var(--a-t2); }
.am-field.empty { color: var(--a-t2); }
.am-tagbox { font-size: .75rem; padding: 6px 10px; }
.am-tagrow { display: grid; gap: 6px; }

/* --- composer head: the description and the Start pill ---------------------- */
/* The 28px readout, the 56px sweeping ring and the round stop that used to live here are
   gone, along with @keyframes am-sweep and the .is-offscreen pause that existed only to
   throttle it. The app deleted that control in f7a03db ("Remove timer ring ... for
   multi-run support"): a single readout cannot answer "which run?" once runs are plural,
   so the Running panel below owns the clock and the stop now, one set per run. */
.am-comphead {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
}
/* Button.action.startPill: height 32, padding 12,0, radius 8, accent border. */
.am-start {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 32px;
  padding: 0 12px;
  border-radius: 8px;
  border: 1px solid var(--a-accent);
  background: transparent;
  color: var(--a-accent);
  font-size: .75rem;
  font-weight: 600;
  box-shadow: 0 1px 2px rgb(0 0 0 / .25);
  transition: background var(--t-fast) var(--e-out), color var(--t-fast) var(--e-out);
}
.am-start:hover { background: #1A3D1A; color: #ADEE6A; }
.am-start .am-glyph { width: 11px; height: 11px; }
.am-glyph { width: 16px; height: 16px; }

/* --- Running panel (Border.card.runningPanel: radius 14, padding 12,10) ------ */
/* No margin-top: .am-top is a grid with gap:10px, which already spaces this. */
.am-runs {
  display: grid;
  gap: 4px;                       /* StackPanel Spacing="4" */
  padding: 10px 12px;
  border-radius: 14px;
  border: 1px solid rgb(255 255 255 / .06);
  background: var(--a-surface);
  box-shadow: 0 2px 8px -1px rgb(0 0 0 / .35);
}
/* Label, rule, chevron - the day-group header idiom, at card-title weight. */
.am-runshdr {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
}
.am-runstitle { font-size: .75rem; font-weight: 600; color: var(--a-t1); }
.am-runrule { height: 1px; margin: 0 10px; background: var(--a-border); }
.am-runshdr svg { width: 10px; height: 10px; color: var(--a-t3); }

/* Grid ColumnDefinitions="3,*,Auto,Auto,Auto" */
.am-run {
  display: grid;
  grid-template-columns: 3px minmax(0, 1fr) auto auto auto;
  align-items: center;
  padding: 2px;
  margin: 1px 0;
  border-radius: 8px;
  transition: background var(--t-fast) var(--e-out);
}
.am-run:hover { background: var(--a-hover); }
/* Full strength on the newest run: the one the hotkey, the compact readout and the
   tray's "stop newest" all act on. */
.am-runrail {
  align-self: stretch;
  margin: 1px 0;
  border-radius: 2px;
  background: var(--a-accent);
  opacity: .45;
}
.am-runrail.newest { opacity: 1; }
.am-rundesc {
  min-width: 0;
  margin-left: 6px;
  padding: 4px;
  font-size: .8125rem;
  color: var(--a-t1);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* ContextName is plugin-supplied and unbounded, so the app caps and ellipsises it. */
.am-runctx {
  margin: 0 6px;
  padding: 2px 6px;
  max-width: 110px;
  border-radius: 6px;
  border: 1px solid var(--a-border);
  background: #181D27;            /* AppSurfaceRaised */
  color: var(--a-t2);
  font-size: .6875rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.am-runtime {
  margin: 0 6px;
  font-size: 1rem;
  font-weight: 700;
  color: var(--a-t1);
  font-variant-numeric: tabular-nums;
}
.am-runstop {
  display: grid;
  place-items: center;
  padding: 8px;
  border-radius: 6px;
  color: var(--a-t2);
  transition: color var(--t-fast) var(--e-out);
}
.am-runstop svg { width: 12px; height: 12px; }
.am-run:hover .am-runstop { color: var(--a-danger); }

/* The manual-add button, which moved into the search bar with the app. */
.am-add {
  display: grid;
  place-items: center;
  width: 28px; height: 28px;
  border-radius: 6px;
  color: var(--a-accent);
  transition: background var(--t-fast) var(--e-out);
}
.am-add:hover { background: var(--a-hover); }
.am-add svg { width: 14px; height: 14px; }

/* --- divider + search bar --------------------------------------------------- */
.am-divider { height: 1px; background: var(--a-border); margin: 0 1rem; }

.am-search {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 12px 16px 8px;
  padding: 10px 3px 10px 12px;
  border-radius: 10px;
  border: 1px solid var(--a-border);
  background: var(--a-surface);
  box-shadow: 0 1px 2px rgb(0 0 0 / .25);
  color: var(--a-t2);
  font-size: .8125rem;
}
.am-search > svg { width: 14px; height: 14px; color: var(--a-t3); }
.am-searchtext { flex: 1 1 auto; }

/* --- entry list (ListBox Margin 16,4,16,16) --------------------------------- */
.am-list { padding: 4px 16px 16px; }

/* Grid ColumnDefinitions="Auto,*,Auto": date, a hairline rule, then the day total. */
.am-dayhdr {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;      /* not baseline: the odometer total is an inline-flex box */
  margin: 14px 4px 8px;
  font-size: .6875rem;        /* 11px */
}
.am-dayhdr .am-date { color: var(--a-t2); font-weight: 600; letter-spacing: .04em; }
.am-dayhdr .am-rule { height: 1px; margin: 0 10px; background: var(--a-border); }
.am-dayhdr .am-total { color: var(--a-accent); font-weight: 700; font-variant-numeric: tabular-nums; }

.am-row {
  display: grid;
  grid-template-columns: 3px minmax(0, 1fr) auto 90px auto;
  align-items: center;
  background: var(--a-surface);
  border: 1px solid var(--a-border);
  border-radius: 10px;
  padding: 12px 14px;
  margin-bottom: 6px;
  transition: border-color var(--t-fast) var(--e-out);
}
.am-row:hover { border-color: var(--a-active); }

/* The accent rail: half-height at rest, growing to fill the row on hover. */
.am-rail {
  align-self: stretch;
  margin: 1px 0;
  border-radius: 2px;
  background: var(--a-accent);
  transform: scaleY(.5);
  transition: transform var(--t-base) var(--e-out);
}
.am-row:hover .am-rail { transform: scaleY(1); }

.am-body { min-width: 0; margin: 0 10px; display: grid; gap: 2px; }
.am-desc { font-size: .8125rem; font-weight: 500; color: var(--a-t1); }
.am-proj { font-size: .6875rem; color: var(--a-t3); }
.am-badge { font-size: .75rem; color: var(--a-t2); margin-right: 8px; }
.am-dur {
  text-align: right;
  margin-right: 10px;
  font-size: .875rem;         /* 14px */
  font-weight: 700;
  color: var(--a-t1);
  font-variant-numeric: tabular-nums;
}
.am-kebab {
  display: grid;
  place-items: center;
  padding: 6px;
  border-radius: 6px;
  color: var(--a-t2);
  transition: background var(--t-fast) var(--e-out), color var(--t-fast) var(--e-out);
}
.am-kebab svg { width: 14px; height: 14px; }
.am-row:hover .am-kebab { background: var(--a-hover); color: var(--a-t1); }

/* ProgressBar.durationBar: this entry's share of its day, filled in AppAccentDimBrush.
   The width comes from initAppMock, which measures each row against its day's longest. */
.am-bar {
  grid-column: 1 / -1;
  margin: 8px 0 0 13px;
  height: 3px;
  border-radius: 2px;
  background: var(--a-border);
  overflow: hidden;
}
.am-bar i {
  display: block;
  height: 100%;
  width: var(--am-share, 0%);
  border-radius: 2px;
  background: rgb(155 230 77 / .333);
  transition: width var(--t-slow) var(--e-out);
}

/* Tag chips: a left-pointing label shape with a punch-hole, per App.axaml's Tag template. */
.am-tags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 2px; }
.am-tag {
  position: relative;
  display: inline-flex;
  align-items: center;
  height: 16px;
  padding: 0 8px 0 14px;
  margin-left: 10px;
  font-size: .625rem;         /* 10px */
  color: var(--a-t2);
  border: 1px solid var(--a-t2);
  border-left: 0;
  border-radius: 0 4px 4px 0;
}
/* The point: a square rotated 45deg with only its lower-left two borders drawn, so the
   two visible edges are the arrowhead's diagonals. 11.3px is 16px/sqrt(2), which makes
   those diagonals land exactly on the box's top and bottom edges. A clipped triangle
   cannot do this — clip-path cuts the border away with the fill. */
.am-tag::before {
  content: "";
  position: absolute;
  left: -5.65px; top: 50%;
  width: 11.3px; height: 11.3px;
  margin-top: -5.65px;
  transform: rotate(45deg);
  border: 1px solid var(--a-t2);
  border-top: 0;
  border-right: 0;
  border-bottom-left-radius: 2px;
  /* ContentControl.Tag.Grey sets Background="Transparent", so the arrowhead shows
     whatever is behind it — the row surface in the list, the composer in the tracker. */
  background: transparent;
}
/* The punch-hole. */
.am-tag::after {
  content: "";
  position: absolute;
  left: 3px; top: 50%;
  width: 3px; height: 3px;
  margin-top: -1.5px;
  border-radius: 50%;
  background: var(--a-t2);
  z-index: 1;
}

/* Switching contexts swaps the MainBar, the project row and the entry list; fade them
   rather than letting the new pane pop in. The switcher itself never moves. */
.am-pane[hidden] { display: none; }
.am-pane.am-swap { animation: am-fade-up var(--t-base) var(--e-out) both; }
@keyframes am-fade-up {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

@media (max-width: 640px) {
  .am-titlebar .am-btn { display: none; }
  .am-ctxrow { grid-template-columns: minmax(0, 1fr); }
  .am-runtime { font-size: .875rem; }
  .am-runctx { max-width: 76px; }
  .am-row { grid-template-columns: 3px minmax(0, 1fr) auto 72px auto; padding: 10px 12px; }
  .am-dur { font-size: .8125rem; }
}

/* ==========================================================================
   Destinations strip — where the hours go once the timer stops

   Sits directly under the mockup and swaps with it (its per-context panes are .am-pane,
   so they inherit the fade above), but it is the site talking about the picture rather
   than part of the picture. So it uses the site's semantic tokens deliberately, never the
   --a-* scale, which is scoped to .app-mock and must not leak out of it.
   ========================================================================== */

.dest-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  max-width: 37.5rem;         /* the mockup's width, so the row reads as belonging to it */
  margin: 1.25rem auto 0;
}

.dest-lead {
  font-family: var(--font-mono);
  font-size: .7rem;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.dest-lead::after { content: " \2192"; }

.dest-chip {
  display: inline-flex;
  align-items: baseline;
  gap: .4rem;
  padding: .35rem .75rem;
  font-size: .8rem;
  color: var(--text-dim);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  /* background-color included: .push is a state the switcher moves between chips, so the
     fill has to ease along with the border rather than popping in under it. */
  transition: border-color var(--t-fast) var(--e-out),
              background-color var(--t-fast) var(--e-out),
              color var(--t-fast) var(--e-out);
}
.dest-chip small { font-size: .7rem; color: var(--text-faint); }

/* The active context's push target, lit by show(). Same lime alphas as .badge-ok and
   .feature-icon, so a lit chip matches every other accented surface on the page. */
.dest-chip.push {
  color: var(--accent);
  background: rgb(163 230 53 / .1);
  border-color: rgb(163 230 53 / .3);
}
.dest-chip.push small { color: var(--accent); opacity: .7; }

/* The ring isn't closed: the shipped integrations are just the plugins we happened to
   write. Dashed, because this one is an invitation rather than a fact. */
.dest-more {
  border-style: dashed;
  background: transparent;
  text-decoration: none;
}
.dest-more:hover { color: var(--accent); border-color: var(--accent); }

.dest-note {
  max-width: 37.5rem;
  margin: .75rem auto 0;
  text-align: center;
  font-size: .8rem;
  color: var(--text-faint);
  /* One line held open. Every context's note fits on one line at this width, so this is a
     floor rather than a guess: it only stops the block collapsing mid-swap, when the
     outgoing pane is already hidden and the incoming one is still fading in. */
  min-height: 1.65em;
}

/* The notes are spans, and an inline box ignores transform — so .am-swap would fade them
   without the lift every other swapping pane gets. Blockifying the visible one restores it.
   :not([hidden]) is load-bearing: a bare .dest-note .am-pane would tie with
   .am-pane[hidden] on specificity and, coming later in the file, win — showing all four. */
.dest-note .am-pane:not([hidden]) { display: block; }

/* The chips shrink but keep their qualifiers: push and export are not the same promise, and
   the row is the only place the page draws that line. Each one still fits a 360px column
   alone, so the strip just wraps to more rows. */
@media (max-width: 640px) {
  .dest-strip { gap: .4rem; }
  .dest-chip { padding: .3rem .6rem; font-size: .75rem; }
}

/* ==========================================================================
   Code blocks — the SDK section's snippets
   ========================================================================== */

.code {
  margin: 0;
  padding: 1.25rem 1.35rem;
  background: var(--n-950);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  overflow-x: auto;               /* the page body must never scroll sideways */
  font-family: var(--font-mono);
  font-size: .8125rem;
  line-height: 1.75;
  color: var(--text-dim);
  tab-size: 4;
}
.code .c { color: var(--text-faint); }   /* comment */
.code .k { color: var(--accent); }       /* keyword */
.code .t { color: var(--text); }         /* type / member */
.code .s { color: var(--warn); }         /* string */

.code-head {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .6rem 1.35rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-bottom: 0;
  border-radius: var(--r-sm) var(--r-sm) 0 0;
  font-family: var(--font-mono);
  font-size: .75rem;
  color: var(--text-faint);
}
.code-head + .code { border-radius: 0 0 var(--r-sm) var(--r-sm); }
.code-head .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--border-strong); }

/* Inline code inside prose. Scoped to text elements so it never reaches the
   <pre class="code"><code> pair above. */
p code, li code, code.chip {
  padding: .1em .4em;
  border: 1px solid var(--border);
  border-radius: .35rem;
  background: var(--n-900);
  color: var(--text);
  font-size: .875em;
}

/* --- SDK section: slot map and capability list --- */
.slotmap {
  display: grid;
  gap: .5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.slotmap li {
  display: grid;
  grid-template-columns: minmax(7.5rem, auto) 1fr;
  gap: .25rem 1rem;
  padding: .7rem .9rem;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--n-950);
  font-size: .875rem;
  color: var(--text-dim);
}
.slotmap li b {
  font-family: var(--font-mono);
  font-size: .8125rem;
  font-weight: 600;
  color: var(--accent);
}
@media (max-width: 560px) {
  .slotmap li { grid-template-columns: 1fr; }
}

/* ==========================================================================
   Ring timer — circular indeterminate wait (redeem portal)
   ========================================================================== */

.ring-timer {
  position: relative;
  width: 96px; height: 96px;
  margin: 0 auto;
}
.ring-timer svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.ring-timer .ring-bg { fill: none; stroke: var(--border); stroke-width: 4; }
.ring-timer .ring-fg {
  fill: none;
  stroke: var(--accent);
  stroke-width: 4;
  stroke-linecap: round;
  stroke-dasharray: 176;
  stroke-dashoffset: 132;
  animation: ring-spin 1.6s var(--e-in-out) infinite;
  transform-origin: 32px 32px;
}
@keyframes ring-spin {
  0%   { transform: rotate(0deg);   stroke-dashoffset: 154; }
  50%  { transform: rotate(180deg); stroke-dashoffset: 66; }
  100% { transform: rotate(360deg); stroke-dashoffset: 154; }
}
.ring-timer .ring-count {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-family: var(--font-mono);
  font-size: 1.1rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
}

/* ==========================================================================
   Odometer — digits roll into place
   ========================================================================== */

.odometer {
  display: inline-flex;
  overflow: hidden;
  line-height: 1;
  height: 1em;
}
.odo-col {
  display: block;
  width: 1ch;              /* exactly one digit of the monospace face */
  overflow: hidden;
  height: 1em;
}
.odo-col.sep { width: auto; }
.odo-strip {
  display: block;
  transform: translateY(0);
  transition: transform .9s var(--e-out);
  transition-delay: calc(var(--i, 0) * 55ms);
}
/* line-height must be exactly 1 here, or each digit drifts inside its 1em window. */
.odo-strip span { display: block; height: 1em; line-height: 1; }

/* ==========================================================================
   Easter eggs — see js/egg.js
   Nothing in here may be visible, hoverable or focusable until its egg has
   been found: no pointer cursors, no hover states on the triggers. The
   reduced-motion block at the end of the file already collapses these
   animations, so none of them are repeated there.
   ========================================================================== */

/* --- the idle prompt, asked of whoever left the tab open --- */

/* Amber, not the usual red: the session did not stop, it stopped counting for you —
   which is the same distinction the app's own idle state draws. */
.live-timer.idle .rec {
  background: var(--warn);
  animation: none;
}

.egg-idle {
  max-width: 22rem;
  margin: .75rem auto 0;
  padding: .85rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  box-shadow: var(--sh-glow);
  text-align: left;
  animation: fade-up .32s var(--e-out) backwards;
}
.egg-idle-line {
  margin: 0 0 .7rem;
  font-size: .875rem;
  color: var(--text-dim);
}
.egg-idle-line strong { color: var(--text); font-weight: 600; }
.egg-idle-actions { display: flex; gap: .5rem; }

/* --- the 404's number, mid-swap --- */

/* One burst of the same glitch the page already runs on a timer, so the code changes
   under cover instead of ticking over cleanly. steps(1) keeps it a jump cut. */
.status-code.rolling { animation: glitch-burst .26s steps(1) 1; }
@keyframes glitch-burst {
  0%        { transform: translate(-4px, 2px) skewX(6deg); opacity: .8; }
  35%       { transform: translate(5px, -2px) skewX(-5deg); opacity: .9; }
  70%       { transform: translate(-2px, 1px) skewX(2deg); }
  100%      { transform: none; opacity: 1; }
}

/* --- stencil mode --- */

/* Set on <html>, so it reaches the status pages' headings too. Headings are otherwise
   var(--font-mono); the stencil face has one weight and no italics, hence the fallback. */
.stencil h1,
.stencil h2,
.stencil .section-label,
.stencil .status-code {
  font-family: 'Saira Stencil One', var(--font-mono);
  font-weight: 400;
}

/* --- the hero clock, coming apart --- */

/* Timings here add up to the SHATTER_MS constant in egg.js; the longest run is the last
   mark's flight (.85s behind an 11 * 16ms stagger). Change one, change the other.
   Nothing in here touches .hand transform or .clock-arc stroke-dashoffset — motion.js
   writes both inline every second, and the hands must land on the true time. */

/* Two animations on disjoint properties, so they compose: the jolt is the form-invalid
   shake, borrowed as-is, and the flare only ever touches filter. */
.clock.shattered {
  animation: shake .4s var(--e-in-out), clock-flare 1.05s var(--e-out);
}
@keyframes clock-flare {
  0%   { filter: drop-shadow(0 12px 32px rgb(163 230 53 / .18)); }
  18%  { filter: drop-shadow(0 6px 44px rgb(163 230 53 / .55)); }
  100% { filter: drop-shadow(0 12px 32px rgb(163 230 53 / .18)); }
}

/* Each mark flies out along its own vector; --fx/--fy/--ox/--oy/--spin come from egg.js. */
.clock.shattered .clock-tick {
  transform-origin: var(--ox) var(--oy);
  animation: tick-shatter .85s var(--e-spring) calc(var(--i) * 16ms) both;
}
@keyframes tick-shatter {
  0%   { transform: translate(0, 0) rotate(0deg); opacity: 1; }
  26%  { transform: translate(calc(var(--fx) * 30px), calc(var(--fy) * 30px)) rotate(var(--spin)); opacity: .85; }
  52%  { transform: translate(calc(var(--fx) * 34px), calc(var(--fy) * 34px)) rotate(var(--spin)); opacity: .25; }
  100% { transform: translate(0, 0) rotate(0deg); opacity: 1; }
}

/* The group, not the hands: see the note in egg.js. */
.clock.shattered .clock-hands {
  transform-origin: 100px 100px;
  animation: hands-recoil .95s var(--e-spring);
}
@keyframes hands-recoil {
  0%   { transform: rotate(0deg) scale(1); opacity: 1; }
  20%  { transform: rotate(-14deg) scale(.86); opacity: .5; }
  55%  { transform: rotate(9deg) scale(1.06); opacity: .85; }
  100% { transform: rotate(0deg) scale(1); opacity: 1; }
}

.clock.shattered .clock-face { animation: face-crack .95s var(--e-out); }
@keyframes face-crack {
  0%   { stroke: var(--border-strong); stroke-width: 1.5; }
  15%  { stroke: var(--accent); stroke-width: 3.5; }
  100% { stroke: var(--border-strong); stroke-width: 1.5; }
}

.clock.shattered .clock-arc { animation: arc-flare .95s var(--e-out); }
@keyframes arc-flare {
  0%   { opacity: .85; stroke-width: 3; }
  20%  { opacity: .2;  stroke-width: 1.5; }
  100% { opacity: .85; stroke-width: 3; }
}

/* The shockwave off the rim. Same idea as .clock-pulse's beat, one size up. */
.clock.shattered::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 1px solid var(--accent);
  pointer-events: none;
  animation: clock-shock .9s var(--e-out);
}
@keyframes clock-shock {
  0%   { opacity: .7; transform: scale(.9); }
  100% { opacity: 0;  transform: scale(1.5); }
}

/* --- the 429's reward for actually waiting --- */

.egg-patience {
  margin: 1.25rem 0 0;
  font-size: .8125rem;
  color: var(--text-faint);
  animation: fade-up .4s var(--e-out) backwards;
}

/* ==========================================================================
   Arcade — the three discount eggs, see js/arcade.js: a timer under the
   landing page mockup, the Lawyer's sign-off at the bottom of the terms,
   and a bare code on the 404. One box, three fillings.
   Deliberately quiet: each should read as a footnote somebody left behind,
   not as a promotion. Nothing here glows until it has been won.
   ========================================================================== */

.arcade { margin: 3rem 0 1rem; }

.arcade-box {
  max-width: 26rem;
  margin-inline: auto;
  padding: 1.5rem;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  text-align: center;
}
.arcade-lead {
  margin: 0 0 1.25rem;
  font-size: .8125rem;
  line-height: 1.5;
  color: var(--text-faint);
}

/* Tabular figures, or the readout jitters as the hundredths change width. */
.arcade-readout {
  font-size: clamp(1.75rem, 7vw, 2.5rem);
  font-weight: 700;
  letter-spacing: -.03em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

.arcade-actions { margin-top: 1.25rem; }
.arcade-actions .btn { min-width: 8rem; }

/* The Lawyer's sign-off. Left-aligned inside a centred box on purpose: these are lines
   to be read, and the indent is the joke — each confirmation is filed under the one it
   confirms, so the stack visibly recurses as it grows. */
.arcade-consent { text-align: left; }
.arcade-consent-row {
  display: flex;
  gap: .6rem;
  align-items: flex-start;
  margin: .65rem 0 0 calc(var(--depth, 0) * 1.15rem);
  font-size: .8125rem;
  line-height: 1.45;
  color: var(--text-dim);
  cursor: pointer;
  animation: fade-up .35s var(--e-out) backwards;
  transition: margin-left .5s var(--e-out), opacity .4s var(--e-out), color .4s var(--e-out);
}
.arcade-consent-row:first-child { margin-top: 0; }
.arcade-consent-row input {
  flex: none;
  margin: .15rem 0 0;
  accent-color: var(--accent);
  cursor: pointer;
}
.arcade-consent-row.signed { color: var(--text-faint); }
/* Nothing is left to confirm, so the recursion unwinds and the stack reads as resolved
   rather than abandoned partway up. The last line stays lit — it is the only honest one. */
.arcade-consent.settled .arcade-consent-row { margin-left: 0; opacity: .5; }
.arcade-consent.settled .arcade-consent-row:last-child { opacity: 1; color: var(--text); }

/* Reserves its line whether or not there is anything to say, so the box does not
   jump the first time a result lands under it. */
.arcade-status {
  min-height: 1.25rem;
  margin: .9rem 0 0;
  font-size: .8125rem;
  color: var(--text-dim);
}

.arcade-reward {
  margin-top: 1.25rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
  animation: fade-up .4s var(--e-out) backwards;
}
.arcade-reward-line {
  margin: 0 0 .85rem;
  font-size: .875rem;
  color: var(--text);
}
.arcade-code-row {
  display: flex;
  gap: .5rem;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
}
.arcade-code {
  padding: .5rem .9rem;
  border: 1px dashed var(--accent);
  border-radius: var(--r-sm);
  background: var(--n-850);
  color: var(--accent);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: .08em;
}
.arcade-reward-note {
  margin: .75rem 0 0;
  font-size: .78rem;
  color: var(--text-faint);
}
.arcade-reward-actions { margin-top: 1rem; }

/* ==========================================================================
   Responsive
   ========================================================================== */

/* The band between the drawer breakpoint and a comfortable desktop. Nine links, the logo and
   the CTA need about 1060px at the full 2rem gap, so anything narrower than that squeezed the
   header - and the row only got tighter when "Try Pro" joined it. Tightening the gap, not the
   type, keeps every label at its own size and hands the band back the ~60px it was short.
   Below 860px none of this applies: the list is a vertical drawer by then. */
@media (max-width: 1080px) and (min-width: 861px) {
  .site-nav ul { gap: 1.4rem; }
}

@media (max-width: 860px) {
  .nav-toggle { display: block; }

  .site-nav {
    position: absolute;
    top: 100%; left: 0; right: 0;
    background: rgb(10 10 10 / .97);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    border-bottom: 1px solid var(--border);
    box-shadow: var(--sh-2);
    /* Collapsed by default; the open state animates the clip + fade. */
    clip-path: inset(0 0 100% 0);
    opacity: 0;
    pointer-events: none;
    /* pointer-events stops the mouse; nothing here stopped the keyboard, so a closed drawer
       still held eight links in the tab order and Tab off the logo walked straight into a menu
       that was not on screen. visibility is what takes an element out of it. Switched rather
       than interpolated, and held open for the length of the fade on the way out - the same
       pattern, and the same reason, as .am-ctx-menu above. */
    visibility: hidden;
    transition: clip-path var(--t-base) var(--e-out), opacity var(--t-fast) var(--e-out),
                visibility 0s linear var(--t-base);
  }
  .site-nav.open {
    clip-path: inset(0 0 0 0);
    opacity: 1;
    pointer-events: auto;
    visibility: visible;
    transition-delay: 0s;
  }

  .site-nav ul {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: .5rem 1.5rem 1.5rem;
  }
  .site-nav li {
    opacity: 0;
    transform: translateY(-8px);
    transition: opacity var(--t-base) var(--e-out), transform var(--t-base) var(--e-out);
  }
  .site-nav.open li { opacity: 1; transform: none; transition-delay: calc(var(--i, 0) * 55ms + 60ms); }
  .site-nav a { display: block; padding: .85rem 0; border-bottom: 1px solid var(--border); font-size: 1rem; }
  .site-nav a::after { display: none; }

  .header-cta { display: none; }
  /* ...and this takes over. It is a list item like the rest, so it inherits the drawer's own
     open/close fade and its --i stagger from initNav for free; only the border every other
     item draws under itself has to go, because this one is a button. */
  .nav-cta { display: block; }
  /* .site-nav a is the more specific selector, so it beats .btn on every property they share.
     Restate the ones that matter or this is not a button: display, because .btn-block only sets
     the width and flex is what centres the label; the padding, because a drawer row's is not a
     button's; and the colour, which is the one that actually hurt - .site-nav a paints
     --text-dim, so the label came out pale slate on lime and was almost unreadable. */
  .site-nav .nav-cta a {
    display: flex;
    padding: .8rem 1.75rem;
    border-bottom: 0;
    margin-top: .85rem;
    color: var(--n-950);
  }

  .hero { padding-top: 2.5rem; }
  .hero-grid { grid-template-columns: 1fr; gap: 2.5rem; }
  .hero h1 { font-size: clamp(2rem, 9vw, 3.75rem); }   /* full width now, so the type can grow back */
  .hero-copy { text-align: center; }
  .hero-copy .hero-sub { margin-left: auto; margin-right: auto; }
  .hero-visual { order: -1; }   /* the clock leads on narrow screens */

  .card { padding: 1.5rem; }
  .modal { padding: 1.5rem; }
  .toast-stack { left: 1rem; right: 1rem; }
  .toast { max-width: none; }
}

@media (max-width: 520px) {
  /* The field already wraps on its own flex-basis; this stops the button sitting half-width on
     the row below it, which reads as a broken layout rather than a deliberate one. */
  .waitlist .btn { width: 100%; }
  .footer-inner { justify-content: center; text-align: center; }
}

/* ==========================================================================
   Reduced motion — collapse everything, break nothing.
   Entrance animations must still land on their final state, so this sets
   near-zero durations rather than `animation: none` (which would leave
   opacity:0 elements permanently invisible).
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0ms !important;
    transition-duration: .01ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }
  html { scroll-behavior: auto; }
  .aurora, .bg-grid { animation: none; }
}

/* ==========================================================================
   Scrollbars

   :root already carries `color-scheme: dark`, which is enough for the
   browsers that draw overlay scrollbars over the content. It is not enough
   for the classic Windows scrollbar Chrome and Brave still use when overlay
   scrollbars are switched off: that one arrives 15px wide, near-white, and
   with arrow buttons on the ends, regardless of the colour scheme.

   The SDK sidebar is where it showed worst. .docs-nav is a max-height scroll
   container that overflows by roughly a hundred pixels at a normal window
   height, so a bright full-height track landed hard against the "Filter
   sections" box - which reads as the search being broken rather than as a
   scrollbar being ugly.

   scrollbar-width/-color are the standard properties and the ones modern
   Chromium honours; the ::-webkit- rules below cover the builds that predate
   them. Applied to everything rather than to .docs-nav alone, because the
   code blocks and wide tables on that same page scroll horizontally and had
   exactly the same problem.
   ========================================================================== */

* {
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
}

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background-color: var(--border-strong);
  border-radius: var(--r-pill);
  /* A transparent border plus content-box clipping insets the thumb inside
     the track, so it reads as a slim pill rather than a full-width slab. */
  border: 2px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background-color: var(--text-faint); }
::-webkit-scrollbar-corner { background: transparent; }

/* ==========================================================================
   Sticky launch-price bar

   The fourth waitlist form, and the only one still on screen once a visitor
   has read enough of the page to want it. Ships `hidden` in the markup and is
   revealed by js/sticky.js once the hero has gone; without that script it
   never appears at all, which is the right failure for a fixed element that
   would otherwise sit over the footer forever.

   `.is-in` is the animated state, not the visible one - `hidden` is what
   decides whether the element is in the layout. The reduced-motion block at
   the end of this file already collapses the transition to nothing, so there
   is no second rule for it here.
   ========================================================================== */

.sticky-cta {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  /* Below .site-header (1000) and both modal layers: this is the least
     important thing on the page and must never sit over one of them. */
  z-index: 60;
  /* Same shape as .site-header's translucent surface, one step more opaque
     because a form sits on it. --n-900 is #171717. */
  background-color: rgb(23 23 23 / .92);
  backdrop-filter: blur(12px) saturate(160%);
  -webkit-backdrop-filter: blur(12px) saturate(160%);
  border-top: 1px solid var(--border);
  box-shadow: var(--sh-3);
  /* The off state. Kept off `opacity` alone so a mid-transition bar cannot
     swallow clicks meant for the page behind it. */
  transform: translateY(110%);
  transition: transform var(--t-base) var(--e-out);
}

.sticky-cta.is-in { transform: translateY(0); }

.sticky-cta-inner {
  position: relative;   /* the containing block for .form-error below */
  display: flex;
  align-items: center;
  gap: 1rem;
  padding-top: .75rem;
  padding-bottom: .75rem;
}

.sticky-cta-copy {
  margin: 0;
  font-size: .9rem;
  color: var(--text-dim);
  flex: 1 1 auto;
  min-width: 0;
}

.sticky-cta-copy strong { color: var(--text); }

/* The form is .waitlist, which is already a row of input + button everywhere
   else on the site; this only stops it stretching to fill the bar. */
.sticky-cta form.waitlist {
  flex: 0 1 26rem;
  margin: 0;
}

/* The error line would otherwise push the bar's own height around as it
   appears. Out of flow, above the bar, where there is room for it. */
.sticky-cta .form-error {
  position: absolute;
  bottom: 100%;
  right: 0;
  margin: 0 0 .5rem;
  padding: .4rem .6rem;
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: .5rem;
}

.sticky-cta-close {
  flex: 0 0 auto;
  background: none;
  border: 0;
  color: var(--text-faint);
  font-size: 1.35rem;
  line-height: 1;
  padding: .25rem .4rem;
  cursor: pointer;
  border-radius: .4rem;
}

.sticky-cta-close:hover { color: var(--text); background: var(--surface-2); }
.sticky-cta-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Written by sticky.js only while the bar is up, so the footer's last line is
   never underneath it. */
body.has-sticky-cta { padding-bottom: 5.5rem; }

@media (max-width: 860px) {
  .sticky-cta-inner {
    flex-wrap: wrap;
    gap: .5rem 1rem;
  }
  /* The sentence and the close button share the first row, the form takes the
     second across the full width - so the input never shrinks to a stub.

     The order matters and is not the DOM order. The close button comes last in
     the markup, where it belongs for a keyboard and a screen reader, but a
     full-width form in front of it pushed it onto a third row of its own and
     stood the bar up at 140px. `order` moves it back beside the sentence
     without moving it in the document. */
  .sticky-cta-close { order: 1; }
  .sticky-cta form.waitlist { order: 2; flex: 1 0 100%; margin: 0; }
  /* ...and inside that row the field and its button stay on one line. The
     global basis is `.waitlist .input { flex: 1 1 14rem }`, and 14rem does not
     fit beside a "Lock it in" button at 390px - so the two wrapped onto
     separate rows and the bar stood nearly 200px tall, close to a quarter of
     a phone screen. An 8rem basis fits both, and the field still takes every
     pixel the button does not. */
  .sticky-cta form.waitlist .input { flex: 1 1 8rem; }
  .sticky-cta-copy { font-size: .82rem; }
  /* The offer keeps its figures and loses its tail. The full sentence is wider
     than the 342px a 390px phone leaves, so it claimed the whole first row on
     its own and bounced the close button down to a row of its own - three rows
     and 140px of screen. Without the tail the row is the figures plus the
     close button, and the bar is two rows and about 100px. The promise the
     tail makes is on the Pro card in full, one tap away. */
  .sticky-cta-tail { display: none; }
  /* Clears the two-row bar. Keep this in step with its real height: too small
     and the footer's last line hides under it at the bottom of the page. */
  body.has-sticky-cta { padding-bottom: 7rem; }
}
