/* Self-hosted fonts (offline-first — no runtime CDN call). Latin subsets only,
   pulled from Google Fonts at build time into src/assets/fonts. Spectral is the
   display face for the wordmark; JetBrains Mono is the "machine/terminal" UI face
   for the launch-tile actions. */
@font-face {
  font-family: "Spectral";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("./assets/fonts/spectral-400.woff2") format("woff2");
}
@font-face {
  font-family: "Spectral";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("./assets/fonts/spectral-500.woff2") format("woff2");
}
/* JetBrains Mono ships as a single variable file covering the weight axis. */
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("./assets/fonts/jetbrains-mono.woff2") format("woff2");
}

/* ============================================================================
   COLOR — primitives → semantic, multi-theme.

   PRIMITIVES (--wm-c-*): the ONLY hand-picked colors — a small per-theme palette
   (12 values + 1 mix ratio). Each theme is one block below.

   SEMANTIC tokens (--wm-paper, --wm-ink, …): what the rest of this stylesheet
   uses. Defined ONCE (theme-independent) as aliases/derivations of the
   primitives, so a role used in 40 places traces back to one primitive and
   themes can't drift role-by-role.

   Scoped to [data-theme] (not just :root) so a preview element can carry its own
   theme: <div data-theme="nord"> re-resolves the whole semantic layer from
   nord's primitives for its own subtree (used by the Settings → Theme previews).
   The inline script in index.html always sets data-theme on <html>, so :root is
   only the no-attribute fallback (= light).

   To add a theme: add a primitives block here, its name to THEMES
   (src/lib/storage.ts), and a BACKING entry (src/lib/lockdown.ts). For any theme
   that can drive the live window, --wm-c-surface / --wm-c-backdrop MUST mirror
   that BACKING (native resize-gap / corner-shield paint can't read CSS). The
   semantic layer never changes.
   ============================================================================ */

/* --- LIGHT (also the no-data-theme fallback) --- */
:root,
[data-theme="light"] {
  color-scheme: light; /* native controls (checkbox accent, widgets) render light */
  --wm-c-surface: #ffffff; /* paper: sheets, login, menus, thumbnails */
  --wm-c-backdrop: #f2f3f4; /* canvas: library + editor backdrop, the unified bar */
  --wm-c-raised: #f0f0f1; /* a neutral lifted off the backdrop (popover-menu rows) */
  --wm-c-border: #dedee0; /* hairlines */
  --wm-c-muted: #8a8a87; /* secondary text / icons */
  --wm-c-text-rgb: 28, 27, 25; /* ink (#1c1b19) as bare channels — see --wm-ink/--wm-sb-rgb */
  --wm-c-text-lift: #34322d; /* ink, slightly lifted: hover on ink/accent fills */
  --wm-c-on-accent: #ffffff; /* text/icons drawn ON an ink/accent fill */
  --wm-c-danger: #c0392b; /* destructive / error */
  --wm-c-danger-strong: #a93226; /*  …its hover */
  --wm-c-danger-bg: #fbeae7; /*  …tint behind an error banner */
  --wm-c-danger-line: #f0c6c0; /*  …that banner's border */
  /* How far the on-canvas press fill is nudged toward ink (the canvas "%" in the
     color-mix below). Higher = subtler; darker themes need a stronger nudge. */
  --wm-c-hover-mix: 94%;
}

/* --- DARK (fully dark sheet). --wm-c-surface/-backdrop mirror BACKING.dark. --- */
[data-theme="dark"] {
  color-scheme: dark; /* native controls flip dark so the checkbox isn't a pale box */
  --wm-c-surface: #1d1d1b;
  --wm-c-backdrop: #131311;
  --wm-c-raised: #28271f;
  --wm-c-border: #34332e;
  --wm-c-muted: #8c8a83;
  --wm-c-text-rgb: 231, 229, 223; /* #e7e5df */
  --wm-c-text-lift: #d6d4cc;
  --wm-c-on-accent: #131311;
  --wm-c-danger: #e0685c;
  --wm-c-danger-strong: #ec8074;
  --wm-c-danger-bg: #2e1a17;
  --wm-c-danger-line: #4a261f;
  --wm-c-hover-mix: 88%;
}

/* --- SEPIA (warm light — e-reader paper, brown ink). Mirrors BACKING.sepia. --- */
[data-theme="sepia"] {
  color-scheme: light;
  --wm-c-surface: #f4ecd9;
  --wm-c-backdrop: #e9dec5;
  --wm-c-raised: #efe6cf;
  --wm-c-border: #d8c9a8;
  --wm-c-muted: #9c8b6e;
  --wm-c-text-rgb: 67, 58, 43; /* #433a2b */
  --wm-c-text-lift: #5c5038;
  --wm-c-on-accent: #f4ecd9;
  --wm-c-danger: #a6402f;
  --wm-c-danger-strong: #8a3324;
  --wm-c-danger-bg: #ecd6c2;
  --wm-c-danger-line: #d8b193;
  --wm-c-hover-mix: 92%;
}

/* --- NORD (cool dark — polar night + snow storm). Mirrors BACKING.nord. --- */
[data-theme="nord"] {
  color-scheme: dark;
  --wm-c-surface: #2e3440;
  --wm-c-backdrop: #272b35;
  --wm-c-raised: #3b4252;
  --wm-c-border: #434c5e;
  --wm-c-muted: #8b93a7;
  --wm-c-text-rgb: 216, 222, 233; /* #d8dee9 */
  --wm-c-text-lift: #c0c8d8;
  --wm-c-on-accent: #2e3440;
  --wm-c-danger: #bf616a;
  --wm-c-danger-strong: #cd7882;
  --wm-c-danger-bg: #3b2b30;
  --wm-c-danger-line: #5a3b41;
  --wm-c-hover-mix: 86%;
}

/* --- GRUVBOX (warm dark — retro groove). Mirrors BACKING.gruvbox. --- */
[data-theme="gruvbox"] {
  color-scheme: dark;
  --wm-c-surface: #282828;
  --wm-c-backdrop: #1d2021;
  --wm-c-raised: #3c3836;
  --wm-c-border: #504945;
  --wm-c-muted: #928374;
  --wm-c-text-rgb: 235, 219, 178; /* #ebdbb2 */
  --wm-c-text-lift: #d5c4a1;
  --wm-c-on-accent: #282828;
  --wm-c-danger: #fb4934;
  --wm-c-danger-strong: #fc6a58;
  --wm-c-danger-bg: #3a2422;
  --wm-c-danger-line: #5a302a;
  --wm-c-hover-mix: 84%;
}

/* --- Semantic tokens + type: theme-independent. Resolve against whichever
   theme's primitives are in scope (root, or a preview's data-theme subtree). --- */
:root,
[data-theme] {
  --wm-paper: var(--wm-c-surface);
  --wm-canvas: var(--wm-c-backdrop);
  /* On-canvas controls' press state: the canvas nudged toward ink. */
  --wm-canvas-hover: color-mix(in srgb, var(--wm-canvas) var(--wm-c-hover-mix), var(--wm-ink));
  --wm-ink: rgb(var(--wm-c-text-rgb));
  --wm-muted: var(--wm-c-muted);
  --wm-line: var(--wm-c-border);
  /* Accent tracks ink everywhere today; promote it to its own primitive if a
     theme ever needs a distinct accent hue. */
  --wm-accent: var(--wm-ink);
  --wm-on-ink: var(--wm-c-on-accent);
  --wm-ink-lift: var(--wm-c-text-lift);
  --wm-sb-rgb: var(--wm-c-text-rgb); /* used as rgba(var(--wm-sb-rgb), <alpha>) */
  --wm-danger: var(--wm-c-danger);
  --wm-danger-strong: var(--wm-c-danger-strong);
  --wm-danger-bg: var(--wm-c-danger-bg);
  --wm-danger-line: var(--wm-c-danger-line);
  /* Translucent paper scrim over a thumbnail (the ⋮ button): paper at 92% alpha. */
  --wm-paper-veil: color-mix(in srgb, var(--wm-paper) 92%, transparent);
  --wm-menu-hover: var(--wm-c-raised);

  /* --- Type --- */
  --wm-serif: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  --wm-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial,
    sans-serif;
  --wm-mono: "Courier New", Courier, monospace;
  /* Wordmark display face, and the mono UI face for launch-tile actions. */
  --wm-display: "Spectral", var(--wm-serif);
  --wm-ui-mono: "JetBrains Mono", var(--wm-mono);

  /* --- Corner radius — two tiers, app-wide. The machine/terminal character is
     near-square: SHARP for every control (buttons, inputs, chips, tools, the
     paper thumbnails). SOFT is reserved for floating popovers (the font list,
     the card ⋮ menu, the modal gate card) so they read as lifted off the
     surface. Nothing should pick a radius outside these two. --- */
  --wm-r-sharp: 2px;
  --wm-r-soft: 6px;
}

* {
  box-sizing: border-box;
}

html,
body,
#root {
  margin: 0;
  height: 100%;
  background: var(--wm-paper);
  color: var(--wm-ink);
  font-family: var(--wm-sans);
  -webkit-font-smoothing: antialiased;
}

/* GENERAL PRINCIPLE: the window surface itself never scrolls or rubber-bands.
   A two-finger drag must not peel the whole page off its window to reveal the
   webview behind it — only the designated inner surfaces (.wm-canvas, lists,
   menus) scroll. So the root is locked (no document scroll) and elastic
   overscroll is disabled here; inner scrollers add `overscroll-behavior: contain`
   (below) so their bounce can't chain up to the root either. */
html,
body,
#root {
  overflow: hidden;
  overscroll-behavior: none;
}

/* App chrome is furniture, not content. Labels, button text, settings/tab
   names, headings — none of it should highlight; selectable text is a "this is
   content you can take" signal that breaks the native feel. So selection is OFF
   by default and opted back in below only for genuine content. (Replaces the
   piecemeal per-component `user-select: none` rules this used to need.) */
#root {
  -webkit-user-select: none;
  user-select: none;
}

/* Selectable content: the document you type, the mantra phrase you author, and
   any editable field (inline doc-rename, size box) where selecting text is part
   of editing. */
.wm-prose,
[contenteditable="true"],
input,
textarea {
  -webkit-user-select: text;
  user-select: text;
}

/* ---------- Intro / threshold ---------- */
.wm-intro {
  height: 100vh;
  display: grid;
  place-items: center;
  background: var(--wm-paper);
}

/* titleBarStyle: Overlay extends the page under the titlebar (so the surface
   shows through), which means the page must declare the window's drag area. This strip
   sits over the titlebar zone; native traffic lights still draw above it. */
.wm-dragbar {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 28px;
  z-index: 40;
}

/* In the scrolling views (settings / export) the card can be taller than the
   window, so content scrolls up under the native traffic lights (which draw
   above the web layer). Make the drag strip an opaque paper band here so it
   masks anything scrolling beneath it — a fixed top border below the lights.
   Height matches the .wm-intro-scroll top spacer (40px) so the card's content
   begins flush at the band's lower edge. */
.wm-intro-scroll .wm-dragbar {
  height: 40px;
  background: var(--wm-paper);
}

.wm-intro-card {
  width: min(420px, 86vw);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 26px;
  text-align: center;
}

/* Visually-hidden but accessible (screen readers + document outline). Carries
   the login's "Word Machine" heading, which is presented visually as the
   drifting quote sea (decorative, aria-hidden) instead of a rendered wordmark. */
.wm-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---------- Launch tile (Enter) screen ----------
   The login window IS the design's card (native macOS chrome supplies the
   rounded corners, shadow, and traffic lights). Two wallpapers fill it (Settings
   → wallpaper; `data-wallpaper` on .wm-home): "cursor" puts the blinking cursor
   mark in the upper region with the action panel below it; "quotes" fills the
   whole window with the drifting quote sea, the panel floating over it inside a
   feathered halo. The panel pins to the bottom in both (the foot's margin-top). */
.wm-home {
  display: block; /* let the card fill the window instead of centering it */
  position: relative; /* containing block for the absolute quote sea */
  overflow: hidden; /* clip the drifting quote tracks to the window */
}

.wm-home-card {
  position: relative;
  z-index: 1; /* above the quote sea */
  width: 100%;
  min-height: 100vh;
  box-sizing: border-box;
  padding: 36px 40px 34px; /* source tile padding */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  text-align: left;
  pointer-events: none; /* empty card area doesn't catch clicks; panel re-enables */
}

/* Legacy wordmark + caret styling (kept for reference; the "cursor" wallpaper now
   renders .wm-login-mark / .wm-login-cursor instead). The blank page waiting.
   flex: 1 holds the upper region and centers the mark vertically; the action
   panel pins below it via the foot's margin-top: auto. */
.wm-wordmark-row {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 4px;
  cursor: default;
}

.wm-wordmark {
  margin: 0;
  font-family: var(--wm-display); /* Spectral */
  font-size: 2.125rem; /* 34px */
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--wm-ink);
  white-space: nowrap;
}

.wm-wordmark-caret {
  display: block;
  width: 2px;
  height: 34px; /* matches the 34px wordmark */
  background: var(--wm-ink);
  border-radius: 1px;
  animation: wm-wordmark-caret 1.1s steps(1) infinite;
}

@keyframes wm-wordmark-caret {
  0%,
  48% {
    opacity: 1;
  }
  49%,
  100% {
    opacity: 0;
  }
}

/* Login mark (replaces the "Word Machine" wordmark): a single blinking cursor —
   the caret that is also the doorway. Holds the upper region like the wordmark
   row did; the bar is the mark itself. Same blink as the wordmark caret. */
.wm-login-mark {
  flex: 1;
  display: flex;
  align-items: center;
}

.wm-login-cursor {
  display: block;
  width: 5px;
  height: 72px;
  background: var(--wm-ink);
  border-radius: var(--wm-r-sharp);
  animation: wm-wordmark-caret 1.1s steps(1, end) infinite;
}

/* Hold the cursor steady for users who'd rather not have the blink. */
@media (prefers-reduced-motion: reduce) {
  .wm-login-cursor {
    animation: none;
  }
}

/* Entering: the cursor stops blinking and morphs into the door. The transform AND
   the inverse-scaled border-radius are driven imperatively in JS — the radius is
   divided by the scale so the widened slab lands at a crisp 2px corner (matching
   --wm-r-sharp) rather than a scaled-up rounded caret. */
.wm-home.is-entering .wm-login-cursor {
  animation: none;
  opacity: 1;
}

/* Fade the action panel out as the door forms, so only the door is in play. */
.wm-home.is-entering .wm-home-foot {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.4s ease;
}

/* Bottom group: any access/vault note above the action row. Pinned to the bottom
   (margin-top: auto, both wallpapers) and sized to its contents (bottom-left),
   capped so a long note wraps rather than stretching across the window. */
.wm-home-foot {
  position: relative;
  z-index: 1;
  pointer-events: auto; /* re-enable: the card above turns pointer-events off */
  margin-top: auto; /* pin to the bottom under the wordmark / over the sea */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 14px;
  width: fit-content;
  max-width: min(360px, 100%);
}

/* "quotes" wallpaper: the sea drifts the FULL window, including past the action
   panel. To keep the controls legible without a hard panel edge slicing the
   drifting text, a feathered halo sits behind them — a radial paper scrim
   (lightly frosted) opaque over the buttons and fading to nothing at its rim, so
   lines fade out as they approach the controls and fade back in. */
.wm-home[data-wallpaper="quotes"] .wm-home-foot::before {
  content: "";
  position: absolute;
  inset: -20px -42px; /* extend the halo well beyond the action cluster */
  z-index: -1; /* behind the buttons, above the sea */
  background: color-mix(in srgb, var(--wm-paper) 86%, transparent);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  -webkit-mask-image: radial-gradient(
    ellipse at center,
    #000 36%,
    transparent 72%
  );
  mask-image: radial-gradient(ellipse at center, #000 36%, transparent 72%);
  pointer-events: none;
}

/* Enter + Settings, side by side. */
.wm-actions {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 0; /* boxes abut: settings' invisible paper box carries 24px left-pad,
             so enter-edge -> settings-text stays the original ~24px (text-to-text
             ~48px) even though settings now has enter's full padding box */
  -webkit-user-select: none; /* WebKit (Tauri on macOS) */
  user-select: none; /* button labels are controls, not selectable text */
}

/* Primary action: solid filled button, near-square corners, mono — the
   "machine/terminal" character. Ink fill, white text. */
.wm-enter {
  padding: 11px 24px;
  /* Hold a constant width across the "enter ↵" / "confirm?" swap so the button
     doesn't resize between the two states. Mono font → size to the widest label
     (8 chars) in ch + the 48px horizontal padding (border-box). */
  min-width: calc(8ch + 50px);
  border: none;
  border-radius: var(--wm-r-sharp);
  background: var(--wm-ink);
  color: var(--wm-on-ink);
  font-family: var(--wm-ui-mono);
  font-size: 0.875rem; /* 14px */
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: background-color 0.18s ease;
}

.wm-enter:hover {
  background: var(--wm-ink-lift); /* ink, slightly lifted */
}

.wm-enter:disabled {
  opacity: 0.4;
  cursor: default;
}

/* Settings: muted mono link beside Enter. It carries the SAME box as Enter
   (padding, corners) but filled with the page color instead of ink — invisible
   on the login, yet it occludes the drifting quote sea exactly like Enter's solid
   rectangle does, so the two read symmetrically (no extra text peeking out below
   the bare link). */
.wm-home-card .wm-textlink {
  margin-top: 0;
  padding: 11px 24px;
  border-radius: var(--wm-r-sharp);
  background: var(--wm-paper);
  font-family: var(--wm-ui-mono);
  font-size: 0.875rem; /* 14px */
  letter-spacing: 0.02em;
}

/* ---------- Login quote sea ----------
   Verbatim public-domain literary lines drifting across the login window in
   place of the wordmark (see QuoteSea.tsx + lib/quotes.ts). Decorative: sits
   behind the action panel, pointer-events off, non-selectable (chrome default).
   All lanes drift the same way (a tide) but vary in speed / size / opacity for
   depth — a "sea". Serif, because the lines are authored literary content.

   The sea fills the WHOLE window — lines drift past the action panel too (the
   panel's own feathered halo, .wm-home-foot::before, fades them around the
   controls). A vertical mask only feathers the window's own top/bottom edges: a
   taller top fade keeps the drift clear of the native traffic lights, a short
   bottom fade softens the window's bottom edge. */
.wm-quote-sea {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  -webkit-mask-image: linear-gradient(
    to bottom,
    transparent 0,
    #000 62px,
    #000 calc(100% - 22px),
    transparent 100%
  );
  mask-image: linear-gradient(
    to bottom,
    transparent 0,
    #000 62px,
    #000 calc(100% - 22px),
    transparent 100%
  );
}

.wm-quote-lane {
  display: flex;
  width: 100%;
  white-space: nowrap;
}

/* The track holds the lane's quotes twice; translateX(-50%) advances exactly one
   copy, looping seamlessly. Uniform right-margin on each quote (not flex gap)
   keeps both halves identical width so the seam is invisible. */
.wm-quote-track {
  display: inline-flex;
  flex: none;
  width: max-content;
  will-change: transform;
  animation: wm-quote-drift linear infinite;
}

.wm-quote {
  font-family: var(--wm-serif);
  color: var(--wm-ink);
  margin-right: 3.5rem;
}

@keyframes wm-quote-drift {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}

/* Depth — 7 lanes, varied (deliberately non-monotonic so it reads organic, not
   like a gradient): nearer lanes larger / more present / faster, far lanes small
   / faint / slow. Negative delays desync the lanes so they don't start aligned.
   Keep the rule count in sync with LANES in QuoteSea.tsx. */
.wm-quote-lane:nth-child(1) .wm-quote-track {
  font-size: 1.3rem;
  opacity: 0.26;
  animation-duration: 96s;
  animation-delay: -12s;
}
.wm-quote-lane:nth-child(2) .wm-quote-track {
  font-size: 0.95rem;
  opacity: 0.13;
  animation-duration: 132s;
  animation-delay: -48s;
}
.wm-quote-lane:nth-child(3) .wm-quote-track {
  font-size: 1.62rem;
  opacity: 0.34;
  animation-duration: 80s;
  animation-delay: -30s;
}
.wm-quote-lane:nth-child(4) .wm-quote-track {
  font-size: 1.05rem;
  opacity: 0.16;
  animation-duration: 116s;
  animation-delay: -70s;
}
.wm-quote-lane:nth-child(5) .wm-quote-track {
  font-size: 1.42rem;
  opacity: 0.28;
  animation-duration: 90s;
  animation-delay: -8s;
}
.wm-quote-lane:nth-child(6) .wm-quote-track {
  font-size: 0.92rem;
  opacity: 0.12;
  animation-duration: 140s;
  animation-delay: -55s;
}
.wm-quote-lane:nth-child(7) .wm-quote-track {
  font-size: 1.18rem;
  opacity: 0.22;
  animation-duration: 106s;
  animation-delay: -34s;
}

/* Respect reduced-motion: hold the sea still (a static field of quotes). */
@media (prefers-reduced-motion: reduce) {
  .wm-quote-track {
    animation: none;
  }
}

.wm-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  text-align: left;
  font-size: 0.82rem;
  color: var(--wm-muted);
}

.wm-field input,
.wm-gate-card input {
  font-size: 1rem;
  padding: 11px 13px;
  border: 1px solid var(--wm-line);
  border-radius: var(--wm-r-sharp);
  background: var(--wm-paper);
  color: var(--wm-ink);
  outline: none;
  transition: border-color 0.15s ease;
  font-family: var(--wm-sans);
}

.wm-field input:focus,
.wm-gate-card input:focus {
  border-color: var(--wm-accent);
}

.wm-input-error {
  border-color: var(--wm-danger) !important;
}

.wm-primary {
  margin-top: 6px;
  padding: 12px 16px;
  border: none;
  border-radius: var(--wm-r-sharp);
  background: var(--wm-accent);
  color: var(--wm-on-ink);
  font-size: 0.98rem;
  font-weight: 500;
  cursor: pointer;
  transition: opacity 0.15s ease;
}

.wm-primary:hover {
  opacity: 0.88;
}

.wm-ghost {
  padding: 12px 16px;
  border: 1px solid var(--wm-line);
  border-radius: var(--wm-r-sharp);
  background: transparent;
  color: var(--wm-ink);
  font-size: 0.95rem;
  cursor: pointer;
}

/* Low-emphasis text button (Settings / Reset to default). */
.wm-textlink {
  margin-top: 2px;
  border: none;
  background: transparent;
  color: var(--wm-muted);
  font-family: var(--wm-sans);
  font-size: 0.82rem;
  cursor: pointer;
  align-self: center;
  padding: 4px;
}

.wm-textlink:hover {
  color: var(--wm-ink);
}

/* ---------- Settings page ----------
   The home view is the quote sea + a bottom-left action panel. Settings is a
   form, so it gets its own tighter, full-width layout in a centered card. */

/* ---------- Scrollbars — project standard ----------
   GENERAL PRINCIPLE: a scrollbar must never change layout size and must stay
   unobtrusive. Two parts, applied to every scroll surface:
     1. `scrollbar-gutter: stable` reserves the gutter whether or not the bar is
        showing, so a view that overflows can't shift horizontally against one
        that doesn't (the settings tabs were jumping for exactly this reason).
     2. An ultra-thin (6px), track-less thumb that's invisible at rest and shows
        muted only WHILE the surface is actively scrolling, then eases out a beat
        after it stops. The thumb's opacity is the `--wm-sb-alpha` custom property
        (0 by default); lib/scrollReveal animates it per element on scroll, since
        WebKit won't run CSS transitions on scrollbar pseudo-elements.
   The webkit + firefox rules below are global (unprefixed), so any element that
   scrolls inherits the look; opt a container into the no-shift behavior with the
   `.wm-scroll` reserve (or the per-container rules that already carry it). */
.wm-scroll,
.wm-intro-scroll,
.wm-export-list,
.wm-lib-body,
.wm-canvas,
.wm-font-menu {
  scrollbar-gutter: stable;
  /* Keep scrolling inside the surface — don't let an overscroll chain to (and
     bounce) the locked root window. See the root lock above. */
  overscroll-behavior: contain;
}

/* Thumb colour is ink at the JS-driven alpha (0 at rest). 28,27,25 = --wm-ink. */

/* Firefox: thin; the thumb alpha tracks the same variable. */
* {
  scrollbar-width: thin;
  scrollbar-color: rgba(var(--wm-sb-rgb), var(--wm-sb-alpha, 0)) transparent;
}

/* WebKit (the app runs in a WKWebView) */
::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}
::-webkit-scrollbar-track,
::-webkit-scrollbar-corner {
  background: transparent;
}
::-webkit-scrollbar-thumb {
  background: rgba(var(--wm-sb-rgb), var(--wm-sb-alpha, 0));
  border-radius: 3px;
}

/* The form can be taller than the short login window, so the settings view
   scrolls and keeps guaranteed room top and bottom — Save/Cancel never sit
   flush against the window edge. The ::before/::after are flexible spacers:
   they grow to center the card when there's room, but never shrink below 40px
   when it overflows (and, being real flex items, they extend the scroll height
   rather than relying on scroll-container padding, which WebKit drops). */
.wm-intro-scroll {
  display: flex;
  flex-direction: column;
  align-items: center;
  overflow-y: auto;
}

.wm-intro-scroll::before,
.wm-intro-scroll::after {
  content: "";
  flex: 1 0 40px;
}

/* TOP-align instead of vertically centering. GENERAL PRINCIPLE: any view whose
   card changes height (content loading, switching tabs, toggling options) must
   pin its top edge — a centered card visibly re-centers when it grows/shrinks,
   so the whole thing jumps. A fixed 40px gap above with all slack below makes
   height changes extend downward smoothly, leaving the chrome put.
   Applied to: the export list (loads async) and settings (tabs + toggles). */
.wm-export-view::before,
.wm-settings-view::before {
  flex: 0 0 40px;
}
.wm-export-view::after,
.wm-settings-view::after {
  flex: 1 0 40px;
}

.wm-settings-card {
  gap: 20px;
}

/* Fixed header: title on the left, back on the right. Sits above the tabs so the
   back action never moves with the panel body's height (the old bottom-pinned
   button drifted in/out of view as panels grew/collapsed). */
.wm-settings-head {
  width: 100%;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

/* Page title — terminal-type mono, left-aligned (the card itself is centered). */
.wm-settings-title {
  margin: 0;
  font-family: var(--wm-ui-mono);
  font-size: 0.95rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--wm-ink);
}

/* Back — the settings view's only navigation. Mono/lowercase to match the title
   and the chrome voice; muted until hover. Baseline-aligned with the title. */
.wm-settings-back {
  border: none;
  background: transparent;
  padding: 0;
  color: var(--wm-muted);
  font-family: var(--wm-ui-mono);
  font-size: 0.82rem;
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: color 0.12s ease;
}

.wm-settings-back:hover {
  color: var(--wm-ink);
}

.wm-settings-body {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.wm-field {
  width: 100%;
}

/* Label line: "Exit mantra" on the left, the Reset link on the right. */
.wm-field-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
}

.wm-reset {
  margin: 0;
  padding: 0;
  align-self: auto;
  font-size: 0.78rem;
}

/* Settings: mantra editor. */
.wm-mantra-edit {
  width: 100%;
  font-size: 1rem;
  padding: 11px 13px;
  border: 1px solid var(--wm-line);
  border-radius: var(--wm-r-sharp);
  background: var(--wm-paper);
  color: var(--wm-ink);
  outline: none;
  resize: none;
  font-family: var(--wm-serif);
  line-height: 1.5;
  transition: border-color 0.15s ease;
}

.wm-mantra-edit:focus {
  border-color: var(--wm-accent);
}

/* Settings: lockdown on/off. The "lockdown" header sits above (a .wm-field-row,
   like Exit mantra / Vault folder); this row is the checkbox + its caption,
   underneath the header. */
.wm-toggle {
  display: flex;
  align-items: center;
  gap: 10px;
  text-align: left;
  cursor: pointer;
  width: 100%;
}

.wm-toggle input {
  width: 16px;
  height: 16px;
  accent-color: var(--wm-accent);
  cursor: pointer;
  flex: none;
}

.wm-toggle-note {
  font-size: 0.78rem;
  color: var(--wm-muted);
  line-height: 1.4;
}

.wm-error {
  color: var(--wm-danger);
  font-size: 0.82rem;
  margin: 0;
}

.wm-access-note {
  text-align: left;
  font-size: 0.8rem;
  color: var(--wm-muted);
  line-height: 1.45;
}

/* ---------- Settings tabs ----------
   Two tabs side by side (lockdown · vault folder); the active one is underlined.
   The row sits on a hairline so inactive tabs read as flush with it and the
   active tab's underline overlaps it. */
.wm-settings-tabs {
  display: flex;
  gap: 22px;
  width: 100%;
  border-bottom: 1px solid var(--wm-line);
}

.wm-settings-tab {
  padding: 0 0 8px;
  margin-bottom: -1px; /* sit the tab's underline on the row's hairline */
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--wm-muted);
  font-family: var(--wm-ui-mono);
  font-size: 0.9rem;
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}

.wm-settings-tab:hover {
  color: var(--wm-ink);
}

.wm-settings-tab.is-active {
  color: var(--wm-ink);
  border-bottom-color: var(--wm-ink);
}

/* ---------- Settings: exit-method picker (master / detail) ----------
   Method names in a left rail; the selected method's description + config in the
   right pane. Keeps the panel calm as methods are added — the rail just grows
   taller while the detail area stays a single, quiet place for the config. */
.wm-method-md {
  display: flex;
  width: 100%;
  border: 1px solid var(--wm-line);
  border-radius: var(--wm-r-sharp);
  overflow: hidden; /* clip the rail's fill + divider to the rounded corners */
}

/* Left rail: one selectable tab per method, stacked. */
.wm-method-rail {
  display: flex;
  flex-direction: column;
  flex: 0 0 40%;
  background: var(--wm-canvas);
  border-right: 1px solid var(--wm-line);
}

.wm-method-tab {
  padding: 10px 12px;
  border: none;
  border-bottom: 1px solid var(--wm-line);
  background: transparent;
  text-align: left;
  cursor: pointer;
  font-family: var(--wm-ui-mono);
  font-size: 0.82rem;
  letter-spacing: 0.02em;
  color: var(--wm-muted);
  transition: color 0.12s ease, background-color 0.12s ease;
}

.wm-method-tab:last-child {
  border-bottom: none;
}

.wm-method-tab:hover {
  color: var(--wm-ink);
}

/* Active method: lifts onto the pane's surface with an accent edge, so the rail
   reads as continuous with the detail it's showing. */
.wm-method-tab.is-active {
  background: var(--wm-paper);
  color: var(--wm-ink);
  box-shadow: inset 2px 0 0 var(--wm-accent);
}

/* Right pane: the selected method's description above its config. */
.wm-method-pane {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  padding: 12px 13px 13px;
  background: var(--wm-paper);
}

.wm-method-desc {
  margin: 0 0 11px;
  color: var(--wm-muted);
  font-size: 0.74rem;
  line-height: 1.45;
  letter-spacing: 0.01em;
}

.wm-method-config {
  display: flex;
  flex-direction: column;
}

/* ---------- Settings: timer-duration stepper ----------
   A custom control (replacing the native number spinner): the value + unit in a
   bordered box with a stacked up/down arrow pair on the right, adjusting in
   5-minute steps. Matches the segmented control's mono / 2px-corner treatment. */
.wm-stepper {
  display: flex;
  /* Shrink to the value + unit + arrows; never stretch to the field width
     (the field is a full-width flex item, which would otherwise pull the box
     and leave dead space to the right of the arrows). */
  width: fit-content;
  align-items: stretch;
  border: 1px solid var(--wm-line);
  border-radius: var(--wm-r-sharp);
  background: var(--wm-paper);
  overflow: hidden;
}

.wm-stepper-value {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  min-width: 26px;
  padding: 8px 0 8px 13px;
  font-family: var(--wm-ui-mono);
  font-size: 0.95rem;
  color: var(--wm-ink);
}

.wm-stepper-unit {
  display: inline-flex;
  align-items: center;
  padding: 8px 13px 8px 7px;
  font-family: var(--wm-ui-mono);
  font-size: 0.8rem;
  color: var(--wm-muted);
}

.wm-stepper-arrows {
  display: flex;
  flex-direction: column;
  border-left: 1px solid var(--wm-line);
}

.wm-stepper-arrow {
  flex: 1 1 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  padding: 0;
  background: var(--wm-paper);
  border: none;
  color: var(--wm-muted);
  cursor: pointer;
  transition: background-color 0.12s ease, color 0.12s ease;
}

.wm-stepper-arrow + .wm-stepper-arrow {
  border-top: 1px solid var(--wm-line);
}

.wm-stepper-arrow:hover:not(:disabled) {
  background: var(--wm-ink);
  color: var(--wm-on-ink);
}

.wm-stepper-arrow:disabled {
  color: var(--wm-line);
  cursor: default;
}

/* ---------- Settings — launch-tile aesthetic ----------
   Bring the login tile's character to the settings form: JetBrains Mono UI
   labels, near-square (2px) corners, and an ink-filled Save that mirrors the
   tile's Enter. Scoped to .wm-settings-card so the shared ExitGate dialog keeps
   its own (rounder, sans) treatment. Palette is unchanged — same paper/ink. */

/* Small UI labels → mono, like the tile's "enter"/"settings". */
.wm-settings-card .wm-field,
.wm-settings-card .wm-vault-path,
.wm-settings-card .wm-toggle-note,
.wm-settings-card .wm-textlink {
  font-family: var(--wm-ui-mono);
  letter-spacing: 0.02em;
}

/* Inputs and the mantra editor → near-square corners, matching the buttons.
   (The mantra value stays serif — it's the user's prose, not chrome.) */
.wm-settings-card .wm-field input,
.wm-settings-card .wm-mantra-edit {
  border-radius: var(--wm-r-sharp);
}

/* Save mirrors the tile's Enter; Cancel is a muted mono outline beside it. */
.wm-settings-card .wm-primary {
  margin-top: 0;
  padding: 11px 24px;
  border-radius: var(--wm-r-sharp);
  background: var(--wm-ink);
  color: var(--wm-on-ink);
  font-family: var(--wm-ui-mono);
  font-size: 0.875rem;
  font-weight: 400;
  letter-spacing: 0.02em;
  transition: background-color 0.18s ease;
}

.wm-settings-card .wm-primary:hover {
  opacity: 1;
  background: var(--wm-ink-lift); /* ink, slightly lifted — same as Enter */
}

.wm-settings-card .wm-ghost {
  padding: 11px 24px;
  border-radius: var(--wm-r-sharp);
  border-color: var(--wm-line);
  color: var(--wm-muted);
  font-family: var(--wm-ui-mono);
  font-size: 0.875rem;
  letter-spacing: 0.02em;
  transition: color 0.18s ease, border-color 0.18s ease;
}

.wm-settings-card .wm-ghost:hover {
  color: var(--wm-ink);
  border-color: var(--wm-ink);
}

/* ---------- Export view ----------
   Reuses the settings card; a scrollable document list above a row of format
   chips. Mono UI type to match the launch tile. */
.wm-export-list {
  width: 100%;
  /* Show at most 5 rows, then scroll. Each row is a fixed 34px (18px line +
     2×8px padding); 5 rows + 4×2px gaps + 2×4px list padding = 186px. */
  max-height: 186px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
  border: 1px solid var(--wm-line);
  border-radius: var(--wm-r-sharp);
  padding: 4px;
}

.wm-export-doc {
  flex: none;
  text-align: left;
  border: none;
  background: transparent;
  color: var(--wm-ink);
  font-family: var(--wm-ui-mono);
  font-size: 0.86rem;
  line-height: 18px;
  letter-spacing: 0.02em;
  padding: 8px 10px;
  border-radius: var(--wm-r-sharp);
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: background-color 0.12s ease;
}

.wm-export-doc:hover {
  background: var(--wm-canvas);
}

.wm-export-doc.is-selected {
  background: var(--wm-ink);
  color: var(--wm-on-ink);
}

.wm-export-formats {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

/* Format chip: the file extension, mono, square — the "machine" character. */
.wm-export-fmt {
  flex: 1;
  min-width: 42px; /* four chips fit the export popover (.wm-card-menu:has) */
  padding: 9px 0;
  border: 1px solid var(--wm-line);
  border-radius: var(--wm-r-sharp);
  background: transparent;
  color: var(--wm-ink);
  font-family: var(--wm-ui-mono);
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}

.wm-export-fmt:hover:not(:disabled) {
  border-color: var(--wm-ink);
}

.wm-export-fmt:disabled {
  opacity: 0.4;
  cursor: default;
}

/* Floating export confirmation. Fixed (out of flow → never shifts the card), and
   in the launch-tile's visual language: ink fill, white mono text, near-square
   2px corners. Fades in, holds, fades out over its lifetime; the component clears
   it just after. */
.wm-toast {
  position: fixed;
  /* Center via auto margins so the box stays INSIDE the viewport — left:50% +
     translateX would anchor the box at center and overflow rightward, flashing a
     scrollbar. The animation then only moves vertically. */
  left: 0;
  right: 0;
  bottom: 22px;
  margin: 0 auto;
  width: max-content;
  max-width: calc(100% - 48px);
  z-index: 60;
  padding: 9px 14px;
  border-radius: var(--wm-r-sharp);
  background: var(--wm-ink);
  color: var(--wm-on-ink);
  font-family: var(--wm-ui-mono);
  font-size: 0.78rem;
  letter-spacing: 0.02em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
  pointer-events: none;
  animation: wm-toast 3s ease forwards;
}

.wm-toast.is-error {
  background: var(--wm-danger);
}

@keyframes wm-toast {
  0% {
    opacity: 0;
    transform: translateY(6px);
  }
  7%,
  86% {
    opacity: 1;
    transform: translateY(0);
  }
  100% {
    opacity: 0;
    transform: translateY(4px);
  }
}

/* ---------- Locked session ---------- */
.wm-session {
  height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--wm-canvas);
}

/* Unified bar: doc name (left), formatting tools (center), window nav (right) —
   one slim row, no separate toolbar. Solid canvas so it matches the surface
   exactly (no blur: nothing scrolls under it). The left padding clears the
   overlay-titlebar traffic lights, which macOS draws natively at top-left.
   A comfortable height: the native traffic lights are centered to match it in
   Rust (center_traffic_lights), so this isn't constrained by the lights' fixed
   position. KEEP IN SYNC with BAR_HEIGHT in lib/lockdown.ts (passed to the
   native centering). */
.wm-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  height: 48px;
  padding: 0 16px 0 90px;
  border-bottom: 1px solid var(--wm-line);
  background: var(--wm-canvas);
  user-select: none;
}

/* Lockdown strips window decorations, so there are no traffic lights to clear —
   collapse the left inset and let the doc name sit flush at the edge. */
.wm-bar.is-flush {
  padding-left: 16px;
}

/* Left cluster: the document name, sitting just past the traffic lights. */
.wm-left {
  display: flex;
  align-items: center;
  gap: 14px;
  flex: none;
}

/* Right cluster: Library + Exit. */
.wm-nav {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
}

/* Read-failure banner above the toolbar (editing disabled to protect the file). */
.wm-doc-error {
  padding: 8px 18px;
  background: var(--wm-danger-bg);
  border-bottom: 1px solid var(--wm-danger-line);
  color: var(--wm-danger-strong);
  font-size: 0.82rem;
  text-align: center;
}

/* Bar buttons: Exit and the editor's ‹ Library back button share one look —
   borderless mono text (the login tile's action type), muted, ink on hover. */
.wm-exit,
.wm-back {
  border: none;
  background: transparent;
  color: var(--wm-muted);
  font-family: var(--wm-ui-mono);
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  padding: 6px 4px;
  cursor: pointer;
  transition: color 0.15s ease;
}

.wm-exit:hover,
.wm-back:hover {
  color: var(--wm-ink);
}

/* No focus ring on the chrome buttons — entering lockdown calls set_focus(),
   which lands on the first focusable element (Exit) and would otherwise draw a
   highlight around it. */
.wm-exit:focus,
.wm-back:focus,
.wm-exit:focus-visible,
.wm-back:focus-visible {
  outline: none;
}

/* Document name, left-aligned by the traffic lights. Same mono treatment as the
   Exit/Library buttons. pointer-events off so it never intercepts the drag. */
.wm-doc-name {
  max-width: 180px;
  font-family: var(--wm-ui-mono);
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  color: var(--wm-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  pointer-events: none;
}

/* Whisper-quiet autosave state, sitting just right of the doc name. Mono to
   match the bar; muted and low-contrast so it never competes with the writing.
   "saving…" while a write is in flight, "saved" once it lands; the dot carries
   the state at a glance (hollow = saving, filled = saved). pointer-events off —
   it's a readout, not a control. */
.wm-save-state {
  display: inline-flex;
  align-items: center;
  justify-content: flex-start;
  gap: 6px;
  /* Reserve the width of the longest state ("saving…") and left-align, so the
     readout never changes width — otherwise the wider "saving…" widens .wm-left
     and pushes the centered toolbar right on every keystroke. */
  min-width: 68px;
  font-family: var(--wm-ui-mono);
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  color: var(--wm-muted);
  opacity: 0.7;
  pointer-events: none;
  user-select: none;
  white-space: nowrap;
  transition: opacity 0.4s ease;
}

/* Once saved, the readout recedes further — present if you look, invisible if
   you don't. */
.wm-save-state.is-saved {
  opacity: 0.4;
}

.wm-save-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  border: 1px solid currentColor;
  background: transparent; /* hollow while saving */
}

.wm-save-state.is-saved .wm-save-dot {
  background: currentColor; /* filled once persisted */
}

/* ---------- Library (document menu) ---------- */
.wm-library {
  height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--wm-canvas);
}

.wm-lib-bar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 14px;
  /* Exit pinned right; empty left clears the traffic lights (see .wm-bar). 48px
     to match the editor bar so the centered traffic lights align in both. */
  height: 48px;
  padding: 0 18px;
  border-bottom: 1px solid var(--wm-line);
  user-select: none;
}

.wm-lib-body {
  flex: 1;
  overflow: auto;
  width: 100%;
  max-width: 1040px;
  margin: 0 auto;
  padding: 30px 26px 60px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* Google-Docs-style grid of fixed-width thumbnail cards, centered. */
.wm-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, 190px);
  justify-content: center;
  gap: 26px 22px;
}

.wm-card {
  position: relative;
  width: 190px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  cursor: pointer; /* a single click opens */
  outline: none;
}

/* The thumbnail frame: a page-shaped white card that clips the scaled content. */
.wm-thumb {
  position: relative;
  width: 190px;
  height: 246px; /* 190 × 11/8.5 — US Letter aspect */
  overflow: hidden;
  background: var(--wm-paper);
  border: 1px solid var(--wm-line);
  border-radius: var(--wm-r-sharp);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

/* Hover (or keyboard focus) highlights a doc card with an ink ring. The dashed
   New tile is excluded — it just darkens its dashed border (below) instead of
   getting a solid ring traced over it. */
.wm-card:hover .wm-thumb:not(.wm-thumb-new),
.wm-card:focus-visible .wm-thumb:not(.wm-thumb-new) {
  border-color: var(--wm-ink);
  box-shadow: 0 0 0 1px var(--wm-ink);
}

/* A renaming card (the natural state of a just-created doc) is held at a steady
   ink ring so it stands out from a grid of otherwise-identical blank cards —
   otherwise the new doc blends in and the writer can't find it. One calm signal:
   the ring reuses the hover treatment (no new visual language), no pulse and no
   lift — the name's underline carries "edit here". */
.wm-card.is-renaming .wm-thumb:not(.wm-thumb-new) {
  border-color: var(--wm-ink);
  box-shadow: 0 0 0 1px var(--wm-ink);
}

/* After commit, the card holds a fading ring for ~1.5s (matched to the JS timer
   that clears the class) so the doc you just named stays findable for a beat
   before it settles into the grid. */
.wm-card.is-justnamed .wm-thumb:not(.wm-thumb-new) {
  animation: wm-justnamed-fade 1.5s ease forwards;
}

@keyframes wm-justnamed-fade {
  0% {
    border-color: var(--wm-ink);
    box-shadow: 0 0 0 1px var(--wm-ink);
  }
  100% {
    border-color: var(--wm-line);
    box-shadow: 0 0 0 0 transparent;
  }
}

@media (prefers-reduced-motion: reduce) {
  /* No fade for reduced motion — but keep a static ring for the just-named
     window (cleared with the class by the JS timer) so the goal of "find the
     doc you just made" still holds without the animation. (The renaming ring is
     already static, so it needs nothing here.) */
  .wm-card.is-justnamed .wm-thumb:not(.wm-thumb-new) {
    animation: none;
    border-color: var(--wm-ink);
    box-shadow: 0 0 0 1px var(--wm-ink);
  }
}

/* New tile: darken the dashed border on focus too (hover handled below). */
.wm-card-new:focus-visible .wm-thumb-new {
  border-color: var(--wm-ink);
}

/* The real document content rendered at full page width (8.5in = 816px) and
   scaled down to the thumbnail (190/816 ≈ 0.2328). 1in padding mirrors the
   editor page. pointer-events off so clicks land on the card. */
.wm-thumb-page {
  position: absolute;
  top: 0;
  left: 0;
  width: 816px;
  min-height: 1056px;
  padding: 96px;
  background: var(--wm-paper);
  transform: scale(0.2328);
  transform-origin: top left;
  pointer-events: none;
}

/* New-document tile: a dashed page with a plus. */
.wm-card-new {
  border: none;
  background: transparent;
  padding: 0;
  cursor: pointer;
  font-family: var(--wm-sans);
  text-align: left;
}

.wm-thumb-new {
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px dashed var(--wm-line);
  background: transparent;
}

.wm-thumb-new span {
  font-size: 2.4rem;
  font-weight: 300;
  line-height: 1;
  color: var(--wm-muted);
}

.wm-card-new:hover .wm-thumb-new {
  border-color: var(--wm-ink);
}

.wm-card-new:hover .wm-thumb-new span {
  color: var(--wm-ink);
}

/* Single create on-ramp: a dashed "+" thumb that click-swaps, in place, for two
   stacked halves (blank / import). Both states share the .wm-thumb-new dashed
   frame so the tile keeps its document shape and footprint either way. */
.wm-create-toggle {
  width: 100%;
  cursor: pointer;
}

.wm-create-toggle:hover,
.wm-create-toggle:focus-visible {
  border-color: var(--wm-ink);
}

.wm-create-toggle:hover span {
  color: var(--wm-ink);
}

/* Split state: the dashed frame divided into two equal half-height targets.
   Override .wm-thumb-new's centered single-child layout to a column of halves. */
.wm-create-split {
  flex-direction: column;
}

.wm-create-half {
  flex: 1;
  width: 100%;
  border: none;
  background: transparent;
  cursor: pointer;
  font-family: var(--wm-ui-mono);
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  color: var(--wm-muted);
  transition: color 0.12s ease, background 0.12s ease;
}

/* Dashed rule between the two halves, echoing the tile's own dashed frame. */
.wm-create-half:first-child {
  border-bottom: 1px dashed var(--wm-line);
}

.wm-create-half:hover {
  color: var(--wm-ink);
  background: var(--wm-menu-hover);
}

/* Export format chips inside a doc card's ⋮ menu: a tight row of the launch
   tile's mono extension chips, sized to the menu width (no min-width floor). */
.wm-card-export {
  display: flex;
  gap: 4px;
  padding: 2px;
}

/* The export sub-step shows four chips (md/txt/docx/pdf) in a row — wider than
   the 124px text menu (rename/delete). Widen the popover for this state only so
   no chip overflows. It's right-anchored, so the extra width extends leftward;
   kept just over the 190px card so a left-column card's menu stays inside
   .wm-lib-body's padding (overflow:auto would clip anything past it). */
.wm-card-menu:has(.wm-card-export) {
  width: 204px;
}

.wm-card-export .wm-export-fmt {
  min-width: 0;
  padding: 6px 0;
}

/* The ⋮ menu button, revealed on hover/selection. */
.wm-card-menu-btn {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 26px;
  height: 26px;
  border: none;
  border-radius: var(--wm-r-sharp);
  background: var(--wm-paper-veil);
  color: var(--wm-muted);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.12s ease, color 0.12s ease;
}

.wm-card:hover .wm-card-menu-btn,
.wm-card:focus-visible .wm-card-menu-btn {
  opacity: 1;
}

.wm-card-menu-btn:hover {
  color: var(--wm-ink);
  background: var(--wm-paper);
}

.wm-card-menu {
  position: absolute;
  top: 34px;
  /* Centered over the 190px thumbnail (was right-anchored at right:6px). The
     export popover is only ~14px wider than the card, so it overhangs ~7px each
     side — within .wm-lib-body's padding, so no edge clips. */
  left: 50%;
  transform: translateX(-50%);
  z-index: 20;
  min-width: 124px;
  padding: 4px;
  background: var(--wm-paper);
  border: 1px solid var(--wm-line);
  border-radius: var(--wm-r-soft);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14);
  display: flex;
  flex-direction: column;
}

.wm-card-menu-item {
  border: none;
  background: transparent;
  text-align: left;
  padding: 7px 10px;
  border-radius: var(--wm-r-sharp);
  font-family: var(--wm-ui-mono);
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  color: var(--wm-ink);
  cursor: pointer;
}

/* The destructive confirm step (Delete → confirm). A danger tint behind danger
   text makes it an unmistakably different, deliberate target — not the same
   pixel the "delete" item occupied, so a stray second click can't trigger it
   (the cancel item is rendered in that slot instead; see Library.tsx). */
.wm-card-menu-item.is-confirm {
  background: var(--wm-danger-bg);
  color: var(--wm-danger-strong);
  font-weight: 600;
}

.wm-card-menu-item.is-confirm:hover {
  background: var(--wm-danger-bg);
  color: var(--wm-danger-strong);
}

.wm-card-menu-item:hover {
  background: var(--wm-menu-hover);
}

.wm-lib-danger {
  color: var(--wm-danger);
}

.wm-lib-danger:hover {
  color: var(--wm-danger-strong);
  background: var(--wm-danger-bg);
}

/* Name + relative date beneath the thumbnail. */
.wm-card-foot {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-height: 34px;
}

.wm-card-label {
  font-family: var(--wm-ui-mono);
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  color: var(--wm-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.wm-card:hover .wm-card-label,
.wm-card:focus-visible .wm-card-label {
  font-weight: 600;
}

.wm-card-date {
  font-family: var(--wm-ui-mono);
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  color: var(--wm-muted);
}

/* Rename happens in place on the label itself (contentEditable) — same font and
   size, just a caret. No box or underline; the card's ink ring is the only
   "this is the renaming card" signal. */
.wm-card-label.is-editing {
  outline: none;
  cursor: text;
  font-weight: 400;
  white-space: pre-wrap;
  overflow: visible;
  text-overflow: clip;
}

.wm-vault-path {
  font-size: 0.9rem;
  color: var(--wm-ink);
  font-family: var(--wm-sans);
  word-break: break-all;
}

/* ---------- Editor / pages ---------- */
.wm-doc {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* Gray workspace; tiptap-pagination-plus renders the white pages inside it. */
.wm-canvas {
  position: relative; /* positioning context for the custom caret */
  flex: 1;
  overflow: auto;
  background: var(--wm-canvas);
  padding: 28px 24px 60px;
  /* Normal block flow (not flex) so the sheet grows with content and the
     canvas scrolls; the sheet is centered with margin:auto below. Scrollbar
     styling + gutter come from the project standard (top of this file). */
}

/* Custom caret (native one is hidden via caret-color: transparent below).
   Shown/hidden via `display` from JS. Steady hard on/off blink; JS resets the
   phase on each keystroke so it stays solid while typing (Google Docs style). */
.wm-caret {
  position: absolute;
  display: none;
  width: 2px;
  background: var(--wm-ink);
  pointer-events: none;
  z-index: 10;
  animation: wm-blink 1.06s step-end infinite;
}

@keyframes wm-blink {
  0% {
    opacity: 1;
  }
  50% {
    opacity: 0;
  }
}

/* A single US Letter sheet (8.5x11in, 1in margins), paper shadow. Grows as you
   type. Continuous for now — real page breaks are a separate decision. */
.wm-page {
  width: 8.5in;
  min-height: 11in;
  margin: 0 auto; /* center horizontally in the block canvas */
  box-sizing: border-box;
  padding: 1in;
  background: var(--wm-paper);
}

/* Formatting cluster, centered in the unified bar. Groups (font+size, B/I/U,
   alignment) are separated by spacing, not dividers — see .wm-group-start. */
.wm-tools {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 3px;
  flex: 1 1 auto;
  min-width: 0;
}

/* A little air before each tool group, in place of the old vertical dividers. */
.wm-group-start {
  margin-left: 12px;
}

.wm-tool {
  min-width: 30px;
  height: 30px;
  padding: 0 8px;
  border: 1px solid transparent;
  background: transparent;
  border-radius: var(--wm-r-sharp);
  color: var(--wm-muted);
  font-size: 0.86rem;
  font-weight: 600;
  cursor: pointer;
}

.wm-tool:hover {
  background: var(--wm-canvas-hover);
  color: var(--wm-ink);
}

/* Active state is a subtle filled chip (the canvas hover tint), not an ink slab. */
.wm-tool.is-active {
  background: var(--wm-canvas-hover);
  color: var(--wm-ink);
}

.wm-tool svg {
  display: block;
}

/* Disabled tool (e.g. indent/outdent outside a list): dimmed, inert. */
.wm-tool:disabled {
  opacity: 0.3;
  cursor: default;
}

.wm-tool:disabled:hover {
  background: transparent;
  color: var(--wm-muted);
}

/* Custom font dropdown */
.wm-font {
  position: relative;
}

.wm-font-btn {
  display: flex;
  align-items: center;
  gap: 4px;
  height: 30px;
  /* Static compact box (Google-Docs style): a fixed width that never grows with
     the font name; the name truncates with an ellipsis instead. */
  width: 86px;
  padding: 0 6px 0 8px;
  border: none;
  border-radius: var(--wm-r-sharp);
  background: transparent;
  color: var(--wm-ink);
  font-family: var(--wm-sans);
  font-size: 0.82rem;
  cursor: pointer;
  outline: none;
  transition: background-color 0.12s ease;
}

.wm-font-btn:hover {
  background: var(--wm-canvas-hover);
}

/* The font name fills the box and "…"-truncates when too long. */
.wm-font-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: left;
}

.wm-font-caret {
  flex: none;
  font-size: 1.05rem;
  line-height: 1;
  color: var(--wm-muted);
}

.wm-font-menu {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: 30;
  min-width: 180px;
  max-height: 320px;
  overflow-y: auto;
  margin: 0;
  padding: 4px;
  list-style: none;
  background: var(--wm-paper);
  border: 1px solid var(--wm-line);
  border-radius: var(--wm-r-soft);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.14);
}

.wm-font-opt {
  display: block;
  width: 100%;
  text-align: left;
  padding: 7px 10px;
  border: none;
  border-radius: var(--wm-r-sharp);
  background: transparent;
  color: var(--wm-ink);
  font-size: 0.95rem;
  cursor: pointer;
}

.wm-font-opt:hover {
  background: var(--wm-canvas-hover);
}

.wm-font-opt.is-active {
  background: var(--wm-ink);
  color: var(--wm-on-ink);
}

/* Line-spacing menu: narrower than the font list, and its presets read in the
   chrome's mono voice rather than a font preview. */
.wm-spacing-menu {
  min-width: 96px;
}

.wm-spacing-menu .wm-font-opt {
  font-family: var(--wm-ui-mono);
  font-size: 0.8rem;
  letter-spacing: 0.02em;
}

/* Google-Docs-style font size: − [number] + */
.wm-size {
  display: flex;
  align-items: center;
  gap: 2px;
  margin-left: 12px; /* group air, matching .wm-group-start */
}

.wm-size-step {
  width: 24px;
  height: 30px;
  border: none;
  background: transparent;
  border-radius: var(--wm-r-sharp);
  color: var(--wm-muted);
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
}

.wm-size-step:hover {
  background: var(--wm-canvas-hover);
  color: var(--wm-ink);
}

.wm-size-input {
  width: 34px;
  height: 30px;
  padding: 0;
  border: 1px solid var(--wm-line);
  border-radius: var(--wm-r-sharp);
  background: transparent;
  color: var(--wm-ink);
  font-family: var(--wm-ui-mono);
  font-size: 0.78rem;
  text-align: center;
  outline: none;
}

.wm-size-input:focus {
  border-color: var(--wm-accent);
}

.wm-prose {
  /* Document defaults — kept in sync with DEFAULT_FONT/DEFAULT_SIZE in
     Editor.tsx so the toolbar can truthfully reflect them. */
  font-family: Georgia, serif;
  font-size: 12pt;
  /* Document default line spacing — kept in sync with DEFAULT_LINE_HEIGHT in
     lineHeight.ts so an un-spaced block's toolbar readout is truthful. */
  line-height: 1.5;
  color: var(--wm-ink);
  outline: none;
  caret-color: transparent; /* hide native caret; we draw our own */
  tab-size: 4; /* Tab inserts a real \t; render it at a sane width */
  /* Fill the page body (11in − 2×1in margins) so the whole sheet is clickable. */
  min-height: 9in;
}

.wm-prose:focus {
  outline: none;
}

.wm-prose p {
  margin: 0 0 1em; /* 1em between paragraphs — the ProseMirror / browser default */
}

.wm-prose h1 {
  font-size: 1.9rem;
  font-weight: 600;
  margin: 0.6em 0 0.4em;
}

.wm-prose h2 {
  font-size: 1.45rem;
  font-weight: 600;
  margin: 0.6em 0 0.3em;
}

.wm-prose h3 {
  font-size: 1.2rem;
  font-weight: 600;
}

.wm-prose blockquote {
  border-left: 3px solid var(--wm-line);
  margin: 0 0 1em;
  padding-left: 1em;
  color: var(--wm-muted);
  font-style: italic;
}

.wm-prose ul,
.wm-prose ol {
  padding-left: 1.4em;
  margin: 0 0 1em;
}

/* ---------- Exit gate ---------- */
.wm-gate-overlay {
  position: fixed;
  inset: 0;
  background: rgba(28, 27, 25, 0.4);
  backdrop-filter: blur(4px);
  display: grid;
  place-items: center;
  z-index: 50;
  /* Fade in over the SAME 120ms linear ramp as the native corner-dim shield
     (mac::set_corner_dim), so the rounded-corner shield and the web wash darken
     together instead of the corner snapping. Linear to match the native timing. */
  animation: wm-gate-fade-in 120ms linear;
}

@keyframes wm-gate-fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

.wm-gate-card {
  width: min(380px, 88vw);
  background: var(--wm-paper);
  border-radius: var(--wm-r-soft);
  padding: 26px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.22);
}

.wm-gate-card h2 {
  margin: 0;
  font-family: var(--wm-serif);
  font-weight: 600;
  font-size: 1.3rem;
}

.wm-gate-card p {
  margin: 0;
  color: var(--wm-muted);
  font-size: 0.9rem;
}

.wm-gate-actions {
  display: flex;
  gap: 10px;
  margin-top: 4px;
}

.wm-gate-actions button {
  flex: 1;
  /* The exit button swaps to a bare seconds countdown ("10", "9", …) mid-session;
     a single token stays on one line and the button width never changes. */
  white-space: nowrap;
}

/* Gate buttons match the settings form's launch-tile aesthetic: JetBrains Mono,
   near-square (2px) corners, ink-filled Exit mirroring Enter/Save, muted mono
   outline Cancel. Kept in sync with the .wm-settings-card button rules above. */
.wm-gate-card .wm-primary {
  margin-top: 0;
  padding: 11px 24px;
  border-radius: var(--wm-r-sharp);
  background: var(--wm-ink);
  color: var(--wm-on-ink);
  font-family: var(--wm-ui-mono);
  font-size: 0.875rem;
  font-weight: 400;
  letter-spacing: 0.02em;
  transition: background-color 0.18s ease;
}

.wm-gate-card .wm-primary:hover {
  opacity: 1;
  background: var(--wm-ink-lift); /* ink, slightly lifted — same as Enter/Save */
}

.wm-gate-card .wm-primary:disabled {
  opacity: 0.4;
}

.wm-gate-card .wm-ghost {
  padding: 11px 24px;
  border-radius: var(--wm-r-sharp);
  border-color: var(--wm-line);
  color: var(--wm-muted);
  font-family: var(--wm-ui-mono);
  font-size: 0.875rem;
  letter-spacing: 0.02em;
  transition: color 0.18s ease, border-color 0.18s ease;
}

.wm-gate-card .wm-ghost:hover {
  color: var(--wm-ink);
  border-color: var(--wm-ink);
}

/* Timer exit gate: a large monospace countdown over a muted label — UI mono to
   match the rest of the chrome (the mantra gate uses serif because that text is
   the user's prose; the countdown is chrome). Tabular figures keep the digits
   from jittering as the count ticks down. */
.wm-timer-display {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 6px 0 2px;
}

.wm-timer-readout {
  font-family: var(--wm-ui-mono);
  font-size: 1.6rem;
  font-weight: 400;
  letter-spacing: 0.02em;
  line-height: 1.1;
  color: color-mix(in srgb, var(--wm-ink) 62%, var(--wm-muted));
  font-variant-numeric: tabular-nums;
}

.wm-timer-label {
  font-family: var(--wm-ui-mono);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  color: var(--wm-muted);
}

/* Mantra gate: a gray template (.wm-mantra-ghost) with a transparent textarea
   (.wm-mantra-entry) overlaid pixel-for-pixel. The ghost's leading run turns to
   ink (.is-filled) as the matching prefix is typed; the textarea contributes
   only the caret. Both layers MUST share font, size, line-height, padding,
   width, and wrapping for the caret to track the fill. */
.wm-mantra-gate {
  position: relative;
  font-family: var(--wm-serif);
  font-size: 1.05rem;
  line-height: 1.55;
}

.wm-mantra-ghost,
.wm-mantra-entry {
  margin: 0;
  width: 100%;
  box-sizing: border-box;
  padding: 12px 14px;
  border: 1px solid var(--wm-line);
  border-radius: var(--wm-r-sharp);
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
  white-space: pre-wrap;
  overflow-wrap: break-word;
  word-break: normal;
}

.wm-mantra-ghost {
  position: relative;
  color: var(--wm-muted);
  min-height: 4.8em;
  user-select: none;
}

.wm-mantra-ghost .is-filled {
  color: var(--wm-ink);
}

/* A wrong keystroke: the typed character shown in subtle red, covering the gray
   template character it replaces. */
.wm-mantra-ghost .is-wrong-char {
  color: var(--wm-danger);
  opacity: 0.72;
}

.wm-mantra-entry {
  position: absolute;
  inset: 0;
  resize: none;
  background: transparent;
  color: transparent; /* typed text is invisible; the ghost shows the phrase */
  caret-color: transparent; /* no caret — the cursor is pinned to the end */
  border-color: transparent; /* the ghost provides the visible border */
  outline: none;
}

.wm-mantra-entry:focus {
  border-color: var(--wm-accent);
}

.wm-mantra-gate.is-wrong .wm-mantra-entry {
  border-color: var(--wm-danger);
}

/* ---------- Theme picker (Settings → Theme) ----------
   Same master/detail menu as the exit-method + wallpaper pickers (reuses
   .wm-method-md / -rail / -tab for the left): a rail of themes; selecting one
   applies it live and the right pane previews the writing surface in that theme.
   The pane's stage carries data-theme so its subtree resolves the selected
   theme's palette — a true live specimen of paper / canvas / ink / muted / line. */
.wm-th-pane {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 13px;
  background: var(--wm-canvas);
}

/* The preview window — a fixed editor-shaped frame (landscape, like the session
   window), framed like a window to match the wallpaper picker's stage. The
   editor surface inside renders in `data-theme`. */
.wm-th-stage {
  flex: none;
  width: 218px;
  height: 150px;
  overflow: hidden;
  border: 1px solid var(--wm-line);
  border-radius: var(--wm-r-soft);
  background: var(--wm-canvas); /* editor backdrop */
  display: flex;
  flex-direction: column;
}

/* The unified bar: doc name + a couple of tool glyphs, on the canvas. */
.wm-th-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 24px;
  padding: 0 10px;
  border-bottom: 1px solid var(--wm-line);
}

.wm-th-doc {
  font-family: var(--wm-ui-mono);
  font-size: 0.58rem;
  letter-spacing: 0.04em;
  color: var(--wm-muted);
}

.wm-th-tools {
  margin-left: auto;
  display: flex;
  gap: 5px;
}

.wm-th-tool {
  width: 10px;
  height: 10px;
  border-radius: var(--wm-r-sharp);
  background: var(--wm-muted);
  opacity: 0.55;
}

/* The gray workspace holding the paper sheet (top of a page, running off-frame). */
.wm-th-canvas {
  flex: 1;
  min-height: 0;
  display: flex;
  justify-content: center;
  padding: 12px 0 0;
  overflow: hidden;
}

.wm-th-sheet {
  width: 58%;
  background: var(--wm-paper);
  border: 1px solid var(--wm-line);
  border-bottom: none;
  border-top-left-radius: 3px;
  border-top-right-radius: 3px;
  padding: 12px 12px 0;
  display: flex;
  flex-direction: column;
}

/* Text as bars (no authored copy): an ink heading over body lines, one muted. */
.wm-th-h {
  height: 6px;
  width: 56%;
  border-radius: var(--wm-r-sharp);
  background: var(--wm-ink);
  margin-bottom: 9px;
}

.wm-th-line {
  height: 4px;
  border-radius: var(--wm-r-sharp);
  background: var(--wm-ink);
  opacity: 0.82;
  margin-bottom: 6px;
}

.wm-th-line.is-muted {
  background: var(--wm-muted);
  opacity: 1;
}

.wm-th-line.w1 {
  width: 100%;
}
.wm-th-line.w2 {
  width: 90%;
}
.wm-th-line.w3 {
  width: 95%;
}
.wm-th-line.w4 {
  width: 68%;
}

/* ---------- Wallpaper picker (Settings → wallpaper) ----------
   Mirrors the exit-method picker (.wm-method-md / -rail / -tab above): a left
   rail of wallpaper options; selecting one updates the right detail. Here the
   detail is a LIVE mini-login — the real login components scaled into a fixed
   window the size & shape of the default login (470×450). The rail + tabs reuse
   the method-picker classes; only the right pane + preview window are new. */
.wm-wp-pane {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 13px;
  /* Canvas (not paper) so the white login window reads as a window on a surface. */
  background: var(--wm-canvas);
}

/* The preview window: one uniform fixed size mirroring the login's 470×450,
   framed like a window (hairline + rounded + soft shadow). Clips the scaled
   screen inside it. */
.wm-wp-stage {
  flex: none;
  width: 207px; /* 470 × 0.44 */
  height: 198px; /* 450 × 0.44 */
  overflow: hidden;
  border: 1px solid var(--wm-line);
  border-radius: var(--wm-r-soft);
  background: var(--wm-paper);
}

/* The 470×450 virtual login, scaled into the stage. Everything inside renders at
   its REAL login size (.wm-quote-sea fills it, .wm-wordmark is 34px, the action
   panel + halo are exact), then this one transform shrinks the whole screen — so
   the preview is the actual login, just smaller, animating live. */
.wm-wp-screen {
  width: 470px;
  height: 450px;
  transform: scale(0.44);
  transform-origin: top left;
}

/* In the preview the card fills the fixed 450px screen, not the viewport (the
   real login uses min-height: 100vh). */
.wm-wp-screen.wm-home .wm-home-card {
  min-height: 100%;
  height: 100%;
}
