/**
 * Keyvaci marketing site design system.
 *
 * Same "security printing" language as the product (ink, guilloché, patina
 * green for action, countersignature gold for verified cryptography), but
 * printed on light paper instead of ink-blue. The product's dark UI appears
 * inside browser frames as the artefact being shown, so the two palettes
 * meet on every screen: light paper around, dark vault inside.
 */

:root {
  /* ---- paper: the light ground, tinted cold like a vault interior ---- */
  --paper: #f3f6fa;
  --paper-2: #eaeff6;
  --paper-3: #e6ecf7;         /* patina-leaning band, for the third surface */
  --paper-4: #dde5f1;         /* deepest paper band, under the client row */
  --card: #ffffff;
  --line: #dfe5ef;
  --line-strong: #c6d0e0;

  /* ---- ink text on paper ---- */
  --ink: #0f1421;
  --ink-soft: #3a4763;
  --ink-dim: #5d6b88;
  --ink-faint: #8b97b0;

  /* ---- the product's own dark surfaces, for embedded app frames ---- */
  --app-ink-900: #080b14;
  --app-ink-850: #0b0f1a;
  --app-ink-800: #0f1421;
  --app-ink-700: #151b2b;
  --app-ink-600: #1c2437;
  --app-line: #222b40;
  --app-line-strong: #33405d;
  --app-text: #e8ecf4;
  --app-text-dim: #a4b0c6;
  --app-text-faint: #6d7c96;

  /* ---- patina: interaction ---- */
  --patina: #178a67;          /* deep enough for AA text on paper */
  --patina-strong: #0f6b4f;
  --patina-bright: #35c39a;   /* graphics and the dark frames only */
  --patina-ink: #04241a;
  --patina-wash: rgba(53, 195, 154, 0.12);
  --patina-edge: rgba(23, 138, 103, 0.35);

  /* ---- countersignature gold: verified cryptography, nothing else ---- */
  --seal: #9a6f1c;            /* text-safe on paper */
  --seal-bright: #d9a441;     /* graphics */
  --seal-wash: rgba(217, 164, 65, 0.14);
  --seal-edge: rgba(154, 111, 28, 0.35);

  --danger: #b4442f;

  /* ---- geometry ---- */
  --r-sm: 8px;
  --r: 12px;
  --r-lg: 18px;
  --r-xl: 26px;
  --r-full: 999px;

  --shadow-1: 0 1px 2px rgba(15, 20, 33, 0.06);
  --shadow-2: 0 6px 24px rgba(15, 20, 33, 0.08);
  --shadow-3: 0 24px 64px rgba(15, 20, 33, 0.14);

  --w: 1120px;
  --topbar-h: 68px;

  /**
   * Be Vietnam Pro ships here with the latin and vietnamese subsets only and
   * has no CJK glyphs, exactly as in the product. Japanese, Korean and Chinese
   * therefore fall through to the platform's own CJK face, named explicitly so
   * the fallback is a chosen font rather than whatever the browser reaches for
   * last. Bundling CJK webfonts would cost megabytes on a marketing page to
   * replace system fonts that are already well made.
   */
  --font-ui: 'Be Vietnam Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI',
    'Hiragino Sans', 'Yu Gothic UI', 'Noto Sans JP',
    'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans KR',
    'PingFang SC', 'Microsoft YaHei', 'Noto Sans SC',
    Roboto, sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo,
    'Hiragino Sans', 'Malgun Gothic', 'PingFang SC', monospace;

  font-family: var(--font-ui);
  color-scheme: light;
}

/**
 * Dark theme: the vault interior. The same ink the product itself is printed
 * on, so switching the site to dark makes marketing and app one continuous
 * surface. Toggled by setting data-theme="dark" on <html> (site.js); every
 * component reads tokens, so this block is the entire theme.
 */
html[data-theme="dark"] {
  --paper: #080b14;
  --paper-2: #0d1220;
  --paper-3: #0a1a1c;         /* patina-leaning band, for the third surface */
  --paper-4: #11172a;         /* deepest paper band, under the client row */
  --card: #0f1421;
  --line: #222b40;
  --line-strong: #33405d;

  --ink: #e8ecf4;
  --ink-soft: #a9b4ca;
  --ink-dim: #93a1bc;
  --ink-faint: #6d7c96;

  --patina: #35c39a;
  --patina-strong: #4bdcb1;
  --patina-wash: rgba(53, 195, 154, 0.10);
  --patina-edge: rgba(53, 195, 154, 0.35);

  --seal: #d9a441;
  --seal-wash: rgba(217, 164, 65, 0.12);
  --seal-edge: rgba(217, 164, 65, 0.38);

  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.45);
  --shadow-2: 0 6px 24px rgba(0, 0, 0, 0.4);
  --shadow-3: 0 24px 64px rgba(0, 0, 0, 0.55);

  color-scheme: dark;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }
html { scroll-behavior: smooth; }
html, body { margin: 0; }

body {
  background: var(--paper);
  color: var(--ink-soft);
  font-size: 16px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  position: relative;
}

/* Cipher watermark: faint fragments of key material drifting behind the
   whole site, at the threshold of visibility. The paper should feel like
   it has already been written on, in a language you can almost read. */
body::before {
  content: '';
  position: fixed; inset: 0; z-index: -2; pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='260'%3E%3Cg fill='%2310192b' fill-opacity='0.030' font-family='IBM Plex Mono,monospace' font-size='11'%3E%3Ctext x='16' y='34'%3E0x2F5A%3C/text%3E%3Ctext x='168' y='78'%3E%E2%96%9A%E2%96%9A%3C/text%3E%3Ctext x='58' y='138'%3Eed25519%3C/text%3E%3Ctext x='196' y='176'%3E::%3C/text%3E%3Ctext x='24' y='224'%3E%C2%A7 44 61%3C/text%3E%3Ctext x='140' y='240'%3E0x91%3C/text%3E%3C/g%3E%3C/svg%3E");
}
html[data-theme="dark"] body::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='260'%3E%3Cg fill='%2335c39a' fill-opacity='0.045' font-family='IBM Plex Mono,monospace' font-size='11'%3E%3Ctext x='16' y='34'%3E0x2F5A%3C/text%3E%3Ctext x='168' y='78'%3E%E2%96%9A%E2%96%9A%3C/text%3E%3Ctext x='58' y='138'%3Eed25519%3C/text%3E%3Ctext x='196' y='176'%3E::%3C/text%3E%3Ctext x='24' y='224'%3E%C2%A7 44 61%3C/text%3E%3Ctext x='140' y='240'%3E0x91%3C/text%3E%3C/g%3E%3C/svg%3E");
}

h1, h2, h3, h4 { color: var(--ink); line-height: 1.18; margin: 0 0 0.5em; letter-spacing: -0.015em; }
h1 { font-size: clamp(2.1rem, 5vw, 3.4rem); font-weight: 700; }
h2 { font-size: clamp(1.6rem, 3.4vw, 2.3rem); font-weight: 700; }
h3 { font-size: 1.2rem; font-weight: 600; }
p { margin: 0 0 1em; }
a { color: var(--patina); text-decoration: none; }
a:hover { text-decoration: underline; }
img, svg { max-width: 100%; height: auto; }
strong { color: var(--ink); }

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

.wrap { max-width: var(--w); margin: 0 auto; padding: 0 24px; }
.wrap-narrow { max-width: 780px; margin: 0 auto; padding: 0 24px; }

/* ------------------------------------------------------------- guilloché */
/* The interlaced line-work of secured documents, reduced to two rotated
   grids of hairlines. Light version: it should read as watermark. */
.engraved { position: relative; isolation: isolate; }
.engraved::before {
  content: '';
  position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background:
    repeating-linear-gradient(63deg,  rgba(23, 138, 103, 0.05) 0 1px, transparent 1px 9px),
    repeating-linear-gradient(-63deg, rgba(23, 138, 103, 0.05) 0 1px, transparent 1px 9px);
  -webkit-mask-image: radial-gradient(120% 90% at 50% 0%, #000 30%, transparent 78%);
          mask-image: radial-gradient(120% 90% at 50% 0%, #000 30%, transparent 78%);
}
html[data-theme="dark"] .engraved::before {
  background:
    repeating-linear-gradient(63deg,  rgba(53, 195, 154, 0.07) 0 1px, transparent 1px 9px),
    repeating-linear-gradient(-63deg, rgba(53, 195, 154, 0.07) 0 1px, transparent 1px 9px);
}

.eyebrow {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--patina);
  margin: 0 0 14px;
}
/* Terminal-style delimiters: eyebrows read like protocol tags. */
.eyebrow::before { content: '[ '; color: var(--seal); }
.eyebrow::after  { content: ' ]'; color: var(--seal); }

.lede { font-size: 1.15rem; color: var(--ink-dim); max-width: 46em; }

/* ---------------------------------------------------------------- topbar */
.topbar {
  position: sticky; top: 0; z-index: 50;
  height: var(--topbar-h);
  background: rgba(246, 248, 251, 0.86);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line);
}
html[data-theme="dark"] .topbar { background: rgba(8, 11, 20, 0.82); }
.topbar-inner {
  max-width: var(--w); margin: 0 auto; padding: 0 24px;
  height: 100%;
  display: flex; align-items: center; gap: 28px;
}
.brand { display: flex; align-items: center; gap: 10px; color: var(--ink); font-weight: 700; font-size: 1.12rem; letter-spacing: -0.01em; }
.brand:hover { text-decoration: none; }
.brand img { width: 30px; height: 30px; }

.nav { display: flex; gap: 4px; margin-left: 8px; }
.nav a {
  color: var(--ink-soft); font-size: 0.94rem; font-weight: 500;
  padding: 8px 12px; border-radius: var(--r-sm);
  /* CJK has no spaces, so without this the browser breaks inside a label
     and the bar grows to two lines somewhere around 960px. */
  white-space: nowrap;
}
.nav a:hover { background: var(--paper-2); color: var(--ink); text-decoration: none; }
.nav a[aria-current="page"] { color: var(--patina); }
/* Copies of the top-bar buttons, shown only inside the mobile menu. */
.nav .nav-cta { display: none; }

.topbar-actions { margin-left: auto; display: flex; align-items: center; gap: 10px; }

.nav-toggle { display: none; margin-left: auto; background: none; border: 1px solid var(--line); border-radius: var(--r-sm); padding: 8px 10px; cursor: pointer; color: var(--ink); }

/* Language picker: five static builds, chosen from the top right of every page. */
.lang-picker { position: relative; flex: none; }
.lang-btn {
  display: inline-flex; align-items: center; gap: 7px;
  height: 38px; padding: 0 13px;
  background: var(--card);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-full);
  color: var(--ink-soft);
  font: 600 0.82rem/1 var(--font-mono);
  letter-spacing: 0.04em;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.lang-btn:hover { color: var(--patina); border-color: var(--patina-edge); }
.lang-btn svg { width: 15px; height: 15px; stroke: currentColor; }
.lang-menu {
  position: absolute; right: 0; top: calc(100% + 8px);
  min-width: 178px;
  background: var(--card);
  border: 1px solid var(--line-strong);
  border-radius: var(--r);
  box-shadow: var(--shadow-2);
  padding: 6px;
  display: none;
  z-index: 60;
}
.lang-menu.open { display: block; }
.lang-menu a {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 9px 12px; border-radius: var(--r-sm);
  color: var(--ink-soft); font-size: 0.93rem;
}
.lang-menu a:hover { background: var(--paper-2); color: var(--ink); text-decoration: none; }
.lang-menu a[aria-current="true"] { color: var(--patina); }
.lang-menu .code { font-family: var(--font-mono); font-size: 0.72rem; color: var(--ink-faint); }

/* Theme switch: the last item on the bar. Sun in the dark, moon in the light. */
.theme-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; flex: none;
  background: var(--card);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-full);
  cursor: pointer;
  color: var(--ink-soft);
  transition: color 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}
.theme-toggle:hover { color: var(--patina); border-color: var(--patina-edge); transform: rotate(15deg); }
.theme-toggle svg { width: 17px; height: 17px; stroke: currentColor; }

/* --------------------------------------------------------------- buttons */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font: 600 0.97rem/1 var(--font-ui);
  padding: 13px 22px;
  border-radius: var(--r-sm);
  border: 1px solid transparent;
  cursor: pointer; white-space: nowrap;
  transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.12s ease;
}
.btn:hover { text-decoration: none; transform: translateY(-1px); }
.btn-primary { background: var(--patina); color: #fff; box-shadow: 0 6px 18px rgba(23, 138, 103, 0.28); }
.btn-primary:hover { background: var(--patina-strong); }
html[data-theme="dark"] .btn-primary { color: var(--patina-ink); box-shadow: 0 6px 18px rgba(53, 195, 154, 0.18); }
html[data-theme="dark"] .price-card .plan-flag { color: var(--patina-ink); }
.btn-ghost { background: var(--card); color: var(--ink); border-color: var(--line-strong); }
.btn-ghost:hover { border-color: var(--patina-edge); color: var(--patina); }
.btn-lg { padding: 16px 28px; font-size: 1.05rem; border-radius: var(--r); }
.btn-sm { padding: 9px 16px; font-size: 0.9rem; }

/* -------------------------------------------------------------- sections */
.section { padding: 96px 0; }
.section-tight { padding: 64px 0; }
.section-alt { background: var(--paper-2); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
/**
 * Two further bands beyond the plain/alt pair. With only two surfaces the home
 * page read as one long sheet: every boundary was the same 4% step, so nothing
 * marked where an argument ended and the next began. These give the page four
 * distinct grounds while keeping every neighbouring pair different.
 */
.section-tint { background: var(--paper-3); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
/**
 * The client band is dark in BOTH themes, not just the dark one. The four marks
 * are one artwork each, and three of them carry near-white detail that would be
 * erased on a light ground; a client's trademark is not the thing to make
 * conditional on a theme toggle. Re-declaring the ink tokens here rather than
 * hand-colouring the contents means every component inside, headings, lede,
 * eyebrow, keeps working with no component-level exceptions.
 */
.section-deep {
  --paper: #080b14;
  --paper-2: #0d1220;
  --card: #0f1421;
  --line: #222b40;
  --line-strong: #33405d;
  --ink: #e8ecf4;
  --ink-soft: #a9b4ca;
  --ink-dim: #93a1bc;
  --ink-faint: #6d7c96;
  --patina: #35c39a;
  --patina-wash: rgba(53, 195, 154, 0.10);
  --patina-edge: rgba(53, 195, 154, 0.35);
  background: #0a0f1e;
  border-top: 1px solid #1b2338;
  border-bottom: 1px solid #1b2338;
  color: var(--ink-soft);
}
.section-head { max-width: 720px; margin-bottom: 48px; }
.center { text-align: center; }
/**
 * The centring class is used both ways: on a wrapper around the heading block
 * and, more often, on the heading block itself. Only matching the descendant
 * case left the second one centring its text inside a 720px box that was
 * still flush left, which reads as a heading nudged off to one side.
 */
.center .section-head,
.section-head.center { margin-left: auto; margin-right: auto; }
/* Same trap one level down: .lede caps at 46em, so it needs the auto margins
   too or it hugs the left edge of an otherwise centred block. */
.center .lede,
.section-head.center .lede { margin-left: auto; margin-right: auto; }

/* ------------------------------------------------------------------ hero */
.hero { padding: 84px 0 72px; overflow: hidden; }
/* The two columns are balanced by making them span the same rows and pinning
   the last thing in each to the bottom, rather than by centring them against
   each other. Centring was right when the right column held only the
   simulation and was 401px shorter than the left; with the cryptography line
   moved under the simulation the gap is 127px, and pinning closes it to 0:
   both columns now start at the same line and end at the same line. */
.hero-grid { display: grid; grid-template-columns: minmax(0, 46fr) minmax(0, 54fr); gap: 56px; align-items: stretch; }
/* The eyebrow is a small line of monospace above the h1. Aligning the frame to
   the h1 rather than to the eyebrow is what stops the panel reading a notch too
   high; 38px is the eyebrow's own height plus its margin. */
.hero-grid > :last-child { padding-top: 38px; display: flex; flex-direction: column; }
/* `margin-top: auto` takes whatever height the left column has spare, so the
   cryptography line lands on the same baseline as the trial note opposite it.
   With no spare height auto resolves to 0, which is why the minimum gap is
   padding rather than margin. The rule above it is dropped because the caption
   already separates it from the frame, and it is centred because under the
   panel it reads as part of it. */
.hero-grid .trustline { justify-content: center; border-top: none;
                        margin-top: auto; padding-top: 22px; }
/* The hero h1 sits in a 46fr column next to the demo panel, so at 1280px it
   has only ~467px of measure for a 54px face — about nine characters a line.
   Without balancing, the first sentence dropped its last word onto a line of
   its own ("bạn." alone in Vietnamese, "secrets." in English). `balance` evens
   the lines out instead; it changes no line COUNT, only where the breaks fall,
   and browsers that do not support it simply wrap as before. */
.hero h1 { text-wrap: balance; }
.hero h1 .accent { color: var(--patina); }
.hero .lede { margin: 18px 0 30px; }
.hero-ctas { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 26px; }
.hero-note { font-size: 0.9rem; color: var(--ink-faint); display: flex; align-items: center; gap: 8px; }
.hero-note .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--patina-bright); box-shadow: 0 0 0 4px var(--patina-wash); }

.trustline { display: flex; flex-wrap: wrap; gap: 10px 22px; margin-top: 34px; padding-top: 22px; border-top: 1px solid var(--line); }
.trustline span { font-family: var(--font-mono); font-size: 0.8rem; color: var(--ink-dim); display: inline-flex; align-items: center; gap: 7px; }
.trustline svg { width: 14px; height: 14px; stroke: var(--patina); }

/* --------------------------------------------------- browser frame mockup */
.frame {
  background: var(--app-ink-850);
  border: 1px solid var(--app-line-strong);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-3);
  overflow: hidden;
  color: var(--app-text);
  font-size: 13px;
}
.frame-bar {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 14px;
  background: var(--app-ink-800);
  border-bottom: 1px solid var(--app-line);
}
.frame-bar .lights { display: flex; gap: 6px; }
.frame-bar .lights i { width: 10px; height: 10px; border-radius: 50%; background: var(--app-ink-600); display: block; }
.frame-bar .url {
  flex: 1; text-align: center;
  font: 400 11px/1 var(--font-mono);
  color: var(--app-text-faint);
  background: var(--app-ink-700);
  border: 1px solid var(--app-line);
  border-radius: var(--r-full);
  padding: 6px 12px;
  max-width: 340px; margin: 0 auto;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.frame-bar .url .lock { color: var(--patina-bright); }

/* --------------------------------------------------- hero: the sealed path */
/**
 * The one picture the product needs to land: a secret is written on your
 * machine, sealed there, crosses the wire already unreadable, and rests on the
 * server as ciphertext nobody can open. The return trip decrypts only after it
 * is back on your device.
 *
 * Everything is drawn with type and hairlines rather than illustration, so it
 * reads as instrumentation rather than decoration, and so it stays sharp at
 * any size without shipping an image.
 */
.flow-head {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 16px;
  background: var(--app-ink-800);
  border-bottom: 1px solid var(--app-line);
  font: 500 10.5px/1 var(--font-mono);
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--app-text-faint);
}
.flow-head .node { display: inline-flex; align-items: center; gap: 7px; white-space: nowrap; transition: color 0.3s ease; }
.flow-head .node i {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--app-line-strong);
  transition: background 0.3s ease, box-shadow 0.3s ease;
}
.flow-head .node.live { color: var(--patina-bright); }
.flow-head .node.live i { background: var(--patina-bright); box-shadow: 0 0 0 4px var(--patina-wash); }
.flow-head .rail {
  flex: 1; height: 1px; position: relative; overflow: hidden;
  background: var(--app-line-strong);
}
.flow-head .rail::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, var(--patina-bright), transparent);
  transform: translateX(-100%);
}
.flow-head.tx .rail::after { animation: rail-run 1.1s linear infinite; }
.flow-head.rx .rail::after { animation: rail-run-back 1.1s linear infinite; }
@keyframes rail-run { to { transform: translateX(100%); } }
@keyframes rail-run-back { from { transform: translateX(100%); } to { transform: translateX(-100%); } }

.flow {
  position: relative;
  padding: 18px 18px 16px;
  background: var(--app-ink-850);
  display: flex; flex-direction: column; gap: 0;
  min-height: 430px;
}

/* ---- the two ends ---- */
.fcard {
  position: relative;
  border: 1px solid var(--app-line);
  background: var(--app-ink-800);
  border-radius: 12px;
  padding: 13px 14px;
  transition: border-color 0.35s ease, box-shadow 0.35s ease;
}
.fcard.active { border-color: var(--patina-edge); box-shadow: 0 0 0 1px var(--patina-wash), 0 8px 30px rgba(53, 195, 154, 0.10); }
.fcard.sealed { border-color: var(--seal-edge); box-shadow: 0 0 0 1px var(--seal-wash); }
.fcard-h {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  font: 500 10px/1 var(--font-mono);
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--app-text-faint);
  margin-bottom: 11px;
}
.fcard-h .who { display: inline-flex; align-items: center; gap: 7px; color: var(--app-text-dim); }
.fcard-h .who svg { width: 13px; height: 13px; stroke: currentColor; fill: none; stroke-width: 2; }
.fcard.active .fcard-h .who { color: var(--patina-bright); }
.fcard.sealed .fcard-h .who { color: var(--seal-bright); }

.frow { display: flex; align-items: baseline; gap: 10px; margin-bottom: 7px; }
.frow .k {
  flex: none; width: 52px;
  font: 400 10px/1.6 var(--font-mono);
  color: var(--app-text-faint);
  letter-spacing: 0.06em;
}
.frow .v {
  font: 400 12.5px/1.6 var(--font-mono);
  color: var(--app-text);
  word-break: break-all;
}
.frow .v.plain { color: var(--patina-bright); }
.frow .v.cipher { color: var(--app-text-faint); letter-spacing: 0.02em; }
.caret {
  display: inline-block; width: 7px; height: 13px;
  background: var(--patina-bright); vertical-align: -2px; margin-left: 1px;
  animation: caret-blink 0.9s steps(1) infinite;
}
@keyframes caret-blink { 50% { opacity: 0; } }

/* The key-derivation line: dim until the moment it does its work. */
.kdf {
  margin-top: 11px; padding-top: 10px;
  border-top: 1px dashed var(--app-line);
  font: 400 10.5px/1.5 var(--font-mono);
  color: var(--app-text-faint);
  display: flex; align-items: center; gap: 8px;
  transition: color 0.3s ease;
}
.kdf .keyglyph {
  width: 15px; height: 15px; flex: none;
  stroke: var(--app-line-strong); fill: none; stroke-width: 1.6;
  transition: stroke 0.3s ease, transform 0.5s cubic-bezier(0.2, 0.9, 0.3, 1.3);
}
.fcard.active .kdf { color: var(--patina); }
.fcard.active .kdf .keyglyph { stroke: var(--patina-bright); transform: rotate(-90deg); }

/* ---- the wire between them ---- */
.channel {
  position: relative;
  flex: 1; min-height: 92px;
  margin: 0 auto;
  width: 100%;
  display: flex; align-items: center; justify-content: center;
}
.channel::before {
  content: ''; position: absolute; top: 0; bottom: 0; left: 50%;
  width: 1px; margin-left: -0.5px;
  background: repeating-linear-gradient(180deg, var(--app-line-strong) 0 5px, transparent 5px 11px);
}
.channel .tag {
  position: relative; z-index: 2;
  background: var(--app-ink-850);
  padding: 5px 11px;
  border: 1px solid var(--app-line);
  border-radius: var(--r-full);
  font: 400 10px/1 var(--font-mono);
  letter-spacing: 0.08em;
  color: var(--app-text-faint);
  white-space: nowrap;
  transition: color 0.3s ease, border-color 0.3s ease;
}
.channel.moving .tag { color: var(--patina-bright); border-color: var(--patina-edge); }

/* The packet: a scrap of ciphertext travelling the wire. */
.packet {
  position: absolute; z-index: 3; left: 50%;
  transform: translate(-50%, 0);
  padding: 4px 9px;
  border-radius: 6px;
  background: var(--app-ink-700);
  border: 1px solid var(--patina-edge);
  box-shadow: 0 0 22px rgba(53, 195, 154, 0.22);
  font: 400 10px/1 var(--font-mono);
  color: var(--patina-bright);
  white-space: nowrap;
  opacity: 0;
}
.packet.down { animation: packet-down 1.15s cubic-bezier(0.5, 0, 0.5, 1) forwards; }
.packet.up { animation: packet-up 1.15s cubic-bezier(0.5, 0, 0.5, 1) forwards; }
@keyframes packet-down {
  0% { opacity: 0; top: 0; }
  12% { opacity: 1; }
  88% { opacity: 1; }
  100% { opacity: 0; top: 100%; }
}
@keyframes packet-up {
  0% { opacity: 0; top: 100%; }
  12% { opacity: 1; }
  88% { opacity: 1; }
  100% { opacity: 0; top: 0; }
}

/* ---- the stored block on the server ---- */
.blob {
  font: 400 11px/1.65 var(--font-mono);
  color: var(--app-text-faint);
  word-break: break-all;
  letter-spacing: 0.02em;
  min-height: 3.3em;
}
.fcard.sealed .blob { color: var(--app-text-dim); }
.seal-note {
  margin-top: 11px; padding-top: 10px;
  border-top: 1px dashed var(--app-line);
  display: flex; align-items: center; gap: 8px;
  font: 500 10.5px/1.4 var(--font-mono);
  color: var(--app-text-faint);
  transition: color 0.3s ease;
}
.seal-note svg { width: 14px; height: 14px; flex: none; stroke: currentColor; fill: none; stroke-width: 2; }
.fcard.sealed .seal-note { color: var(--seal-bright); }

/* The sweep that reads as "this just got sealed". */
.fcard .sweep {
  position: absolute; inset: 0; border-radius: 12px;
  pointer-events: none; overflow: hidden; opacity: 0;
}
.fcard .sweep::after {
  content: ''; position: absolute; inset: -40% 0;
  background: linear-gradient(180deg, transparent, rgba(53, 195, 154, 0.16), transparent);
  transform: translateY(-100%);
}
.fcard.flash .sweep { opacity: 1; }
.fcard.flash .sweep::after { animation: sweep-run 0.75s ease-out; }
@keyframes sweep-run { to { transform: translateY(100%); } }

.demo-caption {
  position: absolute; left: 0; right: 0; bottom: -46px;
  text-align: center;
  font-family: var(--font-mono); font-size: 0.8rem; color: var(--ink-dim);
}
.demo-caption .step { color: var(--patina); }

@media (max-width: 980px) {
  .demo-caption { position: static; margin-top: 12px; }
}
@media (max-width: 760px) {
  .flow { min-height: 400px; padding: 14px; }
  .flow-head { font-size: 9.5px; letter-spacing: 0.08em; }
  .frow .k { width: 44px; }
  .frow .v { font-size: 11.5px; }
}

/* -------------------------------------------------------- encryption demo */
.split-panels { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; align-items: stretch; }
.panel {
  border-radius: var(--r-lg);
  padding: 26px;
  border: 1px solid var(--line);
  background: var(--card);
  box-shadow: var(--shadow-1);
  position: relative;
}
.panel .panel-tag {
  font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.12em;
  text-transform: uppercase; margin-bottom: 14px;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 11px; border-radius: var(--r-full);
}
.panel-you .panel-tag { background: var(--patina-wash); color: var(--patina); }
.panel-server { background: var(--app-ink-850); border-color: var(--app-line-strong); color: var(--app-text-dim); }
.panel-server .panel-tag { background: rgba(217, 164, 65, 0.12); color: var(--seal-bright); }
.panel-server h3 { color: var(--app-text); }
.kv { font-family: var(--font-mono); font-size: 0.82rem; line-height: 1.9; }
.kv b { font-weight: 500; }
.panel-you .kv b { color: var(--ink); }
.panel-server .kv { word-break: break-all; }
.panel-server .kv b { color: var(--app-text-faint); font-weight: 400; }
.panel-server .kv .c { color: var(--app-text-dim); }
.arrow-join {
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-mono); font-size: 0.78rem; color: var(--ink-dim);
  gap: 10px; margin: 0; text-align: center;
}

/* ------------------------------------------------------------ pillar grid */
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; }
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 22px; }
.pillar {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 28px;
  box-shadow: var(--shadow-1);
  position: relative;
  transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}
.pillar:hover { transform: translateY(-3px); box-shadow: var(--shadow-2); border-color: var(--patina-edge); }

/* Registration marks: the crosshair corners of a classified dossier.
   Barely-there at rest; they lock in when the card is examined. */
.pillar::after, .step-card::after, .panel::after {
  content: '';
  position: absolute; inset: 9px; pointer-events: none;
  opacity: 0.35;
  transition: opacity 0.18s ease;
  background:
    linear-gradient(var(--patina), var(--patina)) left 0 top 0 / 11px 1px,
    linear-gradient(var(--patina), var(--patina)) left 0 top 0 / 1px 11px,
    linear-gradient(var(--patina), var(--patina)) right 0 top 0 / 11px 1px,
    linear-gradient(var(--patina), var(--patina)) right 0 top 0 / 1px 11px,
    linear-gradient(var(--patina), var(--patina)) left 0 bottom 0 / 11px 1px,
    linear-gradient(var(--patina), var(--patina)) left 0 bottom 0 / 1px 11px,
    linear-gradient(var(--patina), var(--patina)) right 0 bottom 0 / 11px 1px,
    linear-gradient(var(--patina), var(--patina)) right 0 bottom 0 / 1px 11px;
  background-repeat: no-repeat;
}
.pillar:hover::after { opacity: 1; }
.panel-server::after {
  background:
    linear-gradient(var(--seal-bright), var(--seal-bright)) left 0 top 0 / 11px 1px,
    linear-gradient(var(--seal-bright), var(--seal-bright)) left 0 top 0 / 1px 11px,
    linear-gradient(var(--seal-bright), var(--seal-bright)) right 0 top 0 / 11px 1px,
    linear-gradient(var(--seal-bright), var(--seal-bright)) right 0 top 0 / 1px 11px,
    linear-gradient(var(--seal-bright), var(--seal-bright)) left 0 bottom 0 / 11px 1px,
    linear-gradient(var(--seal-bright), var(--seal-bright)) left 0 bottom 0 / 1px 11px,
    linear-gradient(var(--seal-bright), var(--seal-bright)) right 0 bottom 0 / 11px 1px,
    linear-gradient(var(--seal-bright), var(--seal-bright)) right 0 bottom 0 / 1px 11px;
  background-repeat: no-repeat;
}
.pillar .picon {
  width: 44px; height: 44px; border-radius: 12px;
  background: var(--patina-wash);
  display: flex; align-items: center; justify-content: center;
  margin-bottom: 18px;
}
.pillar .picon svg { width: 21px; height: 21px; stroke: var(--patina); }
.pillar.gold .picon { background: var(--seal-wash); }
.pillar.gold .picon svg { stroke: var(--seal); }
.pillar h3 { margin-bottom: 8px; }
.pillar p { margin: 0; font-size: 0.95rem; color: var(--ink-dim); }
/* The single address the whole site routes to. It was three stacked copies of
   itself under three labels, which read as a copy-paste slip rather than as a
   deliberate one-inbox policy. Now it is one line, set in mono at the size of
   something you are meant to act on, with the routing moved into the sentence
   under it. */
.pillar .addr { font-family: var(--font-mono); font-size: 1.02rem; margin-bottom: 10px; }

/* The founder hero: the portrait and the copy side by side.
   The picture leads on wide screens because a face is the fastest way to answer
   "who is asking me to trust this", and it drops BELOW the copy on narrow ones,
   where a 775px-tall image above the heading would push the whole argument off
   the first screen. */
.founder-hero { display: grid; grid-template-columns: minmax(0, 300px) minmax(0, 1fr);
                gap: 44px; align-items: center; text-align: left; }
.founder-shot { margin: 0; }
.founder-shot img { display: block; width: 100%; height: auto; border-radius: 16px;
                    border: 1px solid var(--line);
                    /* The photo was keyed onto this exact tone, so the frame and
                       the picture share an edge instead of showing a seam. */
                    background: #111622; box-shadow: var(--shadow-2); }
.founder-shot figcaption { margin-top: 14px; }
.founder-shot figcaption strong { display: block; color: var(--ink); font-size: 1.02rem; }
.founder-shot figcaption span { display: block; margin-top: 3px; font-family: var(--font-mono);
                                font-size: 0.78rem; color: var(--ink-faint); }
.founder-intro .lede { margin-left: 0; margin-right: 0; }

@media (max-width: 880px) {
  .founder-hero { grid-template-columns: 1fr; gap: 28px; }
  /* Copy first, portrait second: the heading has to be the first thing read. */
  .founder-shot { order: 2; max-width: 260px; }
  .founder-intro { order: 1; }
}

/* The contact form's answer. Three states, and the failure one has to be as
   readable as the success one: it is the state that carries the way out. */
.cf-result { margin-top: 18px; padding: 16px 18px; border-radius: var(--radius-2, 12px);
             border: 1px solid var(--line); background: var(--surface-2, rgba(255,255,255,0.02));
             font-size: 0.95rem; color: var(--ink-dim); }
.cf-result:focus { outline: 2px solid var(--patina); outline-offset: 2px; }
.cf-result .cf-h { color: var(--ink); font-weight: 600; margin: 0 0 6px; font-size: 1rem; }
.cf-result p { margin: 0 0 10px; }
.cf-result p:last-child { margin-bottom: 0; }
.cf-result .cf-meta { font-family: var(--font-mono); font-size: 0.82rem; color: var(--ink-faint); }
.cf-result .btn { margin-top: 4px; }
.cf-ok   { border-color: var(--patina-edge); background: var(--patina-wash); }
.cf-err  { border-color: var(--seal, #c9a227); background: var(--seal-wash, rgba(201,162,39,0.08)); }
.cf-warn { border-color: var(--line); }

/* The captcha's home. Empty and zero-height until Turnstile renders into it,
   so a form that never loads the challenge has no gap where it would have been,
   and a visitor whose network blocks Cloudflare sees the form they expected. */
.cf-captcha { margin: 4px 0 14px; min-height: 0; }
.cf-captcha:empty { margin: 0; }

/* The honeypot. Off-screen rather than display:none, because some bots skip
   anything that is display:none and this one is meant to be filled in. */
.cf-trap { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* ----------------------------------------------------------------- steps */
.steps { counter-reset: step; display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; }
.step-card { position: relative; background: var(--card); border: 1px solid var(--line); border-radius: var(--r-lg); padding: 28px; box-shadow: var(--shadow-1); overflow: hidden; }
.step-card::before {
  counter-increment: step; content: counter(step, decimal-leading-zero);
  font: 500 0.8rem var(--font-mono); letter-spacing: 0.1em;
  color: var(--seal);
  background: var(--seal-wash);
  border-radius: var(--r-full); padding: 5px 12px;
  display: inline-block; margin-bottom: 16px;
}

/* ------------------------------------------------------------------ FAQ */
.faq { display: grid; gap: 12px; max-width: 820px; margin: 0 auto; }
.faq details { background: var(--card); border: 1px solid var(--line); border-radius: var(--r); padding: 0 22px; box-shadow: var(--shadow-1); }
.faq summary {
  cursor: pointer; list-style: none;
  font-weight: 600; color: var(--ink); font-size: 1.02rem;
  padding: 18px 30px 18px 0;
  position: relative;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: '+'; position: absolute; right: 2px; top: 50%; transform: translateY(-50%);
  font: 400 1.3rem var(--font-mono); color: var(--patina);
}
.faq details[open] summary::after { content: '−'; }
.faq details p { color: var(--ink-dim); margin: 0 0 18px; font-size: 0.97rem; }

/* --------------------------------------------------------------- pricing */
.price-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 420px)); gap: 24px; justify-content: center; }
.price-card { background: var(--card); border: 1px solid var(--line); border-radius: var(--r-xl); padding: 34px; box-shadow: var(--shadow-1); position: relative; }
.price-card.featured { border-color: var(--patina-edge); box-shadow: var(--shadow-2); }
.price-card .plan-flag {
  position: absolute; top: -13px; left: 34px;
  background: var(--patina); color: #fff;
  font: 600 0.72rem/1 var(--font-ui); letter-spacing: 0.08em; text-transform: uppercase;
  border-radius: var(--r-full); padding: 7px 14px;
}
.price-card .amount { font-size: 2.6rem; font-weight: 700; color: var(--ink); letter-spacing: -0.02em; }
.price-card .amount small { font-size: 1rem; font-weight: 500; color: var(--ink-faint); letter-spacing: 0; }
.price-card ul { list-style: none; padding: 0; margin: 22px 0 26px; }
.price-card li { padding: 7px 0 7px 30px; position: relative; font-size: 0.96rem; color: var(--ink-dim); }
.price-card li::before {
  content: ''; position: absolute; left: 0; top: 12px;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--patina-wash) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23178a67' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6L9 17l-5-5'/%3E%3C/svg%3E") center/9px no-repeat;
}
/* Sits above the cards, because the "Most teams" flag is what sends a reader
   down the wrong branch and this has to be read before it. Capped in ch rather
   than px: it is one sentence of prose and the measure should follow the text,
   which runs ~30% longer in Vietnamese than in English. */
.price-pick { text-align: center; color: var(--ink-dim); font-size: 0.95rem;
              line-height: 1.6; max-width: 68ch; margin: 0 auto 24px; }
.price-pick strong { color: var(--ink); font-weight: 600; }

.price-note { text-align: center; color: var(--ink-faint); font-size: 0.9rem; margin-top: 26px; }

/* ------------------------------------------------------------- comparison */
.cmp-table { width: 100%; border-collapse: separate; border-spacing: 0; background: var(--card); border: 1px solid var(--line); border-radius: var(--r-lg); overflow: hidden; box-shadow: var(--shadow-1); font-size: 0.95rem; }
.cmp-table th, .cmp-table td { padding: 14px 18px; text-align: left; border-bottom: 1px solid var(--line); vertical-align: top; }
.cmp-table tr:last-child td { border-bottom: none; }
.cmp-table thead th { background: var(--paper-2); color: var(--ink); font-size: 0.9rem; }
.cmp-table thead th:nth-child(2) { color: var(--patina); }
.cmp-table td:first-child { color: var(--ink); font-weight: 500; width: 30%; }
.cmp-table .yes { color: var(--patina); font-weight: 600; }
.cmp-table .no { color: var(--ink-faint); }
.table-scroll { overflow-x: auto; }

/* ----------------------------------------------------------------- badge */
.badge {
  display: inline-flex; align-items: center; gap: 7px;
  font: 500 0.76rem/1.4 var(--font-mono);
  letter-spacing: 0.03em;
  border-radius: var(--r-full); padding: 6px 14px;
  background: var(--patina-wash); color: var(--patina);
  border: 1px solid var(--patina-edge);
}
.badge.gold { background: var(--seal-wash); color: var(--seal); border-color: var(--seal-edge); }
.badge.soon { background: var(--paper-2); color: var(--ink-dim); border: 1px dashed var(--line-strong); }

/* Decode-on-reveal: headings resolve out of cipher noise (site.js). */
.decoding { color: var(--ink-faint); }

/* -------------------------------------------------------------- CTA band */
.cta-band {
  background: var(--app-ink-850);
  border-radius: var(--r-xl);
  padding: 64px 48px;
  text-align: center;
  position: relative; overflow: hidden;
  border: 1px solid var(--app-line-strong);
}
.cta-band::before {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background:
    repeating-linear-gradient(63deg,  rgba(53, 195, 154, 0.07) 0 1px, transparent 1px 9px),
    repeating-linear-gradient(-63deg, rgba(53, 195, 154, 0.07) 0 1px, transparent 1px 9px);
  -webkit-mask-image: radial-gradient(80% 100% at 50% 100%, #000 20%, transparent 80%);
          mask-image: radial-gradient(80% 100% at 50% 100%, #000 20%, transparent 80%);
}
.cta-band h2 { color: var(--app-text); }
.cta-band p { color: var(--app-text-dim); max-width: 40em; margin: 0 auto 30px; }
.cta-band .btn-ghost { background: transparent; color: var(--app-text); border-color: var(--app-line-strong); }
.cta-band .btn-ghost:hover { border-color: var(--patina-bright); color: var(--patina-bright); }

/* ---------------------------------------------------------------- footer */
.footer { background: var(--app-ink-900); color: var(--app-text-dim); margin-top: 96px; border-top: 1px solid var(--app-line); }
.footer a { color: var(--app-text-dim); }
.footer a:hover { color: var(--patina-bright); text-decoration: none; }
.footer-inner { max-width: var(--w); margin: 0 auto; padding: 64px 24px 40px; }
.footer-grid { display: grid; grid-template-columns: 2fr 1fr 1fr 1fr; gap: 40px; margin-bottom: 48px; }
.footer .brand { color: var(--app-text); margin-bottom: 14px; }
.footer .fdesc { font-size: 0.9rem; max-width: 30em; color: var(--app-text-faint); }
.footer h4 { color: var(--app-text); font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 16px; }
.footer ul { list-style: none; padding: 0; margin: 0; }
.footer li { margin-bottom: 10px; font-size: 0.92rem; }
.footer-legal {
  border-top: 1px solid var(--app-line);
  padding-top: 26px;
  display: flex; flex-wrap: wrap; gap: 10px 24px; align-items: center;
  font-size: 0.82rem; color: var(--app-text-faint);
}
.footer-legal .sep { flex: 1; }

/* ------------------------------------------------------------ prose pages */
.page-hero { padding: 72px 0 40px; }
.page-hero .lede { margin-top: 14px; }
.prose { max-width: 760px; }
.prose h2 { font-size: 1.5rem; margin-top: 2.2em; }
.prose h3 { margin-top: 1.8em; }
.prose ul, .prose ol { padding-left: 1.4em; }
.prose li { margin: 0.4em 0; }
.prose blockquote {
  margin: 1.6em 0; padding: 18px 24px;
  border-left: 3px solid var(--patina);
  background: var(--card); border-radius: 0 var(--r) var(--r) 0;
  color: var(--ink-soft);
}
.prose code { font-family: var(--font-mono); font-size: 0.88em; background: var(--paper-2); border: 1px solid var(--line); border-radius: 5px; padding: 0.1em 0.4em; }
.prose .callout {
  border: 1px solid var(--patina-edge); border-radius: var(--r);
  background: var(--patina-wash);
  padding: 18px 22px; margin: 1.6em 0;
}
.prose .callout.gold { border-color: var(--seal-edge); background: var(--seal-wash); }

/* Legal documents share the prose column; they bring tables, warning
   callouts, and a version footer of their own. */
.prose table { width: 100%; border-collapse: collapse; margin: 1.2em 0; font-size: 0.92rem; }
.prose th, .prose td { border: 1px solid var(--line-strong); padding: 0.55em 0.75em; text-align: left; vertical-align: top; }
.prose th { background: var(--paper-2); color: var(--ink); }
.prose .callout h3 { margin-top: 0; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--danger); }
.prose > footer { margin-top: 3.5em; padding-top: 1.4em; border-top: 1px solid var(--line); font-size: 0.86rem; color: var(--ink-faint); }
.legal-meta { font-family: var(--font-mono); font-size: 0.85rem; color: var(--ink-dim); margin-top: 10px; }
.prose .toc { font-size: 0.94rem; }

/* --------------------------------------------------------------- contact */
.contact-grid { display: grid; grid-template-columns: 1.1fr 0.9fr; gap: 40px; align-items: start; }
.contact-card { background: var(--card); border: 1px solid var(--line); border-radius: var(--r-lg); padding: 30px; box-shadow: var(--shadow-1); }
.field { margin-bottom: 18px; }
.field label { display: block; font-weight: 600; color: var(--ink); font-size: 0.92rem; margin-bottom: 7px; }
.field input, .field textarea, .field select {
  width: 100%; font: 400 0.97rem/1.5 var(--font-ui);
  color: var(--ink); background: var(--paper);
  border: 1px solid var(--line-strong); border-radius: var(--r-sm);
  padding: 11px 14px; outline: none;
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.field input:focus, .field textarea:focus, .field select:focus { border-color: var(--patina); box-shadow: 0 0 0 3px var(--patina-wash); }
.field .hint { font-size: 0.82rem; color: var(--ink-faint); margin-top: 6px; }

/* ------------------------------------------------------- reveal on scroll */
.rv { opacity: 0; transform: translateY(18px); transition: opacity 0.6s ease, transform 0.6s ease; }
.rv.in { opacity: 1; transform: none; }
.rv-d1 { transition-delay: 0.08s; }
.rv-d2 { transition-delay: 0.16s; }
.rv-d3 { transition-delay: 0.24s; }

/* ------------------------------------------------------------- responsive */
/* Between the hamburger breakpoint and full width the bar is tight, more so
   in languages whose labels are wider than the English ones. */
@media (max-width: 1060px) {
  .topbar-inner { gap: 18px; }
  .nav { margin-left: 0; }
  .nav a { padding: 8px 9px; font-size: 0.9rem; }
  .topbar-actions { gap: 8px; }
}

@media (max-width: 980px) {
  .hero-grid { grid-template-columns: 1fr; gap: 48px; }
  .hero-grid > :last-child { padding-top: 0; }
  .hero-grid .trustline { justify-content: flex-start; }
  .grid-3, .steps { grid-template-columns: 1fr 1fr; }
  .footer-grid { grid-template-columns: 1fr 1fr; }
  .contact-grid { grid-template-columns: 1fr; }
  .demo-caption { position: static; margin-top: 12px; }
}
@media (max-width: 760px) {
  .nav {
    display: none;
    position: absolute; top: var(--topbar-h); left: 0; right: 0;
    flex-direction: column; gap: 2px;
    background: var(--card); border-bottom: 1px solid var(--line);
    padding: 12px 16px 16px; box-shadow: var(--shadow-2);
  }
  .nav.open { display: flex; }
  .nav-toggle { display: block; }
  .topbar-actions { margin-left: 0; }
  /* The bar cannot hold two buttons plus two controls at 375px, so the
     calls to action move into the menu and the language button drops to
     its globe. */
  .topbar-actions .btn { display: none; }
  .lang-btn { padding: 0 11px; }
  .lang-btn .lang-tag { display: none; }
  .nav .nav-cta { display: flex; justify-content: center; margin-top: 8px; }
  .eyebrow { font-size: 0.72rem; letter-spacing: 0.08em; }
  .grid-3, .grid-2, .steps, .split-panels, .price-cards { grid-template-columns: 1fr; }
  .section { padding: 64px 0; }
  .demo { height: 400px; }
  .demo-side { width: 52px; }
  .demo-side .side-brand span, .demo-side .item span { display: none; }
  .cta-band { padding: 48px 24px; }
}

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

/* ------------------------------------------- access graph illustration */
/**
 * The second picture the product needs: the company, not the employee, is the
 * centre of its own credentials. One administrator node holds the organisation
 * keys; access radiates out to people; and when two of them leave, the lines
 * are cut and every vault they touched takes a new key.
 *
 * Drawn as one inline SVG so it stays sharp at any width and needs no image
 * request. State lives in classes; JavaScript only sequences the beats.
 */
.graph {
  background: var(--app-ink-850);
  padding: 6px 10px 16px;
}
.gsvg { display: block; width: 100%; height: auto; overflow: visible; }

/* ---- edges: the grant itself ---- */
.edge {
  stroke: var(--app-line-strong);
  stroke-width: 1.5;
  fill: none;
  stroke-linecap: round;
  stroke-dasharray: var(--len);
  stroke-dashoffset: var(--len);
  transition: stroke-dashoffset 0.65s cubic-bezier(0.3, 0.8, 0.4, 1),
              stroke 0.35s ease, opacity 0.45s ease;
}
.edge.on { stroke: var(--patina-bright); stroke-dashoffset: 0; opacity: 0.85; }
/* Key material moving along a live grant. */
.edge.flow {
  stroke-dasharray: 5 9;
  stroke-dashoffset: 0;
  animation: edge-flow 0.65s linear infinite;
}
@keyframes edge-flow { to { stroke-dashoffset: -14; } }
/* Cut: the line goes gold, then retracts to nothing. */
.edge.cut { stroke: var(--seal-bright); stroke-dashoffset: var(--len); opacity: 0.28; animation: none; }

/* ---- nodes ---- */
/* Members are drawn as the thing they actually are: a laptop on someone's
   desk. The admin stays a circle, because it is the organisation, not a
   device. */
.gnode .screen {
  fill: var(--app-ink-800);
  stroke: var(--app-line-strong);
  stroke-width: 1.5;
  transition: stroke 0.35s ease, fill 0.35s ease;
}
.gnode .base {
  fill: var(--app-line-strong);
  transition: fill 0.35s ease;
}
.gnode text.ini {
  fill: var(--app-text-faint);
  font: 600 13px var(--font-mono);
  text-anchor: middle;
  transition: fill 0.35s ease;
}
.gnode text.who {
  fill: var(--app-text-faint);
  font: 400 11px var(--font-mono);
  text-anchor: middle;
  transition: fill 0.35s ease;
}
.gnode.on .screen { stroke: var(--patina-bright); fill: rgba(53, 195, 154, 0.07); }
.gnode.on .base { fill: var(--patina-bright); }
.gnode.on text.ini { fill: var(--patina-bright); }
.gnode.on text.who { fill: var(--app-text-dim); }
.gnode.cut .screen { stroke: var(--seal-bright); fill: rgba(217, 164, 65, 0.06); }
.gnode.cut .base { fill: var(--seal-bright); }
.gnode.cut text.ini,
.gnode.cut text.who { fill: var(--seal-bright); opacity: 0.75; }

/* What each member can actually open, written beside them. */
.gnode text.tags {
  fill: var(--app-text-faint);
  font: 400 9.5px var(--font-mono);
  text-anchor: middle;
  letter-spacing: 0.02em;
  transition: fill 0.35s ease;
}
.gnode.on text.tags { fill: var(--patina-bright); opacity: 0.9; }
.gnode.cut text.tags { fill: var(--seal-bright); opacity: 0.95; }

/* Encrypted payload travelling the granted line. The point of showing it is
   that it is already sealed when it leaves the centre. */
.spark {
  fill: var(--patina-bright);
  opacity: 0;
  offset-distance: 0%;
  offset-rotate: 0deg;
}
.spark.run { animation: spark-run 0.95s cubic-bezier(0.4, 0, 0.5, 1); }
@keyframes spark-run {
  0%   { offset-distance: 0%;   opacity: 0; }
  18%  { opacity: 1; }
  82%  { opacity: 1; }
  100% { offset-distance: 100%; opacity: 0; }
}

/* The ✕ that lands on a revoked member. */
.gnode .strike { stroke: var(--seal-bright); stroke-width: 2; stroke-linecap: round; opacity: 0; transition: opacity 0.3s ease; }
.gnode.cut .strike { opacity: 1; }

/* A grant landing: one soft ring, once. */
.gnode .ping {
  fill: none; stroke: var(--patina-bright); stroke-width: 1.5;
  opacity: 0; transform-origin: center; transform-box: fill-box;
  rx: 7;
}
.gnode.pinged .ping { animation: node-ping 0.75s ease-out; }
@keyframes node-ping {
  from { opacity: 0.85; transform: scale(0.75); }
  to { opacity: 0; transform: scale(1.7); }
}

/* ---- the centre: the organisation's own keys ---- */
.ghub circle.ring {
  fill: none; stroke: var(--patina-edge); stroke-width: 1;
  transform-origin: center; transform-box: fill-box;
  animation: hub-breathe 3.4s ease-in-out infinite;
}
@keyframes hub-breathe {
  0%, 100% { opacity: 0.35; transform: scale(1); }
  50% { opacity: 0.9; transform: scale(1.06); }
}
.ghub circle.body { fill: var(--app-ink-700); stroke: var(--patina-bright); stroke-width: 1.8; }
.ghub .keyglyph { stroke: var(--patina-bright); fill: none; stroke-width: 1.6; stroke-linecap: round; }
.ghub text.label {
  fill: var(--patina-bright);
  font: 500 10px var(--font-mono);
  letter-spacing: 0.14em;
  text-anchor: middle;
}
.ghub text.sub { fill: var(--app-text-faint); font: 400 10px var(--font-mono); text-anchor: middle; }
.ghub.rotating circle.body { stroke: var(--seal-bright); }
.ghub.rotating .keyglyph { stroke: var(--seal-bright); }
.ghub.rotating text.label { fill: var(--seal-bright); }

@media (max-width: 760px) {
  /* Scaled to a phone the whole diagram would put the access labels at about
     four pixels, which is decoration rather than information. Keeping a
     minimum width and letting the reader pan preserves every label instead of
     dropping the part that carries the meaning. */
  .graph { padding: 4px 0 14px; overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .gsvg { min-width: 660px; }
  .rfoot { padding-left: 12px; padding-right: 12px; }
}

.rfoot {
  margin-top: 13px; padding-top: 12px;
  border-top: 1px dashed var(--app-line);
}
.rvaults { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 10px; }
.vchip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 10px;
  border: 1px solid var(--app-line);
  border-radius: var(--r-full);
  font: 400 10px/1.3 var(--font-mono);
  color: var(--app-text-faint);
  white-space: nowrap;
  transition: color 0.3s ease, border-color 0.3s ease;
}
.vchip.on { color: var(--app-text-dim); border-color: var(--app-line-strong); }
.vchip.spin { color: var(--patina-bright); border-color: var(--patina-edge); }
.vchip.done { color: var(--seal-bright); border-color: var(--seal-edge); }
.vchip i {
  width: 9px; height: 9px; flex: none;
  border-radius: 50%;
  background: var(--app-line-strong);
  transition: background 0.3s ease;
}
.vchip.on i { background: var(--app-text-faint); }
.vchip.spin i {
  background: none;
  border: 1.5px solid var(--patina-bright);
  border-top-color: transparent;
  animation: vspin 0.7s linear infinite;
}
.vchip.done i { background: var(--seal-bright); }
@keyframes vspin { to { transform: rotate(360deg); } }

.rnote {
  display: flex; align-items: center; gap: 8px;
  font: 400 10.5px/1.5 var(--font-mono);
  color: var(--app-text-faint);
  transition: color 0.3s ease;
}
.rnote.warn { color: var(--seal-bright); }
.rnote svg { width: 13px; height: 13px; flex: none; stroke: currentColor; fill: none; stroke-width: 2; }

@media (max-width: 760px) {
  .roster { padding: 12px; min-height: 320px; }
  .rrow { gap: 8px; padding: 9px 10px; }
  .rrole { display: none; }
  .rname { font-size: 12px; }
}

/* A caption that sits in the flow rather than under a hero column. */
.demo-caption.inflow { position: static; margin-top: 14px; }

/* ------------------------------------------- how-it-works: split header */
/**
 * The access graph sits beside the heading rather than under the cards, so the
 * claim and the picture that proves it are read together.
 *
 * The heading stays first in the DOM, which is what a screen reader and a
 * crawler follow; grid placement is what puts the figure on the left.
 */
.how-split {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  gap: 44px;
  align-items: center;
  margin-bottom: 56px;
}
.how-split > .section-head { grid-column: 2; grid-row: 1; max-width: none; margin-bottom: 0; }
.how-split > .how-figure   { grid-column: 1; grid-row: 1; min-width: 0; }

/* Half a column is not much room for a diagram, so its type is set larger
   here than it would be at full width. */
.how-figure .gnode text.who  { font-size: 14px; }
.how-figure .gnode text.tags { font-size: 12px; }
.how-figure .gnode text.ini  { font-size: 15px; }
.how-figure .ghub text.label { font-size: 12px; }
.how-figure .flow-head       { font-size: 10px; }

@media (max-width: 980px) {
  /* Stacked: the heading leads, the figure follows, matching the DOM. */
  .how-split { grid-template-columns: 1fr; gap: 32px; margin-bottom: 44px; }
  .how-split > .section-head,
  .how-split > .how-figure { grid-column: 1; grid-row: auto; }
}

/* ------------------------------------------------------- consent banner */
/**
 * Asking before measuring. Two things this deliberately does not do: it does
 * not block the page behind a modal, and it does not make refusing harder than
 * accepting. Both buttons are the same size and sit side by side, because a
 * consent form that nudges is not consent.
 */
.consent {
  position: fixed; z-index: 80;
  left: 16px; right: 16px; bottom: 16px;
  max-width: 560px;
  margin: 0 auto;
  background: var(--card);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-3);
  padding: 20px 22px;
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.28s ease, transform 0.28s ease;
}
.consent.in { opacity: 1; transform: none; }
.consent h2 {
  font: 600 1rem/1.35 var(--font-ui);
  color: var(--ink);
  margin: 0 0 6px;
  letter-spacing: 0;
}
.consent p {
  font-size: 0.9rem;
  line-height: 1.55;
  color: var(--ink-dim);
  margin: 0 0 14px;
}
.consent-actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.consent-actions .btn { flex: 1 1 auto; min-width: 132px; justify-content: center; }
.consent-actions a {
  font-size: 0.85rem;
  color: var(--ink-faint);
  margin-left: auto;
}
.consent-actions a:hover { color: var(--patina); }

/* The way back in, once a choice has been made. */
.consent-reopen {
  background: none; border: none; padding: 0;
  font: inherit; color: inherit; cursor: pointer;
  text-decoration: underline; text-underline-offset: 2px;
}
.consent-reopen:hover { color: var(--patina-bright); }

@media (max-width: 560px) {
  .consent { padding: 17px 18px; left: 10px; right: 10px; bottom: 10px; }
  .consent-actions .btn { flex: 1 1 100%; }
  .consent-actions a { margin: 4px auto 0; }
}

/* ============================================================================
   Client row
   ----------------------------------------------------------------------------
   Four client marks on one line, muted by default, with one lit at a time and
   a line of context that changes with it.

   The marks sit straight on the band, no card. Both files of every mark are
   therefore generated against a dark ground: see scripts/build-client-logos.py
   for which colours had to be lifted off it and why.

   Each mark ships as two files, muted and full colour, stacked in a grid cell
   and cross-faded. Same geometry in both, so the fade shows no ghosting, and
   the muted file remains the one that renders if the colour file is slow.
   ========================================================================== */

.clients-panel { padding: 8px 0 0; }

.client-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 28px;
  flex-wrap: wrap;
}

.client {
  appearance: none;
  border: 1px solid transparent;
  background: transparent;
  border-radius: 12px;
  padding: 12px 16px;
  cursor: pointer;
  transition: border-color 0.35s, background 0.35s, transform 0.35s;
}

.client:hover { border-color: var(--line); }
.client:focus-visible { outline: 2px solid var(--patina); outline-offset: 2px; }

.client.is-on {
  border-color: var(--patina-edge);
  background: var(--patina-wash);
  transform: translateY(-2px);
}

/* The two files occupy the same grid cell, so the box is sized once. */
.client-art { display: grid; }
.client-art img {
  grid-area: 1 / 1;
  height: 65px;
  width: auto;
  display: block;
  transition: opacity 0.55s ease;
}
.client-art .c-color { opacity: 0; }
.client.is-on .c-color { opacity: 1; }
.client.is-on .c-grey { opacity: 0; }

/**
 * The four marks are 3.45:1, 1.88:1, 1.34:1 and 1.15:1. Setting one height for
 * all of them, which is what "same size" means to a file, made the widest
 * lockup carry roughly twice the ink of the narrowest and read as the only real
 * client in the row. These heights even out the covered AREA instead, which is
 * what the eye actually compares. The files themselves stay at a uniform 150px.
 */
.client[data-mark="xnor-group"] .client-art img { height: 52px; }
.client[data-mark="vinh-vinh-thinh"] .client-art img { height: 79px; }
.client[data-mark="vinh-phat-an"] .client-art img { height: 77px; }

/**
 * All four lines stay in the DOM and share one grid cell, so the block is as
 * tall as the longest of them and nothing below it moves when the line changes.
 * Keeping them in the markup also keeps them readable without JavaScript.
 */
.client-lines { display: grid; margin-top: 26px; }
.client-line {
  grid-area: 1 / 1;
  margin: 0;
  text-align: center;
  max-width: 62em;
  justify-self: center;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.5s ease, transform 0.5s ease;
  pointer-events: none;
}
.client-line.is-on { opacity: 1; transform: none; pointer-events: auto; }
.client-line p {
  margin: 0;
  color: var(--ink-soft);
  font-size: 1.06rem;
  line-height: 1.6;
}
.client-line cite {
  display: block;
  margin-top: 10px;
  font-style: normal;
  font-family: var(--mono);
  font-size: 0.76rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--patina);
}

@media (max-width: 900px) {
  .clients-panel { padding: 26px 16px 24px; }
  .client { padding: 10px 12px; }
  .client-art img { height: 45px; }
  .client[data-mark="xnor-group"] .client-art img { height: 36px; }
  .client[data-mark="vinh-vinh-thinh"] .client-art img { height: 54px; }
  .client[data-mark="vinh-phat-an"] .client-art img { height: 52px; }
  .client-line p { font-size: 0.97rem; }
}

/* Without motion the row simply shows every mark in colour and the first line. */
@media (prefers-reduced-motion: reduce) {
  .client-art img { transition: none; }
  .client-art .c-color { opacity: 1; }
  .client-art .c-grey { opacity: 0; }
  .client-line { transition: none; }
}

/* ============================================================================
   Apps stage: read on the left, watch the app on the right
   ----------------------------------------------------------------------------
   Three steps of copy scroll past a screenshot that stays put. The picture is
   pinned because the point is to look at it WHILE reading the sentence that
   explains it; a screenshot that scrolls away mid-paragraph explains nothing.

   Screenshots here are full strength. The previous version floated them behind
   the whole page at half opacity, and it failed for the reason any background
   image of dense UI fails: at 50% it was too faint to read as an application
   and too present to read past. Giving them their own half of one section buys
   both back at once.

   Everything that moves is `transform` and `opacity`. The pinning is native
   `position: sticky`, so the browser does it without a single frame of script.
   ========================================================================== */

.apps-stage { overflow: clip; }

.stage {
  display: grid;
  grid-template-columns: minmax(0, 44fr) minmax(0, 56fr);
  gap: 56px;
  align-items: start;
}

/**
 * On a wide screen the picture reaches past the text column toward the edge of
 * the window. The console is a landscape screenshot of a dense list; boxed into
 * half of a 1200px measure its own labels were too small to read, which defeats
 * the reason for showing a real screenshot at all. The section clips, so the
 * overhang can never cause a horizontal scrollbar.
 */
@media (min-width: 1200px) {
  .stage-sticky { width: calc(100% + min(7vw, 130px)); }
}

/* ---- left: the steps ---- */

.stage-steps { display: flex; flex-direction: column; }

.stage-step {
  min-height: 78vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 40px 0;
  /* Driven from JS as the step approaches the middle of the screen. */
  opacity: var(--stage-o, 0.28);
  transform: translate3d(0, var(--stage-y, 18px), 0);
  transition: opacity 0.45s ease, transform 0.45s ease;
  will-change: opacity, transform;
}

.step-rule {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 18px;
  font: 500 0.76rem/1 var(--font-mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--patina);
}
/* Fills as the step becomes the active one: a progress bar you never notice. */
.step-rule::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(to right, var(--patina) 0%, var(--patina) var(--stage-fill, 0%), var(--line) var(--stage-fill, 0%));
}

/**
 * Trailing room under the last step. `position: sticky` can only travel inside
 * its containing block, and that block ends where the steps end: without this
 * the picture unpinned and scrolled away exactly as the third step reached the
 * middle of the screen, which is the moment it was needed.
 */
.stage-step:last-child { padding-bottom: 20vh; }

.stage-step h3 { font-size: clamp(1.5rem, 2.4vw, 2rem); margin-bottom: 14px; }
.stage-step > p { color: var(--ink-dim); font-size: 1.06rem; max-width: 34em; }

.step-facts { list-style: none; padding: 0; margin: 22px 0 0; display: grid; gap: 10px; }
/**
 * The dot is positioned, not a flex item. With `display: flex` on the li, any
 * inline element in the sentence (an <em>, a link) became a flex item too and
 * got its own column, so "sit under <em>For this site</em>" broke into three
 * ragged pieces. Normal flow keeps the sentence a sentence.
 */
.step-facts li {
  position: relative;
  padding-left: 20px;
  font-size: 0.94rem;
  color: var(--ink-soft);
}
.step-facts li::before {
  content: "";
  position: absolute; left: 0; top: 0.55em;
  width: 6px; height: 6px;
  border-radius: 50%; background: var(--patina); box-shadow: 0 0 0 4px var(--patina-wash);
}
.step-facts em { font-style: normal; color: var(--ink); }

/* ---- right: the pinned picture ---- */

/**
 * Must be as tall as the steps beside it. `position: sticky` pins an element
 * WITHIN ITS CONTAINING BLOCK, so a column only as tall as the picture has
 * nothing to travel through and scrolls away like anything else: the picture
 * left the screen after the first step and the section looked broken from
 * there down. The grid sets `align-items: start` for the copy; this one item
 * opts back into stretching.
 */
.stage-visual { position: relative; align-self: stretch; }

.stage-sticky {
  position: sticky;
  /**
   * Viewport units only. A percentage `top` on a sticky element resolves
   * against the CONTAINING BLOCK's height, not the scrollport, and this column
   * is as tall as all three steps put together: `calc(50% - 34vh)` came out
   * around 1000px and parked the picture below the fold, where it looked like
   * the screenshot had simply failed to load.
   */
  top: 16vh;
  height: 68vh;
  display: grid;
  /**
   * Stretch, not centre. `place-items: center` makes each grid item shrink to
   * its content, so a device frame asking for `width: 100%` measured itself
   * against its own contents and came out a quarter of the size it should be.
   * The `.shot` boxes centre their own contents instead.
   */
  place-items: stretch;
}

.stage-sticky .shot {
  grid-area: 1 / 1;
  margin: 0;
  max-height: 68vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  position: relative;
  will-change: transform, opacity;
}

/* ---------------------------------------------------------------- devices --
 * Each screenshot sits in the hardware it actually runs on, because three
 * rectangles of dark UI side by side are three rectangles of dark UI: nothing
 * in the pixels tells a reader which one is a browser add-on and which is a
 * phone. The frame does that work in the first half second, before any caption
 * is read.
 *
 * Built in CSS around the plain screenshot rather than baked into the image:
 * the shots stay re-capturable without redoing artwork, the frames stay sharp
 * at any size, and the whole set costs no extra bytes over the wire.
 * ------------------------------------------------------------------------ */

.device { position: relative; width: 100%; }
.device img { display: block; width: 100%; height: auto; }

/* ---- browser window, with the popup hanging off the toolbar ---- */

.dev-browser {
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid var(--app-line-strong);
  background: var(--app-ink-900);
  box-shadow: 0 30px 90px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.35);
}

.dev-chrome {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  background: var(--app-ink-700);
  border-bottom: 1px solid var(--app-line);
}
.dev-dots { display: flex; gap: 6px; flex: none; }
.dev-dots i { width: 9px; height: 9px; border-radius: 50%; background: var(--app-line-strong); }
.dev-url {
  flex: 1; min-width: 0; height: 22px;
  display: flex; align-items: center; gap: 6px; padding: 0 10px;
  border-radius: 11px; background: var(--app-ink-900);
  font: 500 10px/1 var(--font-mono);
  color: var(--app-text-faint);
  white-space: nowrap; overflow: hidden;
}
/* The extension's own button in the toolbar: the thing the popup hangs from. */
.dev-ext {
  flex: none; width: 22px; height: 22px; border-radius: 6px;
  display: grid; place-items: center;
  background: var(--patina-wash); color: var(--patina);
  outline: 1px solid var(--patina-edge);
}
.dev-ext svg { width: 14px; height: 14px; }

.dev-page {
  position: relative;
  /* The recording is 16:11, so the window is too and nothing letterboxes. */
  aspect-ratio: 16 / 11;
  background:
    radial-gradient(120% 90% at 20% 0%, rgba(255,255,255,0.03), transparent 60%),
    var(--app-ink-900);
}
/* ---- phone ---- */

.dev-phone {
  border-radius: 13.5%/6.2%;
  padding: 1.9%;
  background: linear-gradient(150deg, #6f7683 0%, #2c313c 32%, #464c58 62%, #23272f 100%);
  box-shadow: 0 30px 90px rgba(0, 0, 0, 0.55), 0 2px 8px rgba(0, 0, 0, 0.4);
}
.dev-phone .dev-screen {
  position: relative;
  border-radius: 11.6%/5.3%;
  overflow: hidden;
  background: #000;
}
/* Dynamic Island. Drawn on the frame because it is the phone, not the app. */
.dev-island {
  position: absolute; z-index: 2;
  top: 1.4%; left: 50%; transform: translateX(-50%);
  width: 30%; height: 3.1%;
  border-radius: 999px;
  background: #05070b;
}
/* Status bar: the screenshot comes from the app, which never draws one. */
.dev-status {
  position: absolute; z-index: 2; inset: 0 0 auto 0;
  display: flex; align-items: center; justify-content: space-between;
  padding: 1.2% 7% 0;
  font: 600 clamp(7px, 1.05vw, 11px)/1 var(--font-ui);
  color: #fff;
  pointer-events: none;
}
.dev-status .sig { display: flex; align-items: flex-end; gap: 2px; height: 0.9em; }
.dev-status .sig i { width: 2px; background: #fff; border-radius: 1px; }
.dev-status .sig i:nth-child(1) { height: 40%; }
.dev-status .sig i:nth-child(2) { height: 62%; }
.dev-status .sig i:nth-child(3) { height: 82%; }
.dev-status .sig i:nth-child(4) { height: 100%; }
.dev-status .bat {
  width: 1.6em; height: 0.85em; border: 1.2px solid rgba(255,255,255,0.75);
  border-radius: 3px; position: relative;
}
.dev-status .bat::before {
  content: ""; position: absolute; inset: 1.5px; width: 72%;
  background: #fff; border-radius: 1px;
}
.dev-status .bat::after {
  content: ""; position: absolute; right: -3px; top: 30%; width: 2px; height: 40%;
  background: rgba(255,255,255,0.75); border-radius: 0 1px 1px 0;
}
/* Pushes the app content below the status bar, as iOS does. */
.dev-phone .dev-screen img { padding-top: 5.2%; background: #0b0e14; }

/* Side rails. */
.dev-phone::before,
.dev-phone::after {
  content: "";
  position: absolute;
  width: 2px;
  border-radius: 2px;
  background: linear-gradient(#8a919e, #4d525c);
}
.dev-phone::before { left: -2px; top: 22%; height: 7%; box-shadow: 0 5.5% 0 0 #6b717c; }
.dev-phone::after  { right: -2px; top: 26%; height: 11%; }

/* ---- laptop ---- */

.dev-laptop { display: flex; flex-direction: column; align-items: center; }
.dev-lid {
  position: relative;
  width: 100%;
  border-radius: 12px 12px 3px 3px;
  padding: 10px 10px 14px;
  background: linear-gradient(160deg, #4a505c 0%, #2b2f38 40%, #3a3f49 100%);
  box-shadow: 0 30px 90px rgba(0, 0, 0, 0.5);
}
.dev-lid .dev-screen {
  border-radius: 3px;
  overflow: hidden;
  background: #000;
  position: relative;
}
/* Camera dot on the top bezel. */
.dev-lid::before {
  content: "";
  position: absolute; left: 50%; transform: translateX(-50%);
  margin-top: -6px; width: 4px; height: 4px; border-radius: 50%;
  background: #11141a;
}
.dev-base {
  width: 116%;
  height: 12px;
  border-radius: 0 0 12px 12px;
  background: linear-gradient(#3c4149 0%, #2a2e35 55%, #1d2027 100%);
  box-shadow: 0 12px 26px rgba(0, 0, 0, 0.45);
  position: relative;
}
/* The dip you grip the lid by. */
.dev-base::after {
  content: "";
  position: absolute; left: 50%; top: 0; transform: translateX(-50%);
  width: 14%; height: 4px;
  border-radius: 0 0 6px 6px;
  background: #171a20;
}

/* ---- how big each device gets on the stage ---- */

.stage-sticky .shot[data-shot="extension"] .device { width: 100%; }
.stage-sticky .shot[data-shot="console"] .device   { width: 100%; }
/* A phone that filled the column would read as a tablet. */
.stage-sticky .shot[data-shot="mobile"] .device    { width: 42%; max-width: 260px; }

/**
 * A caption under the picture naming what is on screen. Without it the reader
 * has to infer which of three clients they are looking at from the copy alone,
 * which is exactly the work the picture was supposed to save them.
 */
/**
 * Sits directly under the device, in flow. Hung off the bottom of the sticky
 * box it landed at the bottom of a 68vh column regardless of how tall the
 * device was, which for the laptop meant the caption printed on top of the
 * paragraph below the stage.
 */
.shot-tag {
  white-space: nowrap;
  font: 500 0.74rem/1 var(--font-mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
  opacity: 0;
}

.stage-foot { margin-top: 26px; }

@media (max-width: 980px) {
  /* One column: each step keeps its own picture directly underneath it, which
     is the only arrangement that still pairs them when nothing can be pinned. */
  .stage { grid-template-columns: 1fr; gap: 0; }
  .stage-step { min-height: 0; padding: 0 0 22px; opacity: 1; transform: none; }
  .stage-visual { display: none; }
  .step-shot { display: block; margin: 22px 0 48px; }
  /* A phone drawn the full width of a phone reads as a tablet. */
  .step-shot .dev-phone { width: 62%; margin: 0 auto; }
}

/* Above the fold-out breakpoint the inline copies are never rendered. */
@media (min-width: 981px) { .step-shot { display: none; } }

@media (prefers-reduced-motion: reduce) {
  .stage-step { opacity: 1 !important; transform: none !important; transition: none; }
  .stage-sticky .shot { transition: none !important; }
}

/* ------------------------------------------------------------- team band */
/*
 * The only section on the site built around a photograph, and deliberately the
 * only one that leaves the `.wrap` column: the picture runs the full width of
 * the viewport with the heading set into its lower edge. Every other section on
 * the home page is a centred stack of cards, so the break is the point.
 *
 * The first attempt put the photo beside the copy. It measured 442px of picture
 * against 1130px of text, because a landscape photograph cannot balance a tall
 * column of prose no matter how the grid is weighted. Letterboxing it across
 * the top solves that: the band's height is set by its own aspect ratio, so the
 * copy underneath can be any length in any of the five languages.
 */
.team-band { padding: 0 0 96px; }

.team-hero { position: relative; line-height: 0; }
.team-shot {
  display: block;
  width: 100%;
  aspect-ratio: 21 / 9;
  object-fit: cover;
  object-position: 50% 47%;   /* the faces sit just above the frame's centre */
}
/* A photograph lit for a bright room, dropped onto a page that is dark by
   default, reads as pasted on unless something carries it into the page. The
   scrim does that and doubles as the contrast the heading needs; it is a black
   ramp rather than a theme token because the text on it is white in both
   themes. */
.team-cap {
  position: absolute;
  inset: auto 0 0 0;
  line-height: 1.2;
  padding: 120px 0 34px;
  background: linear-gradient(180deg, rgba(4, 6, 12, 0) 0%, rgba(4, 6, 12, 0.55) 52%, rgba(4, 6, 12, 0.88) 100%);
}
.team-cap .wrap {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 18px 44px;
  flex-wrap: wrap;
}
.team-cap .eyebrow { color: rgba(255, 255, 255, 0.74); }
.team-cap h2 { color: #fff; margin: 8px 0 0; max-width: 22ch; text-wrap: balance; }
/* Set opposite the heading rather than under it: the credit is what turns the
   picture from decoration into evidence, and putting it on the same baseline as
   the h2 balances a composition whose subject sits left of centre. */
.team-credit {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  line-height: 1.65;
  color: rgba(255, 255, 255, 0.72);
  margin: 0 0 6px;
  max-width: 34ch;
  text-align: right;
}

.team-body { padding-top: 46px; }

/* The numerals come from a counter, not from markup: this is an <ol>, so the
   number is already in the accessibility tree. A hand-written <span>01</span>
   would be read out a second time, and would have to be kept in step by hand
   across five translations. */
.tl {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: tl;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0 48px;
}
.tl > li {
  counter-increment: tl;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: baseline;
  gap: 0 16px;
  padding: 22px 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.tl > li::before {
  content: counter(tl, decimal-leading-zero);
  font-family: var(--font-mono);
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--patina);
  font-variant-numeric: tabular-nums;
}
.tl h3 { font-size: 1.06rem; line-height: 1.45; margin: 0; font-weight: 600; }

.team-foot {
  margin-top: 30px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px 26px;
}

@media (max-width: 900px) {
  .team-band { padding-bottom: 72px; }
  .team-shot { aspect-ratio: 16 / 10; }
  .team-cap { padding: 80px 0 24px; }
  /* Wrapped onto its own line, a right-aligned credit reads as an orphan. */
  .team-credit { text-align: left; max-width: none; margin: 0; }
  .tl { grid-template-columns: 1fr; gap: 0; }
  /* Stacked, adjacent top and bottom rules would double into a 2px line. */
  .tl > li { border-bottom: none; }
  .tl > li:last-child { border-bottom: 1px solid var(--line); }
}
