/* Page-specific styles. All design tokens live in theme.css: never redeclare
   a token here, because this sheet loads after theme.css at equal
   specificity and would silently win over :root. */

/* ════════════════════════════════════════════════════════════════════ */
/* Landing page                                                          */
/* ════════════════════════════════════════════════════════════════════ */

/* Hero cipher scramble: landing.ts flips characters through a cipher
   alphabet before resolving each into place. The element just needs stable
   metrics so the text doesn't jitter while scrambling. */
.cipher-text {
  font-variant-ligatures: none;
  font-feature-settings: "tnum";
}
.cipher-char {
  display: inline-block;
}
/* The floor is only needed while glyphs are still swapping — holding it
   afterwards pads narrow letters and visibly splits the settled word. */
.cipher-char.is-scrambling {
  min-width: 0.32em;
  opacity: 0.85;
}

/* ════════════════════════════════════════════════════════════════════ */
/* Ambient background                                                    */
/* Public pages sit on a lit surface rather than flat paint: a faint     */
/* engineering grid, film grain, and slow-drifting colour. The canvas is */
/* one fixed layer behind everything (z-index -1, above the body paint), */
/* so no section needs its own backdrop and nothing reflows on scroll.   */
/* ════════════════════════════════════════════════════════════════════ */

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

/* Grid: masked to a soft ellipse so it never reaches the page edges. */
.site-canvas::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(var(--grid-line) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
  background-size: 72px 72px;
  -webkit-mask-image: radial-gradient(ellipse 105% 95% at 50% 38%, #000 25%, transparent 100%);
  mask-image: radial-gradient(ellipse 105% 95% at 50% 38%, #000 25%, transparent 100%);
}

/* Grain: kills the banding that big blurred gradients produce on dark
   backgrounds, and gives the surface a texture. */
.site-canvas::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0.5;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.09'/%3E%3C/svg%3E");
}

/* App surfaces take the same canvas at half strength on a wider cell: the
   vault is a working surface, so the grid must never compete with content. */
.site-canvas-app::before {
  opacity: 0.5;
  background-size: 104px 104px;
}
.site-canvas-app::after { opacity: 0.3; }

/* Ambient colour. Size and placement are set inline per instance; --orb
   picks the hue from the spectrum tokens. */
.glow-orb {
  position: absolute;
  pointer-events: none;
  border-radius: 50%;
  filter: blur(90px);
  background: radial-gradient(circle, var(--orb, var(--accent-glow)) 0%, transparent 70%);
}

/* Two drift paths, deliberately different lengths so the orbs never
   settle into a visible loop together. */
@keyframes orb-drift-a {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to { transform: translate3d(6%, 8%, 0) scale(1.12); }
}
@keyframes orb-drift-b {
  from { transform: translate3d(0, 0, 0) scale(1.08); }
  to { transform: translate3d(-7%, -5%, 0) scale(0.94); }
}
.glow-orb-a { animation: orb-drift-a 19s var(--ease-in-out) infinite alternate; }
.glow-orb-b { animation: orb-drift-b 24s var(--ease-in-out) infinite alternate; }

/* Encryption pipeline visual: plaintext travels through the mark and
   leaves as ciphertext. Dots are positioned/timed inline per instance. */
@keyframes pipeline-dot {
  0% { opacity: 0; transform: translateX(0); }
  12% { opacity: 1; }
  46% { opacity: 1; transform: translateX(var(--travel, 120px)); }
  54% { opacity: 1; }
  88% { opacity: 1; transform: translateX(calc(var(--travel, 120px) * 2)); }
  100% { opacity: 0; transform: translateX(calc(var(--travel, 120px) * 2.15)); }
}
.pipeline-dot {
  position: absolute;
  font-family: var(--font-mono);
  font-size: 13px;
  animation-name: pipeline-dot;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

/* The mark breathes; used in the hero and the lock screen. */
@keyframes mark-pulse {
  0%, 100% { opacity: 0.55; transform: scale(1); }
  50% { opacity: 1; transform: scale(1.045); }
}
.mark-pulse { animation: mark-pulse 3.4s var(--ease-in-out) infinite; transform-origin: center; }

/* Marquee of "what a breach would see": an endless drift of ciphertext.
   Two lanes run in opposite directions and both fade out at the rails, so
   the strip reads as a window onto a stream rather than a ticker. */
@keyframes drift {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}
@keyframes drift-reverse {
  from { transform: translateX(-50%); }
  to { transform: translateX(0); }
}
.cipher-drift {
  display: inline-block;
  white-space: nowrap;
  animation: drift 42s linear infinite;
}
.cipher-drift-reverse { animation: drift-reverse 58s linear infinite; }

.marquee-mask {
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}

/* ════════════════════════════════════════════════════════════════════ */
/* Marketing sections (landing + security)                               */
/* ════════════════════════════════════════════════════════════════════ */

/* Eyebrow: a short gradient rule, then the label. Replaces the plain
   uppercase line that opened every section. */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--letter-widest);
  color: var(--accent);
}
.eyebrow::before {
  content: "";
  width: 34px;
  height: 2px;
  border-radius: 2px;
  background-image: var(--gradient-brand);
}

/* Numbered step tile inside a .panel — the digit sits in a gradient chip.
   Used where a step number is a label: the whitepaper's section headers. */
.step-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: var(--radius-lg);
  font-family: var(--font-mono);
  font-weight: 700;
  color: var(--accent-foreground);
  background-image: var(--gradient-brand);
  box-shadow: var(--shadow-accent);
}

/* The landing's three steps want more than a chip. Two copies of the same
   digit: a tilted mark that straightens as the tile lifts, and an enormous
   ghost of itself cropped by the panel's own edge. .panel clips its
   contents, so the crop is free — the digit reads as too big for the tile
   rather than as a badge sitting politely inside one. */
.step-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3.4rem;
  height: 3.4rem;
  border-radius: var(--radius-lg);
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 1.15rem;
  color: var(--accent-foreground);
  background-image: var(--gradient-brand);
  box-shadow: var(--shadow-accent);
  transform: rotate(-7deg);
  transition: transform var(--duration-slow) var(--ease-spring);
}
.panel-interactive:hover .step-mark { transform: rotate(0deg) scale(1.05); }

/* Negative z-index keeps it above the panel's background and behind the
   copy; .panel isolates, so it cannot fall through to the page. */
.step-ghost {
  position: absolute;
  z-index: -1;
  top: -2.6rem;
  right: -1.1rem;
  font-family: var(--font-mono);
  font-weight: 800;
  font-size: 9rem;
  line-height: 1;
  letter-spacing: -0.07em;
  color: transparent;
  background-image: var(--gradient-brand);
  -webkit-background-clip: text;
  background-clip: text;
  opacity: 0.1;
  pointer-events: none;
  user-select: none;
  transition: opacity var(--duration-slow) var(--ease-out);
}
.panel-interactive:hover .step-ghost { opacity: 0.17; }

@media (prefers-reduced-motion: reduce) {
  .step-mark,
  .panel-interactive:hover .step-mark { transition: none; }
}

/* Icon tile for feature cards: a soft accent well, not a bare glyph. */
.icon-tile {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--radius-md);
  color: var(--accent);
  background: var(--accent-subtle);
  border: 1px solid var(--accent-border);
  transition: color var(--duration-base) var(--ease-out),
    background-color var(--duration-base) var(--ease-out);
}
/* Feature grids cycle the tile through the spectrum so a wall of cards
   reads as one family rather than one colour repeated. */
.icon-tile-cool {
  color: var(--accent-2);
  background: var(--accent-2-subtle);
  border-color: var(--accent-2-border);
}
.icon-tile-warm {
  color: var(--accent-3);
  background: var(--accent-3-subtle);
  border-color: var(--accent-3-border);
}
.icon-tile-danger {
  color: var(--danger-fg);
  background: var(--danger-subtle);
  border-color: var(--danger);
}
/* Two more sizes of the same well: a dense settings row, and the mark that
   opens a status panel. The large one takes the larger radius so the corner
   keeps its proportion as the square grows. */
.icon-tile-sm {
  width: 2.25rem;
  height: 2.25rem;
}
.icon-tile-lg {
  width: 3rem;
  height: 3rem;
  border-radius: var(--radius-lg);
}
.panel-interactive:hover .icon-tile {
  background: var(--accent-subtle);
  color: var(--accent);
}
.panel-interactive:hover .icon-tile-cool {
  background: var(--accent-2-subtle);
  color: var(--accent-2);
}
.panel-interactive:hover .icon-tile-warm {
  background: var(--accent-3-subtle);
  color: var(--accent-3);
}

/* The rail that threads the three "how it works" steps together on wide
   screens: plaintext entering at one end, ciphertext leaving at the other. */
.step-rail {
  position: absolute;
  top: 3.4rem;
  left: 12%;
  right: 12%;
  height: 2px;
  background-image: var(--gradient-brand);
  opacity: 0.35;
}

/* Stat band figure. */
.stat-value {
  font-family: var(--font-mono);
  font-weight: 800;
  letter-spacing: var(--letter-tight);
  font-size: var(--text-3xl);
  background-image: var(--gradient-brand);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

/* Full-bleed CTA slab: the gradient sits behind a dark scrim so the
   copy keeps its contrast while the panel still reads as brand colour. */
.cta-slab {
  position: relative;
  isolation: isolate;
  border-radius: var(--radius-xl);
  border: 1px solid var(--accent-border);
  background-color: var(--card);
  overflow: hidden;
}
.cta-slab::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image: var(--gradient-brand);
  opacity: 0.16;
}

/* ════════════════════════════════════════════════════════════════════ */
/* Page headers (/features, /security)                                   */
/*                                                                       */
/* The landing opens on an argument and the whitepaper on a document;    */
/* these two open on a single word, so the word has to do the work. It   */
/* gets display type that scales with the viewport rather than a fixed   */
/* Tailwind step — which is also why the size lives here and not in a    */
/* utility class at the call site: Tailwind loads after this sheet and   */
/* a `text-5xl` beside it would silently win.                            */
/*                                                                       */
/* Only the type is shared. What sits around the title is per page and   */
/* lives with that page's own parts below — /features boards its         */
/* inventory, /security stamps a seal — because two index pages that     */
/* opened the same way would read as one page twice.                     */
/* ════════════════════════════════════════════════════════════════════ */

.hero-title {
  font-size: clamp(2.9rem, 13vw, 5.25rem);
  font-weight: 800;
  /* Not tighter: the gradient is clipped to the glyphs and painted over
     the element's own box, so a line box shorter than the font's
     ascender-to-descender leaves the tail of a 'y' unpainted. */
  line-height: 1.1;
  letter-spacing: var(--letter-tighter);
}

/* ════════════════════════════════════════════════════════════════════ */
/* Tile figures (landing + security)                                     */
/*                                                                       */
/* The feature grids on / and /security do not repeat one icon-and-text  */
/* template. Every tile carries a small figure of the claim it makes —   */
/* redacted item titles, the nine words, an authenticator counting down, */
/* the actual response headers — so the grid reads as six arguments      */
/* rather than six squares.                                              */
/*                                                                       */
/* What follows are the *parts* those figures are assembled from, small  */
/* and combinable on purpose: no two tiles use the same combination, and */
/* a new tile should compose a new figure rather than reuse a finished   */
/* one. Every figure is decorative (aria-hidden) — the copy beside it    */
/* always carries the meaning on its own.                                */
/* ════════════════════════════════════════════════════════════════════ */

/* The well a figure sits in: recessed, so it reads as a specimen inside
   the card rather than as a second card stacked on it. */
.fig {
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background-color: var(--muted);
  padding: 0.85rem 0.95rem;
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: var(--line-normal);
}
/* The variant for material that is ciphertext: the same well, sealed. */
.fig-sealed {
  border-color: var(--accent-border);
  background-color: var(--accent-subtle);
}
.fig-label {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--letter-widest);
  color: var(--muted-foreground);
}

/* A redacted value: the shape of a title, never the title. The sheen
   travels so the bar reads as sealed material rather than a loading
   skeleton; --w sets the length and --delay staggers a stack of them. */
.seal-bar {
  position: relative;
  display: block;
  overflow: hidden;
  width: var(--w, 60%);
  height: 0.5rem;
  border-radius: 999px;
  background-image: var(--gradient-brand);
  opacity: 0.6;
}
.seal-bar::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.45), transparent);
  animation: seal-sheen 3.4s var(--ease-in-out) infinite;
  animation-delay: var(--delay, 0s);
}
@keyframes seal-sheen {
  0% { transform: translateX(-100%); }
  55%, 100% { transform: translateX(240%); }
}

/* A stack of item rows: kind on the left, sealed title on the right. */
.stack-row {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.42rem 0;
}
.stack-row + .stack-row { border-top: 1px solid var(--border-subtle); }
.stack-kind {
  flex: none;
  width: 4.1rem;
  color: var(--muted-foreground);
}

/* The nine words in the slots the app itself lays them out in (see
   components/auth/phrase-grid.tsx) — the marketing figure and the real
   ceremony should be recognisably the same thing. */
.phrase-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.45rem;
}
.phrase-word {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background-color: var(--muted);
  font-family: var(--font-mono);
  font-size: 12px;
}
.phrase-word > i {
  font-style: normal;
  font-size: 10px;
  color: var(--accent);
}

/* Two devices and the signal between them. The dot is the revision
   crossing the wire; the wire itself is the page's gradient hairline. */
.wire-row {
  display: flex;
  align-items: center;
  gap: 0.9rem;
}
.wire-node {
  display: inline-flex;
  flex: none;
  padding: 0.6rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--accent-2-border);
  background: var(--accent-2-subtle);
  color: var(--accent-2);
}
.wire {
  position: relative;
  flex: 1;
  height: 2px;
  background-image: var(--gradient-edge);
}
.wire-dot {
  position: absolute;
  top: 50%;
  width: 7px;
  height: 7px;
  margin-top: -3.5px;
  border-radius: 50%;
  background: var(--accent-2);
  box-shadow: 0 0 12px var(--accent-2);
  animation: wire-run 3s var(--ease-in-out) infinite;
}
@keyframes wire-run {
  0% { left: 0; opacity: 0; }
  15% { opacity: 1; }
  85% { opacity: 1; }
  100% { left: calc(100% - 7px); opacity: 0; }
}

/* Entropy meter under a generated password. */
.meter {
  height: 6px;
  border-radius: 999px;
  background-color: var(--border);
  overflow: hidden;
}
.meter > i {
  display: block;
  height: 100%;
  width: var(--fill, 70%);
  border-radius: 999px;
  background-image: var(--gradient-brand);
}
/* Character tints match SecretText in the app: letters neutral, digits
   cyan, symbols magenta — a password looks the same everywhere. */
.cc-digit { color: var(--accent-2); }
.cc-symbol { color: var(--accent-3); }

/* An authenticator code and the thirty seconds it has left. */
.otp {
  display: flex;
  gap: 0.3rem;
}
.otp-digit {
  flex: 1;
  min-width: 0;
  height: 2.3rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background-color: var(--background);
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 15px;
}
.otp-digit.is-live {
  border-color: var(--accent-border);
  background-color: var(--accent-subtle);
  color: var(--accent);
}
.otp-timer {
  height: 3px;
  border-radius: 999px;
  background-color: var(--border);
  overflow: hidden;
}
.otp-timer > i {
  display: block;
  height: 100%;
  transform-origin: left;
  background-image: var(--gradient-brand);
  animation: otp-drain 30s linear infinite;
}
@keyframes otp-drain {
  from { transform: scaleX(1); }
  to { transform: scaleX(0); }
}

/* Alert rows as the bell drops them: the newest one still unread. */
.feed { list-style: none; margin: 0; padding: 0; }
.feed li {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.65rem;
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
}
.feed li + li { margin-top: 0.2rem; }
.feed .is-new { background-color: var(--accent-subtle); }
.feed-dot {
  flex: none;
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  background-color: var(--border-strong);
}
.feed .is-new .feed-dot {
  background-color: var(--accent);
  box-shadow: 0 0 10px var(--accent-glow);
}
.feed time {
  margin-left: auto;
  flex: none;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--muted-foreground);
}

/* The same events as a history rather than an inbox: one thread, newest
   first, each dot pinned to the accent line running behind them. */
.trail {
  position: relative;
  list-style: none;
  margin: 0;
  padding: 0 0 0 1.15rem;
}
.trail::before {
  content: "";
  position: absolute;
  left: 3px;
  top: 0.95rem;
  bottom: 1.4rem;
  width: 2px;
  border-radius: 2px;
  background-image: linear-gradient(to bottom, var(--accent), var(--accent-2));
  opacity: 0.5;
}
.trail li {
  position: relative;
  padding: 0.28rem 0;
  font-size: var(--text-sm);
}
.trail li::before {
  content: "";
  position: absolute;
  left: -1.15rem;
  top: 0.72rem;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background-color: var(--accent);
}
.trail li:first-child::before { box-shadow: 0 0 0 4px var(--accent-subtle); }
.trail span {
  display: block;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--muted-foreground);
}

/* A field and what the database holds instead of it. */
.kv {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.36rem 0;
}
.kv + .kv { border-top: 1px solid var(--border-subtle); }
.kv > span { color: var(--muted-foreground); }
.cipher-chip {
  padding: 1px 7px;
  border: 1px solid var(--accent-border);
  border-radius: var(--radius-sm);
  background-color: var(--accent-subtle);
  color: var(--accent);
  font-size: 11px;
  word-break: break-all;
}

/* Session rows: the device you recognise, the hash we actually keep. */
.sess + .sess { margin-top: 0.7rem; }
.sess-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
}
.sess-hash {
  margin-top: 0.15rem;
  color: var(--accent-2);
  font-size: 11px;
  word-break: break-all;
}
.sess-gone { color: var(--muted-foreground); text-decoration: line-through; }

/* Auto-lock dial: the ring is the idle window, the hole is the card. */
.dial {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 4.75rem;
  height: 4.75rem;
  border-radius: 50%;
  color: var(--accent);
  background: conic-gradient(from 180deg, var(--accent) var(--sweep, 72%), var(--border) 0);
}
.dial::before {
  content: "";
  position: absolute;
  inset: 5px;
  border-radius: 50%;
  background-color: var(--card);
}
.dial > svg { position: relative; }

/* Response headers, quoted verbatim from what the server sends. Smaller
   than the other figures so a full header fits on one line. */
.term { font-size: 11px; line-height: 1.75; }
.term-prompt { color: var(--accent-3); }
.term-key { color: var(--accent-2); }
.term-val { color: var(--muted-foreground); word-break: break-all; }

/* An artefact that does not exist: the reset button we cannot offer. */
.mock-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.9rem;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-md);
  color: var(--muted-foreground);
  font-size: var(--text-sm);
  font-weight: 600;
  text-decoration: line-through;
  text-decoration-color: var(--danger);
  text-decoration-thickness: 2px;
  cursor: not-allowed;
}

/* What a database dump actually yields, line by line. */
.loot { list-style: none; margin: 0; padding: 0; }
.loot li {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.28rem 0;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--muted-foreground);
}
.loot li:last-child { color: var(--foreground); }
.loot svg { flex: none; }

/* ════════════════════════════════════════════════════════════════════ */
/* Plain-language blocks                                                 */
/* The pages carry the cryptography in mono chips and figures; these     */
/* two blocks carry the sentences a reader who does not want any of it   */
/* needs instead. Both are ordinary HTML — a <details> list and a        */
/* definition list — so they work with JavaScript off.                   */
/* ════════════════════════════════════════════════════════════════════ */

/* Landing FAQ. A stack of disclosures on the panel surface rather than
   inside one card, so an open answer reads as its own object; the accent
   edge is how the page shows which one is open. */
.faq-item {
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background-color: var(--card);
  background-image: var(--surface-raised);
  transition: border-color var(--duration-base) var(--ease-out),
    box-shadow var(--duration-base) var(--ease-out);
}
.faq-item + .faq-item { margin-top: 0.65rem; }
.faq-item:hover { border-color: var(--border-strong); }
.faq-item[open] {
  border-color: var(--accent-border);
  box-shadow: var(--shadow-accent);
}
.faq-item > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem 1.25rem;
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: var(--line-snug);
  cursor: pointer;
  list-style: none;
}
.faq-item > summary::-webkit-details-marker { display: none; }
.faq-item > summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  border-radius: var(--radius-lg);
}
/* The chevron: two borders on a rotated square, so there is no icon to
   ship and no second copy of it to keep in step with the theme. */
.faq-item > summary::after {
  content: "";
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  margin-right: 0.15rem;
  border-right: 2px solid var(--accent);
  border-bottom: 2px solid var(--accent);
  transform: rotate(45deg) translateY(-25%);
  transition: transform var(--duration-base) var(--ease-out);
}
.faq-item[open] > summary::after { transform: rotate(-135deg) translateY(-25%); }
.faq-body {
  padding: 0 1.25rem 1.15rem;
  font-size: var(--text-sm);
  line-height: var(--line-relaxed);
  color: var(--muted-foreground);
  animation: fadeIn var(--duration-slow) var(--ease-out) both;
}

/* "In plain words": the term as the same mono chip the rest of the page
   uses for machinery, next to the sentence a reader actually needs. A
   <dl>, so the pairing is in the markup and not only in the layout. */
.gloss { margin: 0; }
.gloss-row { padding: 0.8rem 0; }
.gloss-row + .gloss-row { border-top: 1px solid var(--border-subtle); }
.gloss dt {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--accent);
  margin-bottom: 0.3rem;
}
.gloss dd {
  margin: 0;
  font-size: var(--text-sm);
  line-height: var(--line-relaxed);
  color: var(--muted-foreground);
}
@media (min-width: 640px) {
  .gloss-row {
    display: grid;
    grid-template-columns: 13.5rem minmax(0, 1fr);
    gap: 1.25rem;
    align-items: baseline;
  }
  .gloss dt { margin-bottom: 0; }
}

/* ════════════════════════════════════════════════════════════════════ */
/* Security page                                                         */
/* ════════════════════════════════════════════════════════════════════ */

/* The header's seal: the wordmark's own glyph struck into a lit disc,
   inside a ring that turns once every half-minute. The page's subject is
   the thing that closes, so it opens on the closure rather than on a
   glyph borrowed from a lock icon set. Decorative — the title says
   "Security" without it. */
.seal-mark {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 4.5rem;
  height: 4.5rem;
  border-radius: var(--radius-full);
  border: 1px solid var(--accent-border);
  background: var(--accent-subtle);
}
.seal-mark::before {
  content: "";
  position: absolute;
  inset: -0.6rem;
  border-radius: var(--radius-full);
  border: 1px dashed var(--accent-2-border);
  animation: seal-turn 32s linear infinite;
}
@keyframes seal-turn {
  to { transform: rotate(360deg); }
}

/* The title's own light. A text-shadow would fall behind glyphs whose
   fill is transparent; a drop-shadow filter reads the painted gradient
   and casts that instead. */
.title-glow { filter: drop-shadow(0 8px 30px var(--accent-glow)); }

/* The four primitives as one instrument panel rather than four chips
   adrift: the eye takes the whole cryptography in a single pass, and on
   a phone the strip wraps inside its own edge instead of ragging out
   across the page. */
.crypto-rail {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.45rem 0.6rem;
  /* Hugs its four chips rather than stretching to the column: a strip
     with a hand's width of empty space at each end reads as an empty
     bar. On a phone it still fills the width, because there it is full. */
  width: fit-content;
  max-width: 100%;
  margin-inline: auto;
  padding: 0.75rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background-color: var(--card);
  background-image: var(--surface-raised);
}

@media (prefers-reduced-motion: reduce) {
  .seal-mark::before { animation: none; }
}

/* Key-derivation diagram. The connectors are drawn with borders so the
   whole figure is plain HTML — no SVG to keep in sync with the layout. */
/* Nodes sit inside a .panel, so they take the page background rather than
   the card colour — otherwise card-on-card leaves them with no edge. */
.flow-node {
  position: relative;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background-color: var(--background);
  background-image: var(--surface-raised);
  padding: 1.1rem 1.25rem;
}
.flow-node-accent {
  border-color: var(--accent-border);
  box-shadow: var(--shadow-accent);
}
.flow-node-cool {
  border-color: var(--accent-2-border);
}

/* Vertical connector between diagram rows. */
.flow-link {
  width: 2px;
  height: 1.75rem;
  margin-inline: auto;
  background-image: linear-gradient(to bottom, var(--accent-border), var(--accent-2-border));
}

/* Brackets that join a pair of nodes to the single node below (merge) or
   below to a pair (split). Hidden when the grid stacks to one column. */
.flow-merge,
.flow-split {
  height: 1.5rem;
  margin-inline: 25%;
  border-left: 2px solid var(--accent-border);
  border-right: 2px solid var(--accent-border);
}
.flow-merge {
  border-bottom: 2px solid var(--accent-border);
  border-end-start-radius: var(--radius-lg);
  border-end-end-radius: var(--radius-lg);
}
.flow-split {
  border-top: 2px solid var(--accent-2-border);
  border-left-color: var(--accent-2-border);
  border-right-color: var(--accent-2-border);
  border-start-start-radius: var(--radius-lg);
  border-start-end-radius: var(--radius-lg);
}

/* The two panes of the "what you see / what we store" comparison. */
.compare-pane {
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background-color: var(--muted);
  padding: 1rem 1.15rem;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  line-height: var(--line-relaxed);
}
.compare-pane-sealed {
  border-color: var(--accent-border);
  background-color: var(--accent-subtle);
  color: var(--accent);
  word-break: break-all;
}

/* "What this makes impossible": a struck-through claim in a red-lined
   card. The mark is decorative — the copy carries the meaning. */
.deny-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  flex-shrink: 0;
  border-radius: var(--radius-full);
  background: var(--danger-subtle);
  color: var(--danger-fg);
}

/* ════════════════════════════════════════════════════════════════════ */
/* Whitepaper                                                            */
/*                                                                       */
/* /security is the argument; /whitepaper is the construction. The page  */
/* is long on purpose, so it reads as a document: a contents rail that   */
/* says where you are, and tables rather than prose wherever the content */
/* is really a list of parameters.                                       */
/* ════════════════════════════════════════════════════════════════════ */

/* Contents rail. Plain anchors, so it navigates with JavaScript off;
   whitepaper.ts adds aria-current to the section being read, which is the
   only thing the script contributes. */
.toc { list-style: none; margin: 0; padding: 0; border-left: 1px solid var(--border); }
.toc a {
  display: block;
  margin-left: -1px;
  padding: 0.4rem 0 0.4rem 0.85rem;
  border-left: 1px solid transparent;
  font-size: var(--text-sm);
  line-height: 1.35;
  color: var(--muted-foreground);
  text-decoration: none;
  transition: color var(--duration-fast) var(--ease-out),
              border-color var(--duration-fast) var(--ease-out);
}
.toc a:hover { color: var(--foreground); }
.toc a[aria-current] {
  color: var(--accent);
  border-left-color: var(--accent);
  font-weight: 600;
}
.toc-num {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  margin-right: 0.45rem;
  opacity: 0.55;
}

/* A URL split at the hash: what the request carries, and what the browser
   keeps to itself. The two halves are styled as opposites because that is
   the whole point of the construction. */
.frag {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 2;
  word-break: break-all;
}
.frag-sent { color: var(--muted-foreground); }
.frag-kept {
  color: var(--accent);
  background: var(--accent-subtle);
  border: 1px solid var(--accent-border);
  border-radius: var(--radius-sm);
  padding: 0.1rem 0.3rem;
}

/* Threat model. A real table: the rows are genuinely a matrix of adversary
   against outcome, and prose would only bury it. */
.threat { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.threat th,
.threat td {
  text-align: left;
  vertical-align: top;
  padding: 0.7rem 0.9rem;
  border-bottom: 1px solid var(--border-subtle);
}
.threat th {
  font-weight: 600;
  color: var(--foreground);
  white-space: nowrap;
}
.threat thead th {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted-foreground);
  border-bottom-color: var(--border);
}
.threat td { color: var(--muted-foreground); line-height: var(--line-relaxed); }
.threat tbody tr:last-child th,
.threat tbody tr:last-child td { border-bottom: none; }

/* Below sm the table stops being a table: each row becomes a small block
   with its adversary as a heading, which beats two columns squeezed into a
   phone or a horizontal scrollbar under the reader's thumb. */
@media (max-width: 639px) {
  .threat thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .threat tbody tr { display: block; border-bottom: 1px solid var(--border-subtle); padding: 0.5rem 0; }
  .threat tbody tr:last-child { border-bottom: none; }
  .threat th,
  .threat td { display: block; border-bottom: none; padding: 0.2rem 0; white-space: normal; }
}

/* ════════════════════════════════════════════════════════════════════ */
/* Features page                                                         */
/*                                                                       */
/* / and /security argue; this page inventories. It deliberately shares  */
/* none of their figures: no seal bars, no phrase grid, no drifting      */
/* ciphertext, no countdown dial. The devices below are its own, and     */
/* each one shows the actual artefact the feature produces — the field   */
/* rows of a card item, the stack of vaults, the row a share link adds.  */
/* Same rule as everywhere else: decorative, aria-hidden, and the copy   */
/* beside one carries the claim without it.                              */
/* ════════════════════════════════════════════════════════════════════ */

/* The title takes the same light the chips below it do: the gradient is
   twice the width of the glyphs and slides across them. Slow enough to
   be noticed rather than watched. */
.title-sheen {
  background-size: 220% 100%;
  animation: title-sheen 11s var(--ease-in-out) infinite;
}
@keyframes title-sheen {
  0%, 100% { background-position: 0% 50%; }
  50% { background-position: 100% 50%; }
}

/* Hero capability cloud: the whole inventory as chips, a few of them lit
   at any moment. --i staggers the glow so the light travels rather than
   pulsing in unison, which would read as a loading state.

   The board is the edge the light crosses. Loose on the page the chips
   read as a tag cloud someone forgot to lay out; inside a frame the same
   twenty read as the contents of the page, which is what they are. */
.cap-board {
  padding: 0.9rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background-color: var(--card);
  background-image: var(--surface-raised);
}
.cap-cloud {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  justify-content: center;
}
.cap-chip {
  border-radius: var(--radius-full);
  border: 1px solid var(--border);
  background-color: var(--card);
  padding: 0.3rem 0.7rem;
  font-size: 12px;
  color: var(--muted-foreground);
  white-space: nowrap;
  animation: cap-glow 9s var(--ease-in-out) infinite;
  animation-delay: calc(var(--i, 0) * 0.42s);
}
@keyframes cap-glow {
  0%, 88%, 100% { border-color: var(--border); color: var(--muted-foreground); background-color: var(--card); }
  93% { border-color: var(--accent-border); color: var(--accent); background-color: var(--accent-subtle); }
}

/* Category specimen: one item's field rows, as the form actually lists
   them. features.ts swaps the contents between categories; the tabs above
   it are the shared .seg control from theme.css, so they match the
   generator's mode pair rather than being a second look-alike. */
.spec-field {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.6rem 0;
  font-size: 13px;
}
.spec-field + .spec-field { border-top: 1px solid var(--border-subtle); }
.spec-field > span:first-child { color: var(--muted-foreground); }
.spec-field > span:last-child { font-family: var(--font-mono); color: var(--foreground); }
.spec-field .is-secret { color: var(--accent); letter-spacing: 0.12em; }
/* The swap is a fade rather than a slide: four categories cycling past
   sideways would pull the eye away from the copy beside them. The floor is
   the tallest category (four rows), so the card holds its height instead of
   collapsing on the two-row note and shunting the page under the cursor. */
.spec-body {
  min-height: 10.5rem;
  transition: opacity var(--duration-base) var(--ease-out);
}
.spec-body.is-swapping { opacity: 0; }

/* Vault deck: separate vaults as stacked cards, the front one square on
   and the ones behind it tilted back. The only place on the site that
   uses depth, which is what makes "more than one vault" read instantly. */
.deck { position: relative; height: 8.5rem; }
.deck-card {
  position: absolute;
  left: 0;
  right: 0;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background-color: var(--card);
  background-image: var(--surface-raised);
  padding: 0.75rem 0.95rem;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 13px;
  transition: transform var(--duration-slow) var(--ease-out);
}
/* Three layers, each set inline with --d (depth index): further back means
   smaller, higher and dimmer. */
.deck-card {
  top: calc(var(--d) * 1.9rem);
  transform: scale(calc(1 - (2 - var(--d)) * 0.05));
  opacity: calc(0.55 + var(--d) * 0.225);
  z-index: var(--d);
}
.panel-interactive:hover .deck-card { transform: scale(calc(1 - (2 - var(--d)) * 0.02)); }
.deck-key {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: var(--radius-full);
  flex: none;
  background: var(--tint, var(--accent));
}

/* People with access to a shared vault: an initial, a name, and what they
   are allowed to do. */
.people { list-style: none; margin: 0; padding: 0; }
.person {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0;
  font-size: 13px;
}
.person + .person { border-top: 1px solid var(--border-subtle); }
.person-face {
  width: 1.65rem;
  height: 1.65rem;
  flex: none;
  border-radius: var(--radius-full);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 700;
  color: var(--accent-foreground);
  background-image: var(--gradient-brand);
}
.person-role {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: var(--letter-wide);
  text-transform: uppercase;
  color: var(--muted-foreground);
}

/* A share link as the dialog lists it: what it is for, how long it has
   left, how many times it has been opened. */
.link-row {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.55rem 0;
  font-size: 13px;
}
.link-row + .link-row { border-top: 1px solid var(--border-subtle); }
.link-name { white-space: nowrap; }
.link-meta { margin-left: auto; font-family: var(--font-mono); font-size: 10px; color: var(--muted-foreground); white-space: nowrap; }
.link-row.is-dead { color: var(--muted-foreground); }
.link-row.is-dead .link-name { text-decoration: line-through; }
/* On a phone the two halves stop fitting on one line, and squeezing them
   breaks the date across two. The lifetime drops under the link it
   belongs to instead, indented past the glyph so the pairing survives. */
@media (max-width: 479px) {
  .link-row { flex-wrap: wrap; }
  .link-meta { margin-left: 0; width: 100%; padding-left: 1.65rem; }
}

/* Sync, shown as the thing that proves it: the same change, stamped at
   the same second on two devices. The stamps flash together, because
   simultaneity is the whole claim. */
.echo { display: grid; gap: 0.5rem; }
.echo-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  background-color: var(--muted);
  padding: 0.5rem 0.7rem;
  font-size: 12px;
}
.echo-row time {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--accent-2);
  animation: echo-flash 4.5s var(--ease-in-out) infinite;
}
@keyframes echo-flash {
  0%, 82%, 100% { opacity: 0.55; }
  88% { opacity: 1; }
}

/* Trash retention: thirty days as thirty marks, the spent ones faded. The
   count is the point, so the figure is literally countable. */
.ticks { display: flex; gap: 3px; align-items: flex-end; }
.tick {
  width: 4px;
  height: 1.1rem;
  border-radius: 1px;
  background-color: var(--accent);
  opacity: 0.85;
}
.tick.is-spent { background-color: var(--border-strong); opacity: 1; height: 0.7rem; }

/* The quiet list: features that deserve naming but not a figure. Hairline
   rules and a mono note, so a long tail of small things does not turn
   into a wall of identical cards.

   Term over definition until the tile is genuinely wide. The two-column
   form needs a 10rem label column, and this list sits in a tile that is
   half-width from sm to lg — at that size the sentence beside the label
   was wrapping one word per line, and a label longer than the column
   pushed its sentence out past the tile's edge. So the columns wait for
   lg, which is where the tile spans four of six. */
.spec-list { list-style: none; margin: 0; padding: 0; }
.spec-list li { padding: 0.75rem 0; }
.spec-list li + li { border-top: 1px solid var(--border-subtle); }
.spec-list strong { display: block; font-weight: 600; color: var(--foreground); margin-bottom: 0.2rem; }
.spec-list span { color: var(--muted-foreground); font-size: var(--text-sm); line-height: var(--line-relaxed); }
@media (min-width: 1024px) {
  .spec-list li {
    display: flex;
    align-items: baseline;
    gap: 1rem;
  }
  .spec-list strong { display: inline; margin-bottom: 0; flex: none; min-width: 10rem; }
}

/* The Emergency Kit as the sheet of paper it is meant to become: sitting
   slightly askew, because the instruction attached to it is "print this". */
.kit-sheet {
  border-radius: var(--radius-md);
  border: 1px solid var(--border-strong);
  background-color: var(--muted);
  padding: 1rem 1.1rem;
  transform: rotate(-1.4deg);
  box-shadow: 0 10px 30px -18px rgba(0, 0, 0, 0.9);
}
.kit-sheet .kit-rule {
  height: 1px;
  background-image: var(--gradient-edge);
  margin: 0.7rem 0;
}
.kit-blank {
  height: 0.55rem;
  border-radius: 2px;
  background-color: var(--border);
}

@media (prefers-reduced-motion: reduce) {
  .cap-chip,
  .title-sheen,
  .echo-row time { animation: none; }
  .kit-sheet { transform: none; }
}

/* ════════════════════════════════════════════════════════════════════ */
/* Site chrome — the mobile navigation drawer                            */
/*                                                                       */
/* Below sm the public header's destinations collapse into a panel that  */
/* slides in from the right over a scrim. Closed is the default and      */
/* needs no script: the root is `visibility: hidden`, which also keeps   */
/* the links out of the accessibility tree, and the whole thing is       */
/* display:none from sm up so it can never appear beside the nav row it  */
/* stands in for.                                                        */
/*                                                                       */
/* Everything animates from a class on the root rather than from         */
/* display, because a panel toggled by `display` cannot slide.           */
/* ════════════════════════════════════════════════════════════════════ */

.nav-drawer-root {
  position: fixed;
  inset: 0;
  /* Above the sticky header (30) and the cookie notice (55): while this
     is open it is the only thing on the page. */
  z-index: 60;
  visibility: hidden;
}
.nav-drawer-root.is-open { visibility: visible; }

/* The scroll lock, scoped to the widths the drawer exists at. Held in CSS
   rather than as an inline `overflow: hidden` so it cannot outlive the
   drawer: a phone rotated into a tablet width hides the panel, and this
   releases the page in the same breath rather than leaving it frozen
   behind something no longer on screen. */
@media (max-width: 639px) {
  body.nav-locked {
    overflow: hidden;
    padding-right: var(--nav-gutter, 0px);
  }
}

/* Takes its blur and tint from the shared .scrim — a drawer and a dialog
   seal the page by the same amount or the two read as different depths. */
.nav-drawer-scrim {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity var(--duration-slow) var(--ease-out);
}
.nav-drawer-root.is-open .nav-drawer-scrim { opacity: 1; }

.nav-drawer {
  position: absolute;
  inset: 0 0 0 auto;
  display: flex;
  flex-direction: column;
  width: min(19rem, 82vw);
  /* Never the full width: the strip of scrim left showing is what says
     the page is still there behind it. */
  padding: calc(0.85rem + env(safe-area-inset-top)) 0.85rem calc(0.85rem + env(safe-area-inset-bottom));
  border-left: 1px solid var(--border);
  background-color: var(--popover);
  background-image: var(--surface-raised);
  box-shadow: var(--shadow-xl);
  overflow-y: auto;
  overscroll-behavior: contain;
  transform: translateX(100%);
  transition: transform var(--duration-slow) var(--ease-out);
}
.nav-drawer-root.is-open .nav-drawer { transform: translateX(0); }

/* The close button alone, sitting where the trigger it replaced was. */
.nav-drawer-head {
  display: flex;
  justify-content: flex-end;
  padding-bottom: 0.6rem;
  margin-bottom: 0.35rem;
  border-bottom: 1px solid var(--border-subtle);
}

.nav-drawer-nav { display: flex; flex-direction: column; }

/* Rows, not pills: these are navigation rather than controls, the same
   call the legal sidebar and the vault switcher make. */
.nav-drawer-item {
  display: flex;
  align-items: center;
  padding: 0.8rem 0.75rem;
  border-radius: var(--radius-md);
  font-size: var(--text-base);
  font-weight: 500;
  color: var(--foreground);
  text-decoration: none;
  transition: background-color var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out);
}
.nav-drawer-item:hover { background-color: var(--muted); }
.nav-drawer-item:active { color: var(--accent); }
.nav-drawer-rule {
  height: 1px;
  margin: 0.45rem 0.75rem;
  /* --border rather than --border-subtle: on the popover surface the
     subtle one is invisible, and this rule is the only thing separating
     the destinations from the way in. */
  background-color: var(--border);
}

/* Footer colophon: the credit line and the source link, sitting under the
   footer's brand blurb. Quieter than the navigation around it, but legible
   on purpose — it comes up to full contrast on hover. */
.colophon {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 12px;
  color: var(--muted-foreground);
  opacity: 0.6;
  transition: opacity var(--duration-slow) var(--ease-out);
}
.colophon:hover,
.colophon:focus-within { opacity: 1; }
.colophon a { color: inherit; text-decoration: none; }
.colophon a:hover { color: var(--accent); }
.colophon svg { display: block; }

/* ════════════════════════════════════════════════════════════════════ */
/* App (vault)                                                           */
/* ════════════════════════════════════════════════════════════════════ */

/* Item rows get a staggered rise on first paint; vault.tsx sets
   --stagger-i per row. */
.item-row-enter {
  animation: rise var(--duration-slow) var(--ease-out) backwards;
  animation-delay: calc(var(--stagger-i, 0) * 28ms);
}

/* A row that wants attention — unread in the notifications centre, selected
   in the vault list — carries an accent edge and a wash that fades out
   across it. Everything else recedes to plain. */
.row-accent {
  position: relative;
  background-image: linear-gradient(90deg, var(--accent-subtle), transparent 45%);
}
.row-accent::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 2px;
  background-image: linear-gradient(to bottom, var(--accent), var(--accent-2));
}

/* An icon tile for content that has been dealt with. */
.icon-tile-muted {
  color: var(--muted-foreground);
  background: var(--muted);
  border-color: var(--border);
}

/* Copy feedback flash on secret fields. */
@keyframes copied-flash {
  0% { background-color: var(--accent-subtle); }
  100% { background-color: transparent; }
}
.copied-flash { animation: copied-flash 700ms ease-out; }

ul { margin: 0.5rem 0; padding-left: 1.25rem; }

/* Legal documents typography. */
.legal-prose h2 { font-size: var(--text-xl); font-weight: 700; margin: 2rem 0 0.75rem; letter-spacing: var(--letter-tight); }
.legal-prose h3 { font-size: var(--text-lg); font-weight: 600; margin: 1.5rem 0 0.5rem; }
.legal-prose p { margin: 0 0 1rem; color: var(--muted-foreground); }
.legal-prose li { margin: 0.35rem 0; color: var(--muted-foreground); }
.legal-prose strong { color: var(--foreground); }
.legal-prose a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
.legal-prose a:hover { color: var(--accent-active); }
.legal-prose code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  background: var(--muted);
  padding: 0.1em 0.4em;
  border-radius: var(--radius-sm);
}
