/* Unfilter VPN — a monochrome surface.

   WHAT THIS REPLACED, and why. The old sheet said so itself: "Built on the NEXUS
   template's motion language ... the palette is shifted to blue/cyan". It was a
   bought template with the colours swapped, and it wore every tell of one:

     * two 520/640px blurred colour orbs drifting across the background on a 36s
       loop, plus two radial gradients and a 44px graph-paper grid over the top
     * a four-stop blue/cyan/pale-blue gradient ANIMATED across the hero title
     * gradient-filled buttons with a coloured drop shadow and a white diagonal
       "shine" that swept across them on hover
     * a logo badge with a coloured glow, a 5s breathing scale, and two
       counter-rotating rings

   The download page was the worst of it: two enormous blue-to-cyan gradient
   buttons reading "Download .exe" — visually identical to the fake download
   buttons on an advert-funded file host, which is a terrible thing for a VPN
   installer to look like.

   The replacement has ONE colour, on ONE word of the title, and nothing else.
   Surfaces are neutral greys, buttons are black and white, and the download page
   leads with file facts and checksums instead of a coloured call to action.
   Everything that moved for decoration has been removed; the entrance fades
   remain, because they are not decoration of the content, they are its arrival. */

:root {
    /* Neutral. Not one of these has a hue -- the old --bg #05070d was a blue-black
       and it tinted every panel mixed from it. */
    --bg: #0a0a0b;
    --bg-2: #101012;
    --panel: rgba(255, 255, 255, 0.026);
    --panel-strong: rgba(255, 255, 255, 0.055);
    --border: rgba(255, 255, 255, 0.115);
    --border-soft: rgba(255, 255, 255, 0.065);
    --text: #f2f2f3;
    --muted: #9b9b9f;
    --faint: #6a6a6e;
    /* --accent IS WHITE. It was #3d8bff and is referenced by ~40 rules -- focus
       rings, hover borders, links, the nav pill. Making it white neutralises all of
       them at once and keeps every one of those rules meaningful. */
    --accent: #ffffff;
    --accent-2: #ffffff;
    /* The single permitted colour, and it is used in exactly one place: one word of
       the hero title. Anything else that wants emphasis uses weight or a border. */
    --title: #6aa4ff;
    /* Kept because they carry MEANING, never used as a fill: an expiry date that has
       passed has to look different from one that has not. Desaturated so they read as
       information rather than decoration. */
    --good: #cfcfd2;
    --warn: #d8b678;
    --bad: #e2727c;
    --mono: ui-monospace, "Cascadia Mono", "JetBrains Mono", Consolas, monospace;
    --sans: "Inter", "Segoe UI", -apple-system, "Helvetica Neue", Arial, sans-serif;
    --radius: 10px;
    --ease: cubic-bezier(0.3, 0.8, 0.2, 1);
    --spring: cubic-bezier(0.34, 1.45, 0.4, 1);
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }

body {
    background: var(--bg);
    color: var(--text);
    font-family: var(--sans);
    font-size: 15px;
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
    min-height: 100vh;
    overflow-x: hidden;
}

/* ── Backdrop ─────────────────────────────────────────────
   A flat colour. It held two radial gradients, a 44px grid drawn with two more
   gradients and masked with a third, and two blurred colour orbs animating on a
   36s loop. None of it said anything; all of it said "template". */
.backdrop { position: fixed; inset: 0; z-index: -2; background: var(--bg); }
/* The orbs are gone. The markup still contains the two <div class="orb"> nodes so
   nothing in app.js has to change; they render nothing. */
.orb { display: none; }

::selection { background: rgba(255, 255, 255, 0.22); }

/* ── Navigation ───────────────────────────────────────── */
nav {
    position: fixed; top: 0; left: 0; right: 0; z-index: 50;
    backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
    background: rgba(5, 7, 13, 0.72);
    border-bottom: 1px solid var(--border-soft);
    animation: navDrop 0.8s var(--ease);
}
@keyframes navDrop { from { transform: translateY(-100%); } to { transform: translateY(0); } }

.nav-inner {
    max-width: 1080px; margin: 0 auto; padding: 0 24px;
    height: 62px; display: flex; align-items: center; gap: 28px;
}
.brand {
    display: flex; align-items: center; gap: 10px; cursor: pointer;
    font-weight: 600; letter-spacing: 0.22em; font-size: 13px; color: var(--text);
    text-decoration: none; user-select: none;
}
.brand .mark { width: 22px; height: 22px; flex: none; border-radius: 5px;
               transition: transform 0.6s var(--spring); }
.brand:hover .mark { transform: rotate(-8deg) scale(1.12); }

/* ── Hero logo badge ──────────────────────────────────────
   The real application icon, framed by two counter-rotating rings so the mark the
   user will see in their taskbar is also the first thing on the page. */
.logo-badge { position: relative; width: 132px; height: 132px; margin: 0 auto 40px;
    display: grid; place-items: center;
    animation: rise 0.8s var(--ease) both; }
.logo-badge img { width: 88px; height: 88px; position: relative; z-index: 1;
    /* No coloured glow, and no 5s breathing scale. */ }
.logo-badge .ring { position: absolute; inset: 0; border-radius: 50%;
    border: 1px solid var(--border);  }
.logo-badge .ring:nth-child(2) { inset: 14px; border-style: dashed;
    border-color: var(--border-soft); }
@keyframes badgeBreathe { 50% { transform: scale(1.05); } }

.nav-links { display: flex; gap: 4px; flex: 1; position: relative; }
.nav-links #nav-pill {
    position: absolute; top: 50%; left: 0; height: 33px; width: 0;
    border-radius: 9px; z-index: 0; opacity: 0; pointer-events: none;
    background: var(--panel-strong);
    border: 1px solid var(--border);
    transition: transform 0.55s var(--spring), width 0.55s var(--spring), opacity 0.35s;
}
.nav-links a {
    color: var(--muted); text-decoration: none; font-size: 13.5px;
    padding: 7px 13px; border-radius: 8px; position: relative; z-index: 1;
    transition: color 0.3s, transform 0.3s var(--spring);
}
.nav-links a:hover { color: var(--text); transform: translateY(-1px); }
.nav-links a.active { color: var(--text); }

.nav-user { display: flex; align-items: center; gap: 10px; font-size: 12.5px; color: var(--faint); }
.nav-user .chip {
    font-family: var(--mono); font-size: 11px; color: var(--muted);
    border: 1px solid var(--border); border-radius: 6px; padding: 3px 8px;
    animation: chipIn 0.5s var(--spring);
}
@keyframes chipIn { from { opacity: 0; transform: scale(0.7); } to { opacity: 1; transform: scale(1); } }

/* ── Directional View Transitions ─────────────────────── */
main { max-width: 1080px; margin: 0 auto; padding: 108px 24px 90px; display: grid; }
main > .view { grid-area: 1 / 1; }

.view { display: none; }
.view.active { display: block; animation: slideIn 0.65s var(--ease) both; }
.view.leaving { display: block; pointer-events: none; animation: slideOut 0.4s var(--ease) both; }

@keyframes slideIn {
    from { opacity: 0; transform: translateX(var(--in, 70px)) scale(0.985); filter: blur(7px); }
    to   { opacity: 1; transform: translateX(0) scale(1); filter: blur(0); }
}
@keyframes slideOut {
    from { opacity: 1; transform: translateX(0); filter: blur(0); }
    to   { opacity: 0; transform: translateX(var(--out, -70px)) scale(0.985); filter: blur(7px); }
}

/* Scroll reveals. Note the `html.js` gate: unlike the entrances below, nothing in
   CSS ever raises a .reveal — only JS adding .shown does — so the hidden state is
   applied only once an inline script in <head> has confirmed JS is running. Without
   the gate, a blocked or failed app.js leaves the features and pricing sections
   invisible, which is most of the page. */
html.js .reveal { opacity: 0; transform: translateY(30px); filter: blur(5px);
          transition: opacity 0.9s var(--ease), transform 0.9s var(--ease), filter 0.9s var(--ease); }
html.js .reveal.shown { opacity: 1; transform: translateY(0); filter: blur(0); }

/* ── Entrance animations own MOTION, never VISIBILITY ──────
 *
 * Every entrance below animates from a hidden state declared inside its own
 * @keyframes `from`, with fill-mode `both`, and the element's own rule leaves it
 * fully visible.
 *
 * The obvious alternative — `opacity: 0` on the element plus a forwards-filled
 * animation to raise it — is what this file used to do, and it makes an animation
 * load-bearing for whether text exists on screen. Anything that stops the animation
 * running then produces a blank page rather than a static one. That is not
 * hypothetical: headless Chrome rendered this site as an empty dark rectangle, and
 * a reduced-motion rule, a throttled background tab, an extension that disables
 * animations, or a keyframe name typo all do the same thing.
 *
 * With the hidden state in the keyframe instead, an environment that skips the
 * animation shows the finished layout immediately. Worst case is no animation, which
 * is a cosmetic loss; the old worst case was an invisible product page.
 */

/* ── Hero ──────────────────────────────────────────────── */
/* The mark, the title, the sentence, the two buttons. Nothing else.
   The `.eyebrow` pill that read NO FILTERS · NO CAPS · NO LIMITS is gone, and so is
   the `pulse` keyframe that only its status dot ever used — the monochrome pass had
   already stopped the dot animating, so those keyframes had been dead for a while.
   The badge carries the extra breathing room the pill used to provide, so the title
   does not end up sitting under the logo. */
.hero { text-align: center; padding: 90px 0 70px; }

.hero h1 {
    font-size: clamp(34px, 6vw, 58px); font-weight: 700; letter-spacing: -0.03em;
    line-height: 1.12; max-width: 820px; margin: 0 auto 22px;
}
/* The per-word reveal mask. overflow:hidden is what hides the word before it
   slides up, and it also clips anything below the box — which cut the descender off
   the g in "through". padding-bottom alone is not enough to fix it: the padding
   grows the box downward, so the same amount has to come back off the bottom margin
   or every line below shifts down. 0.22em clears a descender at every clamp() size
   in .hero h1. */
.hero h1 .w { display: inline-block; overflow: hidden; vertical-align: bottom;
              padding-bottom: 0.22em; margin-bottom: -0.22em; }
.hero h1 .wi { display: inline-block;
               animation: wordUp 0.85s var(--spring) both; }
@keyframes wordUp { from { transform: translateY(115%); }
                    to   { transform: translateY(0); } }

.hero h1 .grad {
    /* THE ONE COLOUR ON THE PAGE. It was
         linear-gradient(92deg, --accent, --accent-2, #8fd9ff, --accent)
       at 260% width, animated across the word on a 7s alternating loop, painted
       through background-clip:text. A moving rainbow on the first word a visitor
       reads is the single loudest "generated" signal a page can carry.

       Flat colour, no clip, no animation. The descender padding that
       background-clip needed is gone with it -- there is real text colour here now,
       so nothing can be clipped away. */
    color: var(--title);
}

.hero p { color: var(--muted); max-width: 620px; margin: 0 auto 36px; font-size: 16.5px;
          animation: rise 0.8s var(--ease) 0.7s both; }
.cta-row { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap;
           animation: rise 0.8s var(--ease) 0.9s both; }
@keyframes rise { from { opacity: 0; transform: translateY(24px); } to { opacity: 1; transform: translateY(0); } }

/* ── The promotion, in a card ──────────────────────────────
   ONE PLACE ONLY: the Create-key panel, where it answers "what will this key get".

   It was also a bordered box in the hero, directly under Download and Get an access
   key, and that was wrong twice over. `.promo` is (0,1,0) while `.hero p` is (0,1,1),
   so the hero's own paragraph rule won and killed `margin: 26px auto 0` -- measured
   margin-top: 0px, promo top 730, buttons bottom 730. Flush. The box's top border sat
   exactly on the buttons' bottom edge and read as a frame drawn through them.

   Not re-spaced, removed: a bordered announcement immediately below the two primary
   buttons competes with them however much air it gets, and the "FREE UNTIL 2027" tag
   on the pricing card already states it where pricing is read. No selector here is
   hero- or section-scoped any more, so it cannot be reintroduced by accident.

   No animation and no width cap: the card it lives in has its own entrance. */
.promo {
    margin: 0 0 18px; padding: 11px 14px;
    border: 1px solid var(--border-soft);
    border-left: 2px solid rgba(255, 255, 255, 0.55);
    border-radius: var(--radius);
    background: var(--panel);
    color: var(--muted); font-size: 13px; line-height: 1.6; text-align: left;
}
.promo strong { color: var(--text); font-weight: 650; }

/* ── "Stay signed in" ──────────────────────────────────────
   The key is the whole account, so this decides whether an unrecoverable credential
   is written to localStorage. It is ticked by default because the alternative was
   re-pasting it on every visit, but it is visible and it is one click to refuse. */
.remember {
    display: flex; align-items: center; gap: 9px; margin-top: 15px;
    font-size: 13px; color: var(--muted); cursor: pointer; user-select: none;
}
.remember input { width: 15px; height: 15px; flex: none; accent-color: var(--accent);
                  cursor: pointer; margin: 0; }
.remember:hover { color: var(--text); }
.remember-note { margin: 7px 0 0; font-size: 12px; color: var(--faint);
                 line-height: 1.55; }

/* ── Buttons ───────────────────────────────────────────── */
.btn {
    position: relative; overflow: hidden;
    display: inline-flex; align-items: center; justify-content: center; gap: 9px;
    padding: 11px 22px; border-radius: 10px; border: 1px solid transparent;
    font-size: 14px; font-weight: 600; font-family: var(--sans);
    cursor: pointer; text-decoration: none; color: #fff;
    transition: transform 0.35s var(--spring), box-shadow 0.35s, border-color 0.35s, color 0.3s;
}
.btn svg { width: 15px; height: 15px; flex: none; transition: transform 0.35s var(--spring); }
.btn.primary {
    /* White fill, black text. It was a blue-to-cyan gradient with a
       0 4px 22px coloured shadow that grew to 0 10px 34px on hover while the button
       lifted 2px and scaled 1.02. Now: a solid rectangle that changes brightness.
       This is the pairing every restrained product interface uses, and it cannot be
       mistaken for an advert. */
    background: var(--accent);
    color: #0a0a0b;
    border-color: var(--accent);
}
.btn.primary:hover { background: #d9d9dc; border-color: #d9d9dc; }
.btn.primary:active { background: #c4c4c8; }

.btn.ghost {
    background: transparent; border-color: var(--border); color: var(--text);
}
/* The hover went to --accent for both border AND text, which when --accent was blue
   turned the label blue. White border, white text, a faint wash. */
.btn.ghost:hover { border-color: rgba(255,255,255,0.34);
                   background: rgba(255,255,255,0.045); }

.btn.small { padding: 7px 13px; font-size: 12.5px; border-radius: 8px; }
.btn.danger { background: transparent; border-color: color-mix(in srgb, var(--bad) 45%, transparent); color: var(--bad); }
.btn.danger:hover { background: color-mix(in srgb, var(--bad) 12%, transparent); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none !important; }

/* ── Features ──────────────────────────────────────────── */
/* ── Reasons ────────────────────────────────────────────
   The three reasons someone wants this at all: unlimited, uncapped, unfiltered.
   Above the feature grid and visually heavier than it, because the grid answers
   "is this any good" while these answer "why would I want it" — which is the
   question a visitor actually arrives with. Same entrance as .feature so the page
   still reads as one thing. */
.reasons { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px;
           margin-top: 46px; }
.reason {
    border: 1px solid var(--border); border-radius: var(--radius);
    background: var(--panel);
    padding: 26px 24px 24px;
    animation: featIn 0.8s var(--ease) both;
    transition: transform 0.45s var(--spring), border-color 0.45s, box-shadow 0.45s;
}
.reason:nth-child(1) { animation-delay: 0.95s; }
.reason:nth-child(2) { animation-delay: 1.08s; }
.reason:nth-child(3) { animation-delay: 1.21s; }
.reason:hover { transform: translateY(-6px) scale(1.015); border-color: var(--accent);
                box-shadow: none; }
.reason svg { width: 24px; height: 24px; margin-bottom: 14px; }
.reason svg path, .reason svg rect, .reason svg circle { stroke: var(--accent-2); }
.reason h3 { font-size: 18px; margin-bottom: 8px; font-weight: 700;
             letter-spacing: -0.015em; }
.reason p { font-size: 13.5px; color: var(--muted); line-height: 1.6; }
@media (max-width: 860px) { .reasons { grid-template-columns: 1fr; } }

.features { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-top: 26px; }
.feature {
    border: 1px solid var(--border-soft); border-radius: var(--radius);
    background: var(--panel);
    padding: 24px 22px 22px;
    animation: featIn 0.8s var(--ease) both;
    transition: transform 0.45s var(--spring), border-color 0.45s, box-shadow 0.45s;
}
.feature:nth-child(1) { animation-delay: 1.05s; }
.feature:nth-child(2) { animation-delay: 1.20s; }
.feature:nth-child(3) { animation-delay: 1.35s; }
.feature:nth-child(4) { animation-delay: 1.50s; }
.feature:nth-child(5) { animation-delay: 1.65s; }
.feature:nth-child(6) { animation-delay: 1.80s; }
@keyframes featIn { from { opacity: 0; transform: translateY(34px); }
                    to   { opacity: 1; transform: translateY(0); } }

.feature:hover { transform: translateY(-6px) scale(1.015); border-color: var(--border);
                 box-shadow: 0 18px 44px rgba(0,0,0,0.35); }
.feature svg { width: 20px; height: 20px; margin-bottom: 14px; }
.feature svg path, .feature svg rect, .feature svg circle { stroke: var(--accent); }
.feature h3 { font-size: 15px; margin-bottom: 6px; font-weight: 600; }
.feature p { font-size: 13px; color: var(--muted); line-height: 1.55; }

/* ── Section Headers ───────────────────────────────────── */
.section-head { text-align: center; margin: 84px 0 38px; }
.section-head h2 { font-size: 27px; font-weight: 700; letter-spacing: -0.02em; }
.section-head p { color: var(--muted); margin-top: 8px; font-size: 14.5px; }

/* ── Plans ─────────────────────────────────────────────── */
.pricing { display: grid; grid-template-columns: repeat(2, 1fr); gap: 18px; align-items: stretch;
           max-width: 720px; margin: 0 auto; }
.plan {
    position: relative; display: flex; flex-direction: column;
    border: 1px solid var(--border-soft); border-radius: 16px; padding: 28px 26px 24px;
    background: var(--panel);
    animation: planIn 0.85s var(--spring) both;
    transition: transform 0.5s var(--spring), border-color 0.5s, box-shadow 0.5s;
}
.plan:nth-child(1) { animation-delay: 0.1s; }
.plan:nth-child(2) { animation-delay: 0.26s; }
.plan:nth-child(3) { animation-delay: 0.42s; }
@keyframes planIn { from { opacity: 0; transform: translateY(38px) scale(0.97); }
                    to   { opacity: 1; transform: translateY(0) scale(1); } }

.plan:hover { transform: translateY(-8px) scale(1.015); border-color: var(--border);
              box-shadow: 0 24px 54px rgba(0,0,0,0.4); }
.plan.popular {
    border-color: rgba(255, 255, 255, 0.30);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.10);
}
.plan.popular:hover { box-shadow: 0 0 0 1px rgba(255,255,255,.18); }
.plan .tag {
    position: absolute; top: -11px; left: 50%; transform: translateX(-50%);
    font-size: 10.5px; letter-spacing: 0.16em; text-transform: uppercase;
    /* White FILL needs dark INK. The blanket --accent -> white change filled this
       pill white while its text stayed white, so it read as an empty white lozenge.
       Caught in a screenshot, not in the diff. */
    background: var(--accent); color: #0a0a0b;
    padding: 4px 12px; border-radius: 999px; font-weight: 700; white-space: nowrap;
}
/* tagGlow pulsed a badge between a blue and a cyan halo. Removed; the badge is
   a bordered label now and does not need to throb to be noticed. */
@keyframes tagGlow { from { opacity: 1; } to { opacity: 1; } }

.plan h3 { font-size: 15px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); font-weight: 600; }
.plan .price { font-size: 34px; font-weight: 700; margin: 12px 0 2px; letter-spacing: -0.02em; }
.plan .price small { font-size: 13px; color: var(--faint); font-weight: 400; }
.plan ul { list-style: none; margin: 18px 0 26px; display: grid; gap: 10px; flex: 1; }
.plan li { display: flex; gap: 10px; align-items: flex-start; font-size: 13.5px; color: var(--muted);
           animation: liIn 0.5s var(--ease) both; }
.plan li:nth-child(1) { animation-delay: 0.55s; }
.plan li:nth-child(2) { animation-delay: 0.68s; }
.plan li:nth-child(3) { animation-delay: 0.81s; }
.plan li:nth-child(4) { animation-delay: 0.94s; }
.plan li:nth-child(5) { animation-delay: 1.07s; }
@keyframes liIn { from { opacity: 0; transform: translateX(-12px); } to { opacity: 1; transform: translateX(0); } }

.plan li svg { width: 14px; height: 14px; margin-top: 3.5px; flex: none; }
.plan li svg path { stroke: var(--good); }
.free-note {
    margin-top: 22px; text-align: center; color: var(--faint); font-size: 13px;
    border: 1px dashed var(--border-soft); border-radius: var(--radius); padding: 16px;
    max-width: 720px; margin-left: auto; margin-right: auto;
}
.buy-note { text-align: center; color: var(--muted); font-size: 13.5px; margin-top: 26px; }
.buy-note a { color: var(--accent); text-decoration: none; border-bottom: 1px solid rgba(255,255,255,.35); }

/* ── Cards & Panels ────────────────────────────────────── */
.card {
    border: 1px solid var(--border-soft); border-radius: var(--radius);
    background: var(--panel);
    padding: 26px;
    animation: cardIn 0.7s var(--spring) both;
}
@keyframes cardIn { from { opacity: 0; transform: translateY(26px) scale(0.97); }
                    to   { opacity: 1; transform: translateY(0) scale(1); } }
.card h3 { font-size: 15px; font-weight: 600; margin-bottom: 4px; }
.card .sub { font-size: 13px; color: var(--muted); margin-bottom: 18px; }

/* ── Auth Forms ────────────────────────────────────────── */
.auth-wrap { max-width: 460px; margin: 30px auto 0; }
.auth-tabs { display: flex; gap: 6px; margin-bottom: 22px; background: var(--panel);
    padding: 4px; border-radius: 10px; border: 1px solid var(--border-soft); }
.auth-tabs button {
    flex: 1; padding: 9px; background: none; border: none; color: var(--muted);
    font-family: var(--sans); font-size: 13.5px; font-weight: 600; cursor: pointer;
    border-radius: 8px; transition: all 0.35s var(--spring); z-index: 1;
}
.auth-tabs button.on { background: var(--bg-2); color: var(--text); box-shadow: 0 2px 10px rgba(0,0,0,0.35); }
.field { margin-bottom: 16px; }
.field label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 7px;
    letter-spacing: 0.04em; text-transform: uppercase; }
.field input {
    width: 100%; background: var(--bg-2); border: 1px solid var(--border);
    color: var(--text); border-radius: 10px; padding: 12px 14px; font-size: 14px;
    font-family: var(--mono); transition: border-color 0.35s, box-shadow 0.35s, transform 0.35s var(--ease);
}
.field input:focus { outline: none; border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.13); }

/* ── Account Key Modal ─────────────────────────────────── */
.modal-veil {
    position: fixed; inset: 0; z-index: 90; display: none;
    background: rgba(3, 5, 10, 0.78); backdrop-filter: blur(8px);
    align-items: center; justify-content: center; padding: 20px;
}
.modal-veil.on { display: flex; animation: fadeIn 0.4s var(--ease); }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

.modal {
    width: min(620px, calc(100vw - 40px)); border: 1px solid var(--border);
    border-radius: var(--radius); background: var(--bg-2);
    padding: 30px; animation: modalIn 0.65s var(--spring);
    box-shadow: 0 30px 90px rgba(0, 0, 0, 0.6);
}
@keyframes modalIn { from { opacity: 0; transform: translateY(34px) scale(0.93); }
                     to   { opacity: 1; transform: translateY(0) scale(1); } }
.modal h3 { font-size: 17px; margin-bottom: 6px; }
.modal .warn { color: var(--warn); font-size: 13px; margin: 14px 0 18px;
    display: flex; gap: 9px; align-items: flex-start; }
.modal .warn svg { width: 15px; height: 15px; margin-top: 2px; flex: none;  }
.modal .warn svg path { stroke: var(--warn); }
@keyframes wiggle { 0%, 88%, 100% { transform: rotate(0); } 92% { transform: rotate(9deg); } 96% { transform: rotate(-8deg); } }

.key-box {
    font-family: var(--mono); font-size: 12.5px; line-height: 1.8;
    background: var(--bg); border: 1px solid var(--border); border-radius: 12px;
    padding: 16px; word-break: break-all; color: var(--accent);
    user-select: all; margin-bottom: 18px;
    background-image: linear-gradient(90deg, var(--bg) 30%, rgba(255,255,255,.07) 50%, var(--bg) 70%);
    background-size: 200% 100%;
    animation: keyScan 1.4s ease .3s backwards;
}
@keyframes keyScan { from { background-position: -200% 0; } to { background-position: 200% 0; } }

/* ── Dashboard ─────────────────────────────────────────── */
.dash { display: grid; grid-template-columns: 340px 1fr; gap: 18px; align-items: start; }
.dash .card:nth-child(2) { animation-delay: 0.12s; }

.plan-badge {
    display: inline-flex; align-items: center; gap: 8px; font-size: 11px;
    letter-spacing: 0.14em; text-transform: uppercase; font-weight: 700;
    border-radius: 999px; padding: 5px 13px; margin-bottom: 14px;
    animation: chipIn 0.55s var(--spring) 0.15s backwards;
}
.plan-badge.none      { color: var(--bad);    border: 1px solid color-mix(in srgb, var(--bad) 45%, transparent); }
.plan-badge.free_only { color: var(--warn);   border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent); }
.plan-badge.all       { color: var(--good);   border: 1px solid var(--border); }
.plan-badge.admin     { color: var(--accent-2); border: 1px solid var(--border); }

.stat-row { display: flex; justify-content: space-between; gap: 12px; font-size: 13px;
    color: var(--muted); padding: 9px 0; border-bottom: 1px solid var(--border-soft);
    animation: liIn 0.5s var(--ease) both; }
.stat-row:nth-of-type(1) { animation-delay: 0.2s; }
.stat-row:nth-of-type(2) { animation-delay: 0.3s; }
.stat-row:nth-of-type(3) { animation-delay: 0.4s; }
.stat-row:nth-of-type(4) { animation-delay: 0.5s; }
.stat-row:nth-of-type(5) { animation-delay: 0.6s; }
.stat-row:last-child { border-bottom: none; }
.stat-row b { color: var(--text); font-weight: 600; font-family: var(--mono); font-size: 12.5px; }

.meter { height: 6px; border-radius: 4px; background: var(--panel-strong); overflow: hidden; margin: 8px 0 4px; }
.meter > div { height: 100%; border-radius: 4px;
    background: var(--accent);
    background-size: 200% 100%;
    
    transition: width 1.4s var(--ease); }
@keyframes meterFlow { from { background-position: 0% 0; } to { background-position: 100% 0; } }

.locked { position: relative; overflow: hidden; }
.locked .veil {
    position: absolute; inset: 0; z-index: 5; display: flex; flex-direction: column;
    align-items: center; justify-content: center; gap: 14px; padding: 20px;
    background: rgba(10, 10, 11, 0.9);
    backdrop-filter: blur(7px); border-radius: var(--radius);
}
.locked .veil svg { width: 22px; height: 22px; }
.locked .veil svg path, .locked .veil svg rect { stroke: var(--warn); }
@keyframes floaty { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-6px); } }
.locked .veil p { color: var(--muted); font-size: 13.5px; max-width: 340px; text-align: center; }

/* ── Download Panels ──────────────────────────────────── */
.os-tabs { display: inline-flex; gap: 4px; padding: 4px; border-radius: 11px;
    background: var(--panel); border: 1px solid var(--border-soft); margin-bottom: 18px; }
.os-tabs button { padding: 8px 18px; border-radius: 8px; border: 0; background: transparent;
    color: var(--muted); font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
    display: inline-flex; align-items: center; gap: 8px;
    transition: all 0.35s var(--spring); }
.os-tabs button svg { width: 14px; height: 14px; }
.os-tabs button svg path, .os-tabs button svg rect { stroke: currentColor; }
.os-tabs button.on { background: var(--bg-2); color: var(--text); box-shadow: 0 2px 10px rgba(0,0,0,.35); }

.code {
    position: relative; font-family: var(--mono); font-size: 13px; text-align: left;
    background: var(--bg); border: 1px solid var(--border); border-radius: 11px;
    padding: 15px 96px 15px 17px; color: var(--text); overflow-x: auto; white-space: pre;
    animation: cardIn 0.6s var(--spring) both;
}
.code .prompt { color: var(--faint); user-select: none; }
.copy {
    position: absolute; top: 9px; right: 9px; padding: 6px 11px; font-size: 11px;
    border-radius: 7px; border: 1px solid var(--border); background: var(--panel-strong);
    color: var(--muted); cursor: pointer; font-family: var(--sans);
    transition: all 0.25s var(--spring);
}
.copy:hover { color: var(--text); border-color: var(--accent); transform: translateY(-1px); }
.copy.done { color: var(--good); border-color: var(--good); }

/* ── Downloads ─────────────────────────────────────────────
   A RELEASE LIST, NOT A CALL TO ACTION.

   These were two cards side by side, each ending in a full-width blue-to-cyan
   gradient button reading "Download .exe · 31.3 MB". That is, precisely, the shape
   of the fake download button on an advert-funded file host — and the one place on
   this whole site where looking like that does real damage, because what is behind
   it is an installer that asks for Administrator.

   So it reads like a release page instead: one row per build, the filename in
   monospace, the size and the FULL SHA-256 laid out as facts, and an ordinary
   bordered link on the right. The checksum is the thing that earns trust here, so it
   gets room rather than being truncated to 32 characters and greyed out.

   Rows, not a 2-up grid: a row can hold a 64-character hash on one line, and a list
   of files is a list, not a pair of adverts. */
.dl-grid { display: flex; flex-direction: column; gap: 0; margin-top: 18px;
           border: 1px solid var(--border-soft); border-radius: var(--radius);
           overflow: hidden; }
.dl {
    display: grid; grid-template-columns: 1fr auto; align-items: start;
    gap: 4px 24px; padding: 18px 20px;
    border-bottom: 1px solid var(--border-soft);
    background: var(--panel);
    animation: featIn 0.7s var(--ease) both;
}
.dl:last-child { border-bottom: 0; }
.dl:nth-child(1) { animation-delay: 0.06s; }
.dl:nth-child(2) { animation-delay: 0.14s; }
.dl:nth-child(3) { animation-delay: 0.22s; }
.dl:hover { background: var(--panel-strong); }
/* The icon carried stroke: var(--accent) and sat above the title like a feature
   badge. It is a small neutral glyph in the row now, or nothing at all. */
.dl .ic { display: none; }
.dl h4 { font-size: 14.5px; font-weight: 600; grid-column: 1; }
.dl .why { font-size: 12.5px; color: var(--muted); margin: 3px 0 0; grid-column: 1;
           line-height: 1.55; max-width: 62ch; }
/* The action sits in column 2, spanning both rows, so it is vertically centred
   against the text rather than stranded under it. */
.dl .btn { grid-column: 2; grid-row: 1 / span 3; align-self: center;
           white-space: nowrap; }
/* The file facts. A definition list in monospace: name, size, hash. This is what a
   cautious person actually wants to see before running an installer. */
.dl .facts { grid-column: 1; margin-top: 12px; display: grid;
             grid-template-columns: auto 1fr; gap: 3px 14px;
             font-family: var(--mono); font-size: 11.5px; }
.dl .facts dt { color: var(--faint); }
.dl .facts dd { color: var(--muted); margin: 0; word-break: break-all;
                user-select: all; }
.dl .meta { font-family: var(--mono); font-size: 11px; color: var(--faint);
            margin-top: 10px; grid-column: 1; word-break: break-all; }
@media (max-width: 780px) {
    .dl { grid-template-columns: 1fr; }
    .dl .btn { grid-column: 1; grid-row: auto; justify-self: start; margin-top: 12px; }
}

/* ── Admin ─────────────────────────────────────────────── */
.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-bottom: 20px; }
.stat { border: 1px solid var(--border-soft); border-radius: 12px; padding: 18px 20px;
    background: var(--panel);
    animation: planIn 0.7s var(--spring) both;
    transition: transform 0.4s var(--spring), border-color 0.4s; }
.stat:nth-child(1) { animation-delay: 0.05s; }
.stat:nth-child(2) { animation-delay: 0.15s; }
.stat:nth-child(3) { animation-delay: 0.25s; }
.stat:nth-child(4) { animation-delay: 0.35s; }
.stat:hover { transform: translateY(-3px); border-color: var(--border); }
.stat .num { font-size: 26px; font-weight: 700; font-family: var(--mono); letter-spacing: -0.02em; }
.stat .lbl { font-size: 11px; color: var(--faint); letter-spacing: 0.14em; text-transform: uppercase; margin-top: 2px; }

.table-wrap { border: 1px solid var(--border-soft); border-radius: var(--radius); overflow: auto; }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th {
    text-align: left; padding: 11px 14px; color: var(--faint); font-size: 10.5px;
    letter-spacing: 0.14em; text-transform: uppercase; font-weight: 600;
    border-bottom: 1px solid var(--border-soft); background: var(--panel);
    white-space: nowrap;
}
td { padding: 10px 14px; border-bottom: 1px solid var(--border-soft); color: var(--muted); vertical-align: middle; }
tr:last-child td { border-bottom: none; }
tbody tr { transition: background 0.25s, transform 0.3s var(--ease); }
tbody tr:hover { background: var(--panel); }
tbody tr.rowin { animation: liIn 0.45s var(--ease) both; }
td .mono, .mono { font-family: var(--mono); font-size: 11.5px; }
td b { color: var(--text); font-weight: 600; }
.row-actions { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
select.sel {
    background: var(--bg-2); color: var(--text); border: 1px solid var(--border);
    border-radius: 7px; padding: 6px 9px; font-size: 12px; font-family: var(--sans); cursor: pointer;
}
.icon-btn {
    background: none; border: 1px solid var(--border); border-radius: 7px; color: var(--muted);
    padding: 6px 9px; cursor: pointer; font-size: 12px; font-family: var(--sans);
    transition: all 0.25s var(--spring);
}
.icon-btn:hover { color: var(--text); border-color: var(--accent); transform: translateY(-1px); }
.icon-btn.del:hover { color: var(--bad); border-color: var(--bad); }

.admin-form { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; }
.admin-form .field { margin: 0; }
.admin-form .field input, .admin-form .field select { padding: 9px 11px; font-size: 13px; }
.admin-form .field select {
    background: var(--bg-2); color: var(--text); border: 1px solid var(--border);
    border-radius: 10px; font-family: var(--sans); width: 100%; cursor: pointer;
}
/* A date input has to be told to use the dark surface or it renders white. */
.admin-form .field input[type=date] { font-family: var(--sans); color-scheme: dark; }

/* ── Grant editor ─────────────────────────────────────────
   Two stages, shown as two columns, because that is literally the model: a tier
   for a period, then a fallback tier for a period, then nothing. */
.grant-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin: 4px 0 6px; }
.grant-stage { border: 1px solid var(--border-soft); border-radius: 12px; padding: 16px;
    background: var(--panel); }
.grant-stage h5 { font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase;
    color: var(--muted); margin-bottom: 12px; font-weight: 600;
    display: flex; align-items: center; gap: 8px; }
.grant-stage h5 .n { width: 18px; height: 18px; border-radius: 50%; flex: none;
    display: grid; place-items: center; font-size: 10px; color: var(--accent);
    border: 1px solid rgba(255,255,255,.28); letter-spacing: 0; }
.grant-arrow { display: grid; place-items: center; color: var(--faint); font-size: 18px; }
.grant-preview { font-size: 12.5px; color: var(--muted); border: 1px dashed var(--border-soft);
    border-radius: 10px; padding: 12px 14px; margin-top: 12px; line-height: 1.6; }
.grant-preview b { color: var(--text); font-weight: 600; }
.hint { font-size: 11.5px; color: var(--faint); margin-top: 5px; }

/* Radio-style mode switch (days vs a fixed date). */
.seg { display: inline-flex; padding: 3px; border-radius: 9px; background: var(--bg-2);
    border: 1px solid var(--border); }
.seg button { padding: 6px 12px; border: 0; background: transparent; color: var(--muted);
    font: inherit; font-size: 12px; border-radius: 7px; cursor: pointer;
    transition: all 0.3s var(--spring); }
.seg button.on { background: var(--panel-strong); color: var(--text); }

/* The mass-operation card is the one control that can change every account. */
.danger-zone { border-color: color-mix(in srgb, var(--warn) 38%, transparent) !important; }
.danger-zone h3 { color: var(--warn); }
.affected { font-family: var(--mono); font-size: 22px; color: var(--text); }

/* ── Toasts ────────────────────────────────────────────── */
/* ── toasts ────────────────────────────────────────────────────────────────
   Matched to the app's, so the product looks like one thing. Three changes from
   what was here: the background was a hardcoded #0d1220 that ignored every token
   around it, there was no way to dismiss one, and the entrance was a 0.6s spring
   that overshot by 6px — lively the first time and irritating by the fifth.

   --tone carries severity to the edge, the icon badge and the timer bar from a
   single declaration. */
.toasts { position: fixed; right: 22px; bottom: 22px; z-index: 99;
    display: flex; flex-direction: column; gap: 10px; align-items: flex-end;
    pointer-events: none; }
.toast {
    --tone: var(--accent);
    position: relative; overflow: hidden; pointer-events: auto;
    display: grid; grid-template-columns: auto 1fr auto; align-items: flex-start;
    gap: 11px;
    /* One width for every card -- min/max sized each to its own text, which
       right-aligned into a ragged staircase. */
    width: 344px; box-sizing: border-box;
    padding: 11px 11px 12px 13px;
    background: color-mix(in srgb, var(--bg-2) 88%, var(--tone));
    /* 85%, not 60%: a bright --warn ringed the whole card and shouted over the
       rest. The icon and the left edge carry severity. */
    border: 1px solid color-mix(in srgb, var(--border) 85%, var(--tone));
    border-radius: 12px; box-shadow: 0 14px 40px rgba(0, 0, 0, 0.5);
    font-size: 13.5px; line-height: 1.45; color: var(--text);
    backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
    animation: toastIn 0.2s var(--ease) both;
}
/* A border-left fought the radius and showed a square corner against the blur. */
.toast::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0;
    width: 3px; background: var(--tone); }
.toast .ti { flex: none; width: 22px; height: 22px; border-radius: 50%;
    display: grid; place-items: center; margin-top: -1px; color: var(--tone);
    background: color-mix(in srgb, var(--tone) 18%, transparent); }
.toast .ti svg { width: 13px; height: 13px; fill: none; stroke: currentColor;
    stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.toast .tx { padding-top: 2px; overflow-wrap: anywhere; }
.toast .tn { margin-left: 6px; padding: 1px 6px; border-radius: 8px;
    font-size: 11px; font-weight: 600; color: var(--tone);
    background: color-mix(in srgb, var(--tone) 16%, transparent); }
.toast .tc { flex: none; width: 22px; height: 22px; border-radius: 6px; border: 0;
    padding: 0; cursor: pointer; background: transparent; color: var(--muted);
    opacity: 0.5; display: grid; place-items: center;
    transition: opacity 0.15s, background 0.15s; }
.toast .tc:hover { opacity: 1; background: var(--panel-strong); }
.toast .tc svg { width: 11px; height: 11px; fill: none; stroke: currentColor;
    stroke-width: 2.6; stroke-linecap: round; }
/* Counts down the auto-dismiss, so a card that vanishes does not read as a
   glitch. Paused on hover so reading does not race the timer. */
.toast .tb { position: absolute; left: 0; bottom: 0; height: 2px; width: 100%;
    transform-origin: left; background: var(--tone); opacity: 0.5;
    animation: toastDrain linear both; }
.toast:hover .tb { animation-play-state: paused; }
.toast.ok   { --tone: var(--good); }
.toast.err  { --tone: var(--bad); }
.toast.warn { --tone: var(--warn); }
.toast.out  { animation: toastOut 0.16s ease both; }
@keyframes toastIn {
    from { opacity: 0; transform: translateY(8px) scale(0.97); }
    to   { opacity: 1; transform: none; }
}
@keyframes toastOut {
    from { opacity: 1; transform: none; }
    to   { opacity: 0; transform: translateY(4px) scale(0.98); }
}
@keyframes toastDrain { from { transform: scaleX(1); } to { transform: scaleX(0); } }
@media (prefers-reduced-motion: reduce) {
    .toast { animation-duration: 0.01s; }
    .toast .tb { animation: none; opacity: 0; }
}

.divider { height: 1px; background: var(--border-soft); margin: 22px 0; }
.spinner { display: inline-block; width: 13px; height: 13px; border-radius: 50%;
    border: 2px solid var(--border); border-top-color: var(--accent);
    animation: spin 0.8s linear infinite; vertical-align: -2px; }
@keyframes spin { to { transform: rotate(360deg); } }

footer { border-top: 1px solid var(--border-soft); margin-top: 60px; padding: 28px 24px;
    color: var(--faint); font-size: 12.5px; }
footer .inner { max-width: 1080px; margin: 0 auto; display: flex; gap: 16px;
    flex-wrap: wrap; align-items: center; }
footer a { color: var(--muted); text-decoration: none; }
footer a:hover { color: var(--text); }
footer .grow { flex: 1; }

[hidden] { display: none !important; }

/* ── Responsive ────────────────────────────────────────── */
@media (max-width: 860px) {
    .features, .pricing, .stats, .dl-grid { grid-template-columns: 1fr; }
    .dash { grid-template-columns: 1fr; }
    .nav-links a { padding: 7px 8px; font-size: 12.5px; }
    .nav-user .chip { display: none; }
    main { padding: 92px 18px 70px; }
}

/* ── Ported from the template: motion we had left behind ───
 *
 * Four animations existed in the template's stylesheet and had no counterpart
 * here. They were not decoration there — each marked a moment in a flow, and this
 * site has the same moments. They are ported to the equivalent moment rather than
 * bolted onto whatever was nearby.
 *
 *   sweep     -> the template's indeterminate upload bar. Here: any API request in
 *                flight. This site talks to the API on nearly every interaction and
 *                had no way to say "working", so a slow request read as a dead click.
 *   stageIn   -> the template's per-stage pipeline reveal. Here: a panel that has
 *                just re-rendered from fresh data. renderAdmin()/renderApp() replace
 *                innerHTML wholesale, and an instant swap is indistinguishable from
 *                nothing having happened.
 *   resultPop -> the template's result card. Here: the outcome of a mass operation
 *                and of a preview, which are the two riskiest actions in the panel.
 *   drawCheck -> the check inside that result, stroked on rather than popped in.
 *   breathe   -> the template's idle dropzone, inviting the one action it wants.
 *                Here: the key box, holding a token that cannot be recovered once
 *                the modal closes.
 */

/* An indeterminate progress bar pinned under the nav, shown while requests are in
   flight. Indeterminate rather than percentage-based because the API returns no
   progress information, and a fake percentage is a lie about what is known. */
.progress-track {
    position: fixed; top: 62px; left: 0; right: 0; height: 2px; z-index: 60;
    overflow: hidden; pointer-events: none;
    opacity: 0; transition: opacity 0.25s var(--ease);
}
.progress-track.on { opacity: 1; }
.progress-track > i {
    position: absolute; inset: 0; display: block; width: 34%;
    background: var(--border);
    animation: sweep 1.5s ease-in-out infinite;
}
@keyframes sweep { from { transform: translateX(-110%); } to { transform: translateX(300%); } }

/* A re-rendered panel. Deliberately small and quick: it fires on every mutation in
   the admin panel, so anything longer than this becomes an obstruction. */
.stage-anim { animation: stageIn 0.45s var(--ease); }
@keyframes stageIn { from { opacity: 0; transform: translateY(8px); }
                     to   { opacity: 1; transform: translateY(0); } }

/* The outcome of a mass operation. Green-tinted, because the two states it reports
   are "here is what would change" and "done" — the destructive confirmation happens
   before this, in a dialog. */
.result {
    display: none; margin-top: 14px;
    border: 1px solid var(--border); border-radius: var(--radius);
    padding: 14px 16px; background: var(--panel);
}
.result.on { display: block; animation: resultPop 0.7s var(--spring); }
.result .head {
    display: flex; align-items: center; gap: 9px;
    font-weight: 600; font-size: 13.5px; margin-bottom: 4px;
}
.result .head svg { width: 16px; height: 16px; flex: none; }
.result .head svg path {
    stroke: var(--good); stroke-width: 2.4; fill: none;
    stroke-linecap: round; stroke-linejoin: round;
    /* dashoffset 0, not 26: the tick is drawn by default and the keyframe hides it
       to start with, so a skipped animation shows a complete tick rather than an
       empty box. Same rule as every entrance above. */
    stroke-dasharray: 26; stroke-dashoffset: 0;
}
.result.on .head svg path { animation: drawCheck 0.65s ease 0.18s both; }
.result .meta { font-size: 12.5px; color: var(--muted); line-height: 1.55; }
.result .meta b { color: var(--text); font-weight: 500; }
@keyframes resultPop { from { opacity: 0; transform: scale(0.94) translateY(12px); }
                       60%  { transform: scale(1.015) translateY(0); }
                       to   { opacity: 1; transform: scale(1); } }
@keyframes drawCheck { from { stroke-dashoffset: 26; } to { stroke-dashoffset: 0; } }

/* The key box breathes while it is unread. The token behind it is shown exactly
   once and cannot be recovered, so the border keeps asking to be acted on until
   the visitor copies it — at which point .taken stops the breathing, because the
   ask has been answered. */
.key-box { animation: keyScan 1.4s ease .3s backwards; }
.key-box.taken { animation: none; border-color: rgba(255,255,255,.30); }
@keyframes breathe {
    0%, 100% { border-color: var(--border); }
    50%      { border-color: rgba(255, 255, 255, 0.42); }
}

/* ── Motion policy: animations are always on ───────────────
 *
 * This site does not reduce motion, by decision of its owner. Stated plainly
 * because the omission is deliberate and the usual advice is the opposite.
 *
 * Two earlier attempts are worth recording so neither is repeated.
 *
 * The first was the widely copied "accessible reset":
 *
 *     @media (prefers-reduced-motion: reduce) {
 *       *, *::before, *::after { animation-duration: 0.001ms !important;
 *                                transition-duration: 0.001ms !important; }
 *     }
 *
 * Measured in a real engine, every one of this site's animations resolved to
 * 0.001 ms — attached, named, and imperceptible. It did not reduce motion, it
 * deleted it, and the site read as completely static to anyone whose OS asks for
 * reduce. Windows' "Show animations in Windows" switch does exactly that, and it is
 * off on plenty of machines, so this was the default experience rather than an edge
 * case. It also left animation-DELAY untouched, so staggered content still waited up
 * to a second and then popped into place with no movement.
 *
 * The second was a targeted reduction plus a visitor toggle. It worked, but it is
 * not wanted here, and every rule in it was a second code path that could rot.
 *
 * So: one path. If reduced motion is ever reintroduced, note that the entrance
 * animations own their elements' visibility — `.feature` and friends declare
 * `opacity: 0` and rely on a forwards-filled animation to raise it — so
 * `animation: none` is never a safe way to switch one off. Substitute a fade.
 */

/* ── Access-key field ──────────────────────────────────────────────────
   The key is this account's password, so it is entered in a real password field
   inside a real form (see authHtml) and the browser offers to save it. A reveal
   button, because the key is 35 characters, there is no "forgot key" flow, and a
   mistyped character is otherwise indistinguishable from a rejected key. */
.key-field { position: relative; display: flex; align-items: center; }
.key-field input { flex: 1; padding-right: 62px; }
.key-peek {
    position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
    background: none; border: 0; padding: 4px 8px; cursor: pointer;
    font: inherit; font-size: 12px; font-weight: 600; color: var(--muted);
    border-radius: 6px;
}
.key-peek:hover { color: var(--accent); background: rgba(255, 255, 255, 0.05); }

/* ── Legal pages ───────────────────────────────────────
   Long-form prose, so the constraints are different from the rest of the site: a
   measured line length, generous leading, and headings that establish hierarchy
   without shouting. Both documents live in one view and are switched by tabs
   rather than by routes, so a reader can move between them without a page
   transition animating underneath them. */
.legal-wrap { max-width: 760px; margin: 0 auto; }
.legal-tabs { display: flex; gap: 6px; margin-bottom: 28px;
              border-bottom: 1px solid var(--border); }
.legal-tabs button {
    background: none; border: 0; border-bottom: 2px solid transparent;
    color: var(--muted); font: inherit; font-size: 14px; font-weight: 600;
    padding: 10px 14px; cursor: pointer; margin-bottom: -1px;
    transition: color 0.2s, border-color 0.2s;
}
.legal-tabs button:hover { color: var(--text); }
.legal-tabs button.on { color: var(--accent); border-bottom-color: var(--accent); }

.legal-doc { display: none; font-size: 14.5px; line-height: 1.75; color: var(--muted); }
.legal-doc.on { display: block; animation: rise 0.5s var(--ease) both; }
.legal-doc h2 { font-size: 26px; font-weight: 700; letter-spacing: -0.02em;
                color: var(--text); margin-bottom: 6px; }
.legal-doc h3 { font-size: 18px; font-weight: 700; color: var(--text);
                margin: 40px 0 12px; padding-top: 4px; }
.legal-doc h4 { font-size: 15px; font-weight: 600; color: var(--text);
                margin: 26px 0 10px; }
.legal-doc p { margin-bottom: 16px; }
.legal-doc strong { color: var(--text); font-weight: 600; }
.legal-doc a { color: var(--accent); text-decoration: none; }
.legal-doc a:hover { text-decoration: underline; }
.legal-doc code { font-family: var(--mono); font-size: 12.5px;
                  background: var(--panel-strong); border: 1px solid var(--border-soft);
                  border-radius: 5px; padding: 1px 5px; color: var(--text); }
.legal-doc ul { margin: 0 0 18px 0; padding-left: 22px; }
.legal-doc li { margin-bottom: 8px; }
.legal-doc hr { border: 0; border-top: 1px solid var(--border); margin: 32px 0; }
/* The one call-out in the privacy policy: the exit-node disclosure. It is set apart
   because it is the paragraph a reader most needs to actually read. */
.legal-doc blockquote {
    margin: 22px 0; padding: 16px 20px; border-left: 3px solid var(--accent);
    background: var(--panel); border-radius: 0 10px 10px 0;
}
.legal-doc blockquote p:last-child { margin-bottom: 0; }
.legal-tablewrap { overflow-x: auto; margin: 0 0 22px; }
.legal-doc table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.legal-doc th, .legal-doc td { text-align: left; padding: 9px 12px;
                               border-bottom: 1px solid var(--border-soft);
                               vertical-align: top; }
.legal-doc th { color: var(--text); font-weight: 600; white-space: nowrap;
                border-bottom-color: var(--border); }
@media (max-width: 640px) {
    .legal-doc h2 { font-size: 22px; }
    .legal-tabs button { padding: 10px 8px; font-size: 13px; }
}
