/* PAGE — the sheet the script-free static pages share: privacy/ and docs/.

   Both are build INPUTS rather than copies beside the build (see
   vite.config.ts), and this file is what that buys. They get the app's own
   colours from tokens.css, in both themes and every palette, and they get ONE
   prose style instead of a \3c style> block per page quietly drifting from its
   neighbour.

   The same two texts also render INSIDE the app — Landing.svelte imports each
   page's <main> raw — and that copy is styled by css/landing.css, which has to
   restate these rules under `.landing-prose` because it is the app's
   stylesheet the landing page is wearing. One text, two frames; this is the
   cold-URL frame. */
/* TOKENS — colour, elevation, geometry. The only place a literal lives.

   COLOUR IS DECLARED IN PAIRS AND BOUND IN ONE PLACE. Every palette writes
   `--l-<token>` and `--d-<token>` — its light value and its dark one, side by
   side on the same line — and the three binding blocks at the bottom of this
   file are the only thing that decides which half of each pair becomes the live
   `--<token>`. `[data-theme]` is what lets an explicit choice beat the media
   query in both directions (js/settings.ts writes it), exactly as before.

   The indirection buys one thing and it is worth the read: the dark values used
   to be written out TWICE, once media-scoped and once attribute-scoped, under a
   comment asking whoever came next to keep the copies in step. The named
   palettes in css/palettes.css would have made that three copies each, of five
   palettes — so the copies now carry token NAMES rather than colours, which
   means the worst a drift can do is leave a token unbound instead of leaving a
   colour subtly wrong. test/theme.test.ts holds the two dark blocks identical.

   Nothing here selects an element; everything below in the other files reads
   from the live tokens and never from a pair. */

:root {
  /* ---- surfaces ------------------------------------- light | dark ------- */
  --l-bg: #ffffff;              --d-bg: #16181d;
  /* Menus and sheets float ABOVE the page, so they get their own surface —
     equal to --bg in light, deliberately lighter than it in dark, where a
     floating panel painted the page's colour reads as a hole. */
  --l-raised: #ffffff;          --d-raised: #1b1e25;
  /* NOT THE DARKEST INK AVAILABLE, and that is the point. Near-black on white
     is 15:1 — past the range the eye reads comfortably for long stretches, and
     it makes every glyph edge ring. Backing off to ~11:1 keeps body copy well
     clear of AAA (7:1) while letting the page feel like paper rather than a
     terminal. The quiet tier moves with it, but only as far as 4.5:1: notes and
     completed rows are CONTENT set in --text-dim, so AA for small text is the
     floor it is allowed to reach and not a step further.

     The dark halves are the same step down, mirrored: light-on-dark optically
     BLOOMS, so the softening matters more there, not less. */
  --l-text: #353c46;            --d-text: #c8cdd6;
  --l-text-dim: #717783;        --d-text-dim: #838c99;
  --l-dot: #6b7280;             --d-dot: #9aa4b2;
  /* Gutter controls are interactive, not de-emphasised text, so they get their
     own token: --text-dim is tuned for body copy and falls under 3:1 at glyph
     size. 4.0:1 on white here, 7.0:1 on the dark background. */
  --l-gutter-icon: #718096;     --d-gutter-icon: #9aa4b2;
  --l-accent: #4f7cf0;          --d-accent: #6b93f5;
  --l-line: #e6e9ee;            --d-line: #23272f;
  /* STATUS, not identity — which is why these two have no dark half here and
     none in most of the palettes either: a red that means "this failed" has a
     legibility floor to clear before it has a family to belong to, and the
     binding below falls back to the light value wherever a palette hasn't
     offered a better one. --warn is deliberately NOT --danger: nothing has gone
     wrong when an edit is safe in IndexedDB and waiting for a network. */
  --l-danger: #e53e3e;
  --l-warn: #b7791f;

  /* ---- elevation -------------------------------------------------------- */
  --l-shadow-1: 0 1px 2px rgba(16, 24, 40, .05), 0 6px 16px rgba(16, 24, 40, .07);
  --d-shadow-1: 0 1px 2px rgba(0, 0, 0, .3), 0 6px 16px rgba(0, 0, 0, .3);
  --l-shadow-2: 0 8px 20px rgba(16, 24, 40, .08), 0 24px 48px rgba(16, 24, 40, .12);
  --d-shadow-2: 0 8px 20px rgba(0, 0, 0, .32), 0 24px 48px rgba(0, 0, 0, .42);

  /* ---- the tiers that are a step off another one -------------------------
     DERIVED RATHER THAN DECLARED, and so they appear exactly once — no pair, no
     palette. Each of these four is only ever "the same colour, nearly all the
     way back to the surface", which is a ratio and not a choice: the halo is
     the bullet's own grey at a whisper, the soft fill is the accent at a
     whisper, and an indent guide is a step quieter than a border because at
     --line weight the outline reads as a table. The percentages are the ones
     that reproduce the hand-picked values these used to be, in both modes,
     which is why they are these numbers and not round ones — and it is what
     lets a palette in css/palettes.css declare eight colours instead of twelve
     and still hold together. */
  --dot-halo: color-mix(in srgb, var(--dot) 22%, var(--bg));
  --accent-soft: color-mix(in srgb, var(--accent) 12%, var(--bg));
  --guide: color-mix(in srgb, var(--line) 85%, var(--bg));
  --guide-lit: color-mix(in srgb, var(--text-dim) 35%, var(--bg));

  /* ---- geometry --------------------------------------------------------- */
  --bar-h: 57px;
  --docbar-h: 46px;
  --rail-w: 250px;
  --col: 720px;
  --col-pad: 28px;
  --r-sm: 6px;
  --r-md: 8px;
  --r-lg: 14px;
  --r-pill: 999px;
  /* One 28px grid for every icon button in the chrome, so the bars line up
     however many controls a state happens to show. */
  --hit: 28px;

  --mono: ui-monospace, SFMono-Regular, Menlo, monospace;
  /* `system-ui` FIRST, and it is not decoration. The stack used to open with
     `-apple-system` / `BlinkMacSystemFont`, which name the Apple UI face and
     nothing else — on Android both miss, `"Segoe UI"` misses, and the text
     landed on whatever `Roboto` resolved to rather than on the platform's
     actual UI font at its actual optical size. `system-ui` is the one keyword
     every current engine answers with its real interface face (SF on Apple,
     Roboto on Android, Segoe UI Variable on Windows), and those are the faces
     tuned for reading at these sizes. The rest stays as the fallback it always
     was. */
  --sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  /* ---- the document's type ------------------------------------------------
     The OUTLINE's scale, kept out of the chrome's: a bullet, its note and the
     zoom title are the only things here that are being read rather than
     operated, and on a phone they are read at arm's length on a smaller
     screen. Naming them means the narrow override at the bottom of this file
     is four lines instead of a hunt through the document rules. */
  --doc-text: 16px;
  --doc-line: 22px;
  --doc-note: 13px;
  --doc-note-line: 19px;
  --doc-title: 27px;
  /* THE WEIGHT IS A TOKEN because it is not the same number on every screen.
     A phone is read at arm's length, usually one-handed, often in the dark
     theme — and light-on-dark optically THINS a face, which is why the same
     400 that is right on a laptop reads washed out on a handset. The coarse
     block lifts it; nothing else may set a weight on body copy. */
  --doc-weight: 400;

  /* ---- motion ------------------------------------------------------------
     Three speeds and two curves, and nothing in the app is allowed a fourth.
     The durations are duplicated in js/motion.ts, which is where the
     script-driven half of the same system lives; keep the two in step.

     FAST is for affordances — a hover, a focus ring, a control lighting up.
     BASE is a panel arriving. MOVE is reserved for the STRUCTURAL changes: a
     zoom, a branch unrolling, a row changing places. Anything that redraws the
     outline gets MOVE, so every one of them takes the same amount of time and
     the app has one tempo rather than a dozen. */
  --dur-fast: 90ms;
  --dur-base: 150ms;
  --dur-move: 200ms;
  /* Decelerate: things arrive and settle. The default for anything appearing. */
  --ease: cubic-bezier(.22, .7, .3, 1);
  /* Accelerate: for things LEAVING, which should pick up speed on the way out.
     A decelerating exit reads as hesitation. */
  --ease-exit: cubic-bezier(.5, 0, .85, .3);
}

/* ---- the binding -------------------------------------------------------
   Which half of every pair is live. THREE BLOCKS AND NOTHING ELSE MAY DO THIS:
   the light one is the default, and the system decides between them — unless
   data-theme says otherwise, in which case the choice wins in both directions.

   All three bind on `:root` alone, deliberately: a palette in css/palettes.css
   sets pairs and never a live token, so it can be one flat block per palette
   with no mode in its selector and nothing to outrank here.

   The dark body appears twice (media-scoped and attribute-scoped) because CSS
   has no way to write one rule for both. They are token NAMES, so keep the two
   identical — test/theme.test.ts fails if they diverge by a character. */
:root {
  color-scheme: light;
  --bg: var(--l-bg);
  --raised: var(--l-raised);
  --text: var(--l-text);
  --text-dim: var(--l-text-dim);
  --dot: var(--l-dot);
  --gutter-icon: var(--l-gutter-icon);
  --accent: var(--l-accent);
  --line: var(--l-line);
  --danger: var(--l-danger);
  --warn: var(--l-warn);
  --shadow-1: var(--l-shadow-1);
  --shadow-2: var(--l-shadow-2);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg: var(--d-bg);
    --raised: var(--d-raised);
    --text: var(--d-text);
    --text-dim: var(--d-text-dim);
    --dot: var(--d-dot);
    --gutter-icon: var(--d-gutter-icon);
    --accent: var(--d-accent);
    --line: var(--d-line);
    --danger: var(--d-danger, var(--l-danger));
    --warn: var(--d-warn, var(--l-warn));
    --shadow-1: var(--d-shadow-1);
    --shadow-2: var(--d-shadow-2);
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: var(--d-bg);
  --raised: var(--d-raised);
  --text: var(--d-text);
  --text-dim: var(--d-text-dim);
  --dot: var(--d-dot);
  --gutter-icon: var(--d-gutter-icon);
  --accent: var(--d-accent);
  --line: var(--d-line);
  --danger: var(--d-danger, var(--l-danger));
  --warn: var(--d-warn, var(--l-warn));
  --shadow-1: var(--d-shadow-1);
  --shadow-2: var(--d-shadow-2);
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 16px/1.6 var(--sans);
}
main {
  max-width: 40rem;
  margin: 0 auto;
  padding: 3rem 1.25rem 4rem;
}
h1 {
  font-size: 1.5rem;
  margin: 0 0 0.25rem;
}
h2 {
  font-size: 1.05rem;
  margin: 2rem 0 0.5rem;
}
p,
li {
  margin: 0.5rem 0;
}
ul {
  padding-left: 1.25rem;
}
.updated {
  color: var(--text-dim);
  margin: 0 0 2rem;
}
a {
  color: inherit;
}

/* ---------------------------------------------------------------- docs */
/* The docs are two columns: the contents, which stays put, and the prose. The
   privacy policy is one column and keeps `main`'s own width above. */
.docs {
  display: grid;
  grid-template-columns: 12rem minmax(0, 40rem);
  gap: 3rem;
  max-width: 55rem;
}
.docs-toc {
  position: sticky;
  top: 2rem;
  align-self: start;
  font-size: 0.9rem;
}
.docs-toc b {
  color: var(--text-dim);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.docs-toc ol {
  list-style: none;
  margin: 0.6rem 0 0;
  padding: 0;
}
.docs-toc a {
  display: block;
  padding: 0.25rem 0 0.25rem 0.75rem;
  border-left: 2px solid var(--line);
  color: var(--text-dim);
  text-decoration: none;
}
.docs-toc a:hover {
  color: var(--text);
}
/* Set by js/docs-toc.ts — the section you have scrolled to. */
.docs-toc a[aria-current="true"] {
  color: var(--text);
  border-left-color: var(--accent);
}
.docs-body > *:first-child {
  margin-top: 0;
}
.docs-body h2 {
  scroll-margin-top: 1.5rem;
}

/* One column, contents first: a phone has no room beside the text. */
@media (max-width: 760px) {
  .docs {
    grid-template-columns: minmax(0, 1fr);
    gap: 1.5rem;
  }
  .docs-toc {
    position: static;
  }
}

/* The shortcut table. `overflow-x` on the wrapper, never on the page: three
   columns of key glyphs are wider than a phone and the body must not scroll
   sideways because of them. */
.keys {
  overflow-x: auto;
}
.keys table {
  border-collapse: collapse;
  width: 100%;
  font-size: 0.95rem;
}
.keys th {
  text-align: left;
  font-weight: 600;
  color: var(--text-dim);
  font-size: 0.85rem;
}
.keys th,
.keys td {
  padding: 0.35rem 0.75rem 0.35rem 0;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.keys caption {
  text-align: left;
  font-weight: 600;
  padding: 1.25rem 0 0.35rem;
}
kbd {
  font: inherit;
  font-size: 0.9em;
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0.1em 0.4em;
  background: var(--raised);
}

footer {
  margin-top: 3rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
  color: var(--text-dim);
  font-size: 0.9rem;
}
