/* Story Through Time — aurora design system.
 *
 * SOURCE OF TRUTH. This file is copied verbatim to each site's assets and to
 * the app's globals.css by `scripts/sync-design.mjs`. Edit it here, then run
 * that script; do not edit the copies. The duplication is deliberate: each
 * domain deploys independently and must not fail because a sibling's asset is
 * missing, so there is no shared runtime request between them.
 *
 * Two themes, both first-class. Dark is the default because the aurora palette
 * was designed for it; light is a *rebuilt* palette rather than an inversion —
 * the accents are darkened until they clear AA on a pale surface, and the
 * decorative glows are pulled back to where they read as warmth rather than
 * smear. Half-inverting a dark palette is what produces the muddy light mode
 * everybody turns off again.
 *
 * Which theme applies:
 *   - `data-theme="dark"` or `"light"` on <html> wins, and is what a
 *     signed-in person's saved preference sets.
 *   - otherwise the operating system decides, via prefers-color-scheme.
 *
 * All foreground/background pairs in BOTH themes clear WCAG AA, asserted in
 * tests/foundation.test.ts against the worst-case stacked surface. Do not
 * adjust these by eye: on either palette a colour that looks obviously
 * readable can measure 4.1.
 *
 * No third-party requests: the one webfont is self-hosted, so the CSP stays
 * `font-src 'self'` with no external origin.
 */

/* Inter Variable, SIL Open Font License 1.1 — see assets/inter-LICENSE.txt.
 * `swap` so text is readable immediately on a slow connection rather than
 * invisible while 47KB arrives. */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/assets/inter.woff2') format('woff2');
}

:root {
  color-scheme: dark;

  --bg: #0a0818;
  --bg-deep: #06050f;
  --surface: rgba(255, 255, 255, 0.045);
  --surface-hover: rgba(255, 255, 255, 0.075);
  --border: rgba(255, 255, 255, 0.10);
  --border-bright: rgba(255, 255, 255, 0.20);

  /* Contrast against the page background AND against the translucent card
   * surfaces stacked on it, which are lighter and therefore the worst case.
   * Ratios are asserted in tests/foundation.test.ts; do not darken these by
   * eye. --faint was #7d78a0 and measured 4.09 on a hovered card: under AA. */
  --text: #f2f0ff;
  --muted: #a9a4c7;
  --faint: #918cb4;

  --violet: #a78bfa;
  --fuchsia: #e879f9;
  --cyan: #67e8f9;

  /* The ink that goes *on* an accent — a filled button, the skip link, a tag
   * label — as opposed to on the page.
   *
   * It has to flip with the theme and it was not doing so. The accents here
   * are light, so dark ink reads on them; in the light theme they are dark
   * violets and teals, where that same dark ink measured 2.6:1. A solid dark
   * button wants light text and a solid light button wants dark text, and
   * hardcoding either one means being wrong in exactly one theme.
   *
   * `--danger` is a stop in the destructive button's gradient for the same
   * reason: a coral that carries dark ink on a dark page is far too light to
   * carry white on a pale one. */
  --on-accent: #14102b;
  --danger: #f0647a;

  /* The face of something you type into.
   *
   * Every field hardcoded `rgba(255, 255, 255, 0.04)`, which is the same
   * mistake the tile face made and in the same direction: on near-black a 4%
   * white film is a readable well, and on the near-white light theme it is
   * nothing at all, so every input, select and textarea in the product
   * collapsed to a bare one-pixel outline. An empty form read as a wireframe.
   * A field has to look like it will hold what you write in either theme, so
   * it is a token, and in the light theme it is plain white — a page tinted
   * faintly lavender makes white read as inset rather than as more page. */
  --field: rgba(255, 255, 255, 0.045);

  --radius: 20px;
  --radius-sm: 12px;
  --radius-pill: 999px;

  /* A tile's face, and the glow *behind* it.
   *
   * These were hardcoded white translucency plus a blurred gradient pseudo-
   * element sitting under the tile. Two things were wrong with that. On the
   * light theme a 9%-white face over a near-white page is not a surface at
   * all, so the gradient showed straight through and body text had to compete
   * with it. And both tiles take a `transform` on hover, which creates a
   * stacking context — inside one, a `z-index: -1` child paints *above* the
   * parent's background rather than behind it, so the glow washed over the
   * face exactly when somebody was reading it.
   *
   * The glow is a box-shadow now. A shadow is drawn outside the border box by
   * construction and cannot ever cover the content, which is what "behind the
   * tile" has to mean. The face is a token so each theme can be legible on its
   * own terms rather than one palette borrowing the other's.
   *
   * The face carries the colour, and it is the *mark's* gradient — violet into
   * fuchsia into cyan, on the same 160deg the thread runs. A neutral white film
   * was what this was, and it made a tile read as a sheet of frosted glass that
   * happened to be lit from outside; the tint makes it part of the brand rather
   * than a container for it. The alphas are chosen so the composited face lands
   * at the same luminance as the 7.5%-white film it replaces, which is the
   * surface tests/foundation.test.ts already asserts every text colour against:
   * colour was added, contrast was not spent. */
  --tile: linear-gradient(
    160deg,
    rgba(167, 139, 250, 0.15) 0%,
    rgba(232, 121, 249, 0.09) 52%,
    rgba(103, 232, 249, 0.07) 100%
  );
  --tile-glow:
    0 24px 70px -24px rgba(6, 5, 15, 0.85),
    0 8px 44px -16px rgba(167, 139, 250, 0.42),
    0 -6px 36px -18px rgba(103, 232, 249, 0.30);
  --tile-glow-strong:
    0 28px 80px -24px rgba(6, 5, 15, 0.9),
    0 10px 52px -14px rgba(232, 121, 249, 0.55),
    0 -8px 44px -16px rgba(103, 232, 249, 0.40);
  /* Opaque equivalents, for browsers with no backdrop-filter. Without these
   * the translucent faces show the aurora straight through and lose contrast. */
  --panel-solid: #15122c;
  /* The flat stand-in for --tile where there is no backdrop-filter. It is the
   * violet end of the gradient composited onto the page, because that end is
   * the lightest and therefore the worst case for text on it. */
  --tile-solid: #221c3a;

  /* The sticky header is translucent over whatever scrolls beneath it, so it
   * needs its own tinted colour rather than a surface token. */
  --header-bg: rgba(10, 8, 24, 0.55);
  --header-solid: #0d0b1e;

  --shell: min(92%, 1200px);
  /* One spring, used everywhere, so the whole page moves with one personality. */
  --spring: cubic-bezier(0.22, 1, 0.36, 1);
}


/* ─── Light ─────────────────────────────────────────────────────────────────
 *
 * Rebuilt, not inverted. The accents are much darker than their dark-theme
 * counterparts because the same violet that reads beautifully on near-black
 * measures about 2:1 on near-white — the hue is kept, the lightness is not. */
:root[data-theme='light'] {
  color-scheme: light;

  --bg: #faf8ff;
  --bg-deep: #f1edfb;
  /* Surfaces tint *downwards* here: a card is slightly darker than the page,
   * which is why the worst case for contrast in this theme is the hovered
   * card rather than the page. */
  --surface: rgba(20, 16, 43, 0.035);
  --surface-hover: rgba(20, 16, 43, 0.065);
  --border: rgba(20, 16, 43, 0.14);
  --border-bright: rgba(20, 16, 43, 0.26);

  --text: #14102b;
  --muted: #443e6b;
  --faint: #534d7d;

  --violet: #6d28d9;
  --fuchsia: #a21caf;
  --cyan: #0d6a83;

  /* Accents are dark here, so the ink on them is white. */
  --on-accent: #ffffff;
  --danger: #a81640;

  /* White, not a wash of the page. A translucent film over a pale ground is
   * not a surface — see the token's own comment in the dark theme. */
  --field: #ffffff;

  /* Near-opaque *and* tinted. Two failures got it here. A 5%-dark wash was not
   * a surface at all, so the glow behind the tile came straight through it and
   * the body text had to compete with a gradient; the white sheet that replaced
   * it fixed the reading and left nothing of the palette on the page. The tint
   * belongs *in* the face, where it cannot move behind the words: the mark's
   * own violet-fuchsia-cyan run, opaque enough that nothing shows through and
   * pale enough that --muted still measures better than 7:1 on its darkest
   * end. The glow stays gentler than the dark theme's — on a pale ground the
   * same shadow that reads as light on near-black reads as dirt. */
  --tile: linear-gradient(
    160deg,
    rgba(237, 228, 255, 0.97) 0%,
    rgba(252, 241, 255, 0.95) 52%,
    rgba(226, 243, 250, 0.96) 100%
  );
  --tile-glow:
    0 18px 40px -20px rgba(20, 16, 43, 0.22),
    0 14px 44px -12px rgba(109, 40, 217, 0.38),
    0 -10px 38px -14px rgba(13, 106, 131, 0.26);
  --tile-glow-strong:
    0 22px 48px -18px rgba(20, 16, 43, 0.26),
    0 18px 56px -10px rgba(162, 28, 175, 0.48),
    0 -12px 46px -12px rgba(13, 106, 131, 0.36);
  --panel-solid: #efebfa;
  /* The flat stand-in for --tile. The violet end of the gradient, because on
   * this palette that end is the darkest and so the worst case for text. */
  --tile-solid: #ede5ff;

  --header-bg: rgba(250, 248, 255, 0.72);
  --header-solid: #f4f1fd;
}

/* No explicit choice: follow the operating system. `:not([data-theme='dark'])`
 * so somebody who chose dark keeps it on a light desktop. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme='dark']) {
    color-scheme: light;

    --bg: #faf8ff;
    --bg-deep: #f1edfb;
    --surface: rgba(20, 16, 43, 0.035);
    --surface-hover: rgba(20, 16, 43, 0.065);
    --border: rgba(20, 16, 43, 0.14);
    --border-bright: rgba(20, 16, 43, 0.26);

    --text: #14102b;
    --muted: #443e6b;
    --faint: #534d7d;

    --violet: #6d28d9;
    --fuchsia: #a21caf;
    --cyan: #0d6a83;

    /* Accents are dark here, so the ink on them is white. */
    --on-accent: #ffffff;
    --danger: #a81640;

    /* White, not a wash of the page. A translucent film over a pale ground is
     * not a surface — see the token's own comment in the dark theme. */
    --field: #ffffff;

    /* Near-opaque *and* tinted. Two failures got it here. A 5%-dark wash was not
     * a surface at all, so the glow behind the tile came straight through it and
     * the body text had to compete with a gradient; the white sheet that replaced
     * it fixed the reading and left nothing of the palette on the page. The tint
     * belongs *in* the face, where it cannot move behind the words: the mark's
     * own violet-fuchsia-cyan run, opaque enough that nothing shows through and
     * pale enough that --muted still measures better than 7:1 on its darkest
     * end. The glow stays gentler than the dark theme's — on a pale ground the
     * same shadow that reads as light on near-black reads as dirt. */
    --tile: linear-gradient(
      160deg,
      rgba(237, 228, 255, 0.97) 0%,
      rgba(252, 241, 255, 0.95) 52%,
      rgba(226, 243, 250, 0.96) 100%
    );
    --tile-glow:
      0 18px 40px -20px rgba(20, 16, 43, 0.22),
      0 14px 44px -12px rgba(109, 40, 217, 0.38),
      0 -10px 38px -14px rgba(13, 106, 131, 0.26);
    --tile-glow-strong:
      0 22px 48px -18px rgba(20, 16, 43, 0.26),
      0 18px 56px -10px rgba(162, 28, 175, 0.48),
      0 -12px 46px -12px rgba(13, 106, 131, 0.36);
    --panel-solid: #efebfa;
    --tile-solid: #ede5ff;

    --header-bg: rgba(250, 248, 255, 0.72);
    --header-solid: #f4f1fd;
  }
}

*,
*::before,
*::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  color: var(--text);
  font-family: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  font-size: 1rem;
  line-height: 1.6;
  font-feature-settings: 'cv02', 'cv03', 'cv04', 'ss01';
  -webkit-font-smoothing: antialiased;
  /* The orbs are painted on a fixed layer behind everything; without this the
   * page would scroll sideways on mobile where they overhang the viewport. */
  overflow-x: hidden;
}

/* Aurora. Three slow radial washes on a fixed layer, plus a fine grain so the
 * large flat gradients do not band on 8-bit displays. Decorative and inert:
 * pointer-events none, and aria-hidden by virtue of being a pseudo-element. */
body::before {
  content: '';
  position: fixed;
  inset: -20vmax;
  z-index: -2;
  pointer-events: none;
  background:
    radial-gradient(38vmax 38vmax at 12% 8%, rgba(167, 139, 250, 0.28), transparent 62%),
    radial-gradient(34vmax 34vmax at 88% 22%, rgba(232, 121, 249, 0.20), transparent 62%),
    radial-gradient(42vmax 42vmax at 58% 96%, rgba(103, 232, 249, 0.14), transparent 66%),
    linear-gradient(180deg, var(--bg) 0%, var(--bg-deep) 100%);
  animation: drift 4s var(--spring) both;
}

body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0.32;
  /* Inline SVG fractal noise: no extra request, no binary asset. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
}

/* The decorative layers are tuned for darkness. On a light page the same orb
 * opacities turn into a grey smear and the noise reads as a dirty screen, so
 * both are pulled back — kept, because they are the brand, but quieter. */
:root[data-theme='light'] body::before { opacity: 0.55; }
:root[data-theme='light'] body::after { opacity: 0.13; }
@media (prefers-color-scheme: light) {
  :root:not([data-theme='dark']) body::before { opacity: 0.55; }
  :root:not([data-theme='dark']) body::after { opacity: 0.13; }
}

@keyframes drift {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(-3%, 2%, 0) scale(1.08); }
}

a { color: inherit; text-decoration: none; }

:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 4px;
  border-radius: 6px;
}

.skip-link {
  position: absolute;
  top: -120px;
  left: 1rem;
  z-index: 100;
  padding: 0.75rem 1.25rem;
  border-radius: var(--radius-pill);
  background: var(--violet);
  color: var(--on-accent);
  font-weight: 600;
  transition: top 0.25s var(--spring);
}
.skip-link:focus { top: 1rem; }

/* ─── Shell ─────────────────────────────────────────────────────────────── */

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Wraps rather than overflowing: the signed-in navigation has ten
   * destinations, and a bar that pushed the sign-out button off the edge on a
   * laptop would be worse than one that takes two lines. */
  flex-wrap: wrap;
  gap: 0.75rem 2rem;
  width: 100%;
  padding: 1rem max(4vw, calc((100% - var(--shell)) / 2));
  background: var(--header-bg);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  border-bottom: 1px solid var(--border);
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: -0.01em;
}

/*
 * The lockup: the mark, then the name.
 *
 * This was a CSS gradient dot for as long as there was no real mark. Now that
 * there is one, the header shows it — but the glow the dot had is kept, as a
 * drop-shadow on the drawing rather than a box-shadow on a square, so it
 * follows the thread and the moments instead of haloing a bounding box.
 *
 * Sized in ems so the mark tracks the wordmark if the type size ever changes,
 * and given explicit width and height so the header does not reflow when the
 * SVG arrives — the mark is the first thing painted in the first row of the
 * page, which is the worst possible place to shift layout.
 */
.wordmark-mark {
  width: 1.9em;
  height: 1.9em;
  flex: none;
  /* The same optical correction as the hero mark, at header scale: the
   * drawing starts about 18% into its own box, so without this the logo sits
   * inboard of every other thing on the page's left edge. 0.184 of 1.9em. */
  margin-left: -0.35em;
  filter: drop-shadow(0 0 10px rgba(167, 139, 250, 0.55));
}

/* The hero mark, on the marketing pages: the same drawing, larger, and the
 * animated file rather than the static one. */
.brand-mark {
  display: block;
  /* Named, because the optical correction below has to be a fraction of it. */
  --mark: clamp(56px, 9vw, 88px);
  width: var(--mark);
  height: var(--mark);
  /*
   * Pulled left and up, so the *drawing* aligns rather than its bounding box.
   *
   * The thread runs corner to corner but does not reach either corner: the
   * earliest moment's centre sits at 15 of 64 with a radius of 3.25, leaving
   * about 18% of the box empty before any ink, and a similar band beneath it.
   * Left alone, the mark floats inboard of the eyebrow and heading below it
   * and reads as a stray image rather than the start of the column.
   */
  margin-left: calc(var(--mark) * -0.184);
  /* The same correction vertically: the lowest ink stops about 14% short of
   * the bottom edge, so the visual gap to the eyebrow is larger than the
   * number suggests. Tucked close, so the mark and the line beneath it read
   * as one opening gesture. */
  margin-bottom: calc(0.85rem - var(--mark) * 0.137);
  filter: drop-shadow(0 0 28px rgba(167, 139, 250, 0.45));
}

.wordmark span { color: var(--muted); font-weight: 450; }

.site-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.2rem;
}
/* The sign-out form is laid out with the sign-out button, further down. */

.site-nav a {
  padding: 0.5rem 0.95rem;
  border-radius: var(--radius-pill);
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--muted);
  transition: color 0.2s var(--spring), background 0.2s var(--spring);
}
.site-nav a:hover { color: var(--text); background: var(--surface-hover); }

main {
  width: var(--shell);
  margin: 0 auto;
  padding: clamp(3.5rem, 9vw, 7rem) 0 4rem;
}

/* ─── Type ──────────────────────────────────────────────────────────────── */

h1, h2, h3, p, ul { margin: 0; }

h1 {
  font-size: clamp(2.75rem, 7.5vw, 5.5rem);
  line-height: 0.98;
  letter-spacing: -0.045em;
  font-weight: 600;
  margin-bottom: 1.5rem;
}

/* Gradient text needs a paint fallback: if background-clip is unsupported the
 * colour below still renders, rather than leaving an invisible headline. */
h1 em {
  font-style: normal;
  color: var(--violet);
  background: linear-gradient(115deg, var(--violet) 10%, var(--fuchsia) 50%, var(--cyan) 95%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

h2 {
  font-size: clamp(1.75rem, 3.4vw, 2.6rem);
  line-height: 1.1;
  letter-spacing: -0.035em;
  font-weight: 600;
}

h3 {
  font-size: 1.0625rem;
  line-height: 1.35;
  letter-spacing: -0.015em;
  font-weight: 600;
  margin-bottom: 0.5rem;
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 1.25rem;
  padding: 0.4rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--muted);
}

.eyebrow::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--cyan);
  box-shadow: 0 0 10px var(--cyan);
}

/* Inline style attributes would need `style-src 'unsafe-inline'`, which would
 * defeat the whole point of the nonce, so spacing lives here instead. */
.band .lead { margin-top: 1.5rem; }

.lead {
  max-width: 44ch;
  font-size: clamp(1.05rem, 1.8vw, 1.3rem);
  line-height: 1.55;
  color: var(--muted);
}

.status {
  max-width: 48ch;
  margin-top: 1.5rem;
  font-size: 0.95rem;
  color: var(--faint);
}

/* ─── Links and buttons ─────────────────────────────────────────────────── */

.text-link {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 2rem;
  padding: 0.8rem 1.4rem;
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-pill);
  background: var(--surface);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  font-size: 0.925rem;
  font-weight: 550;
  transition: transform 0.25s var(--spring), background 0.25s var(--spring), border-color 0.25s var(--spring);
}

.text-link:hover {
  transform: translateY(-2px);
  background: var(--surface-hover);
  border-color: var(--violet);
}

.text-link span { transition: transform 0.25s var(--spring); }
.text-link:hover span { transform: translate(3px, -3px); }

/*
 * The 2rem above is the air a link needs when it stands alone under a
 * paragraph, and it is wrong the moment the link joins a row.
 *
 * `.actions` is a flex row, so the margin does not collapse and does not
 * centre: it pushed the secondary link about 16px below the primary button, so
 * the two pills in the hero and in the closing call to action never shared a
 * centre line — the single most visible misalignment on the page, and one no
 * bounding-box check catches because both boxes still start at the same x.
 * On a phone it was worse: the button goes full width, the row wraps, and the
 * margin opened a 48px hole between two things that are one choice.
 *
 * `.text-link.quiet` already carried this override, which is the tell that the
 * problem was known and patched one variant at a time.
 */
.actions .text-link { margin-top: 0; }

/* The row was borrowing that margin for its own space above, so give it some
 * where it follows prose. Not on `.actions` itself: inside a `.form` the grid
 * gap already spaces it, and in `.share-list` it is deliberately tighter. */
.lead + .actions,
.status + .actions { margin-top: 2rem; }

/* ─── Layout ────────────────────────────────────────────────────────────── */

.hero {
  display: grid;
  grid-template-columns: 1.25fr 1fr;
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: center;
}

.band {
  margin-top: clamp(4rem, 9vw, 8rem);
  padding-top: clamp(2.5rem, 5vw, 4rem);
  border-top: 1px solid var(--border);
}

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1rem;
  margin-top: 2.5rem;
}

.card {
  padding: 1.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  transition: transform 0.3s var(--spring), border-color 0.3s var(--spring), background 0.3s var(--spring);
}

.card:hover {
  transform: translateY(-4px);
  border-color: var(--border-bright);
  background: var(--surface-hover);
}

.card p { color: var(--muted); font-size: 0.925rem; }

/* A subheading after a row of cards had nothing at all between it and them:
 * "Fifteen questions to bring with you" sat directly on the cards' bottom
 * edge, while every other transition inside a band is 20 to 40px. The cards
 * own the space below them everywhere else because whatever follows carries
 * its own top margin; a bare h3 does not. */
.cards + h3 { margin-top: 2.5rem; }

/* ─── The memory tile ───────────────────────────────────────────────────── */

/* The light theme used to tint these tiles *downwards* — a 5%-dark wash, on
 * the reasoning that a white fill vanishes on a pale page. True, but 5% of
 * anything is not a surface either: the glow behind the tile came straight
 * through it and the body text had to compete with a gradient. Both themes now
 * take their face from --tile, which is near-opaque here. */

.memory {
  position: relative;
  padding: clamp(1.5rem, 3vw, 2.25rem);
  border: 1px solid var(--border-bright);
  border-radius: var(--radius);
  background: var(--tile);
  backdrop-filter: blur(24px) saturate(160%);
  -webkit-backdrop-filter: blur(24px) saturate(160%);
  /* The glow, thrown outward from behind the tile. See --tile-glow. */
  box-shadow: var(--tile-glow);
  transition: transform 0.4s var(--spring), box-shadow 0.4s var(--spring);
}

.memory:hover {
  transform: translateY(-5px) rotate(-0.4deg);
  box-shadow: var(--tile-glow-strong);
}

.memory .date {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin: 1.75rem 0 0.6rem;
  font-size: 0.8rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* The eyebrow already carries 1.25rem beneath it, and `.date`'s own 1.75rem
 * stacked straight on top of that — an inline-flex element's margins do not
 * collapse with its sibling's — so the opening of the example tile had a 48px
 * hole in it where every other eyebrow on the page is followed at 20px. */
.memory .eyebrow + .date { margin-top: 0; }

/* Uncertainty is shown, not hidden: a dated memory and an approximate one must
 * be visibly different, because the difference is the point of the product. */
.memory .date span {
  padding: 0.2rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: rgba(167, 139, 250, 0.14);
  color: var(--violet);
  font-size: 0.7rem;
  font-weight: 550;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.memory h2 { font-size: clamp(1.35rem, 2.4vw, 1.75rem); margin-bottom: 0.75rem; }
/* The title is the way into a memory, so it has to look like one. */
.memory h2 a { color: inherit; text-decoration: none; }
.memory h2 a:hover { text-decoration: underline; text-decoration-thickness: 2px; }
.memory h2 a:focus-visible { outline: 2px solid var(--cyan); outline-offset: 3px; border-radius: 4px; }
.memory > p:not(.eyebrow):not(.date):not(.caption) { color: var(--muted); font-size: 0.95rem; }

.memory .caption {
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
  font-size: 0.8rem;
  color: var(--faint);
}

/* ─── Prompt list ───────────────────────────────────────────────────────── */

.prompts {
  display: grid;
  gap: 0.6rem;
  margin-top: 2.5rem;
  padding: 0;
  list-style: none;
  counter-reset: prompt;
}

.prompts li {
  counter-increment: prompt;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 1rem;
  align-items: start;
  padding: 1.1rem 1.35rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--muted);
  font-size: 0.95rem;
  transition: border-color 0.25s var(--spring), background 0.25s var(--spring), transform 0.25s var(--spring);
}

.prompts li::before {
  content: counter(prompt, decimal-leading-zero);
  color: var(--violet);
  font-size: 0.75rem;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  padding-top: 0.2rem;
}

.prompts li:hover {
  transform: translateX(4px);
  border-color: var(--border-bright);
  background: var(--surface-hover);
}

/* ─── Footer ────────────────────────────────────────────────────────────── */

.site-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  width: var(--shell);
  margin: 4rem auto 0;
  padding: 2rem 0 3rem;
  border-top: 1px solid var(--border);
  font-size: 0.85rem;
  color: var(--faint);
}

.site-footer p { margin: 0; }

/* ─── Entrance motion ───────────────────────────────────────────────────── */

.reveal {
  animation: rise 0.8s var(--spring) both;
}
.reveal:nth-child(2) { animation-delay: 0.08s; }

@keyframes rise {
  from { transform: translateY(12px); }
  to   { transform: none; }
}

/* ─── Responsive ────────────────────────────────────────────────────────── */

@media (max-width: 860px) {
  .hero { grid-template-columns: 1fr; }
  .site-header { padding: 0.85rem 4vw; }
  .site-nav a,
  .site-nav button.nav-signout { padding: 0.45rem 0.7rem; font-size: 0.85rem; }
}

@media (max-width: 520px) {
  /* "Through Time" used to be dropped here, which left the header saying
   * "Story" — not an abbreviation of the name, just the first word of it, and
   * it read as text that had been cut off. It was hidden to buy a row in the
   * bar, and it does not: the signed-in nav wraps to three rows on a phone
   * either way, so the whole saving was a name. It stays until the viewport
   * is genuinely too narrow to hold it.
   *
   * `inline` rather than dropping the rule, so `.wordmark span` still has a
   * declaration here if the breakpoint above ever hides it again. */
  .wordmark span { display: inline; }
  .memory:hover { transform: none; }
}

@media (max-width: 360px) {
  .wordmark span { display: none; }
}

/* Honour the setting completely: no drift, no reveal, no hover translation.
 * Vestibular triggers are not a style preference. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .memory:hover, .card:hover, .text-link:hover, .prompts li:hover { transform: none; }
}

/* Blur is expensive and not universal. Without it the translucent surfaces
 * would show the aurora straight through and lose contrast, so fall back to
 * opaque panels. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-header { background: var(--header-solid); }
  .card, .eyebrow, .text-link, .prompts li { background: var(--panel-solid); }
  .memory, .timeline-entry { background: var(--tile-solid); }
}

/* ─── Application ───────────────────────────────────────────────────────────
 *
 * Only the signed-in product uses these. They live in the shared system so the
 * marketing pages and the app cannot drift into looking like two products.
 */

/* A reading measure, centred.
 *
 * 62ch is right for prose — much wider and the eye loses the start of the next
 * line. Left-aligned inside a 1200px shell, though, it left half the window
 * empty and looked like a layout bug rather than a decision. Centring costs
 * nothing and makes the whitespace read as deliberate.
 *
 * Pages that are a list *and* a form use `.split` below instead, which
 * genuinely uses the width rather than balancing the emptiness. */
.narrow { max-width: 62ch; margin-inline: auto; }
/* Inside a column that is already the measure, a second narrower measure on a
 * single paragraph is not restraint, it is a ragged block: on /settings the
 * "what we cannot undo" note broke at 48ch directly under a 62ch paragraph and
 * read as text that had failed to justify. `.lead` already gave its width up
 * here for the same reason; `.status` was simply missed. */
.narrow .lead,
.narrow .status { max-width: none; }

/* Two concerns side by side on a wide screen: what you have, and how to add
 * to it. Below 62rem it stacks in source order, so each page decides which of
 * the two a person meets first on a phone. */
.split {
  display: grid;
  gap: clamp(2rem, 4vw, 3.5rem);
  align-items: start;
}
@media (min-width: 62rem) {
  .split { grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr); }
}
/* The first band in each column sits directly under the page heading, so it
 * needs far less air above it than a band that follows a wall of text. */
.split > * > .band:first-child { margin-top: clamp(1.5rem, 3vw, 2.5rem); }

/* Prose inside a wide page still wants a reading measure; without this a
 * paragraph runs the full 78rem and the eye loses the next line. */
.wide .lead { max-width: 52ch; }

.notice {
  margin-bottom: 1.5rem;
  padding: 0.9rem 1.2rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  font-size: 0.95rem;
}
/* Colour is a second signal, never the only one: the text says what happened. */
.notice-error { border-color: rgba(232, 121, 249, 0.45); color: var(--text); }

.providers { display: grid; gap: 0.75rem; margin-top: 2rem; padding: 0; list-style: none; }

.provider,
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.9rem 1.4rem;
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-weight: 550;
  cursor: pointer;
  transition: transform 0.25s var(--spring), background 0.25s var(--spring), border-color 0.25s var(--spring);
}
.provider:hover { transform: translateY(-2px); background: var(--surface-hover); border-color: var(--violet); }

.button { width: auto; background: linear-gradient(135deg, var(--violet), var(--fuchsia)); color: var(--on-accent); border-color: transparent; }
/* Hover keeps the gradient. This shared a rule with .provider, which swapped
 * the background for a near-white tint while the text stayed white — every
 * primary button all but vanished under the pointer, and on phones a tap
 * leaves that state behind (the washed-out Stop button). Caught by axe on
 * "Save AI preferences" at 1.21:1. */
.button:hover { transform: translateY(-2px); filter: brightness(1.08); border-color: transparent; }

/* ─── Forms ─── */

.form { display: grid; gap: 2rem; margin-top: 2.5rem; }

.field { display: grid; gap: 0.5rem; border: 0; padding: 0; margin: 0; }
.field legend { padding: 0; font-size: 1.0625rem; font-weight: 600; letter-spacing: -0.015em; }

.field label {
  font-size: 0.85rem;
  font-weight: 550;
  color: var(--muted);
}

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 0.8rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--field);
  color: var(--text);
  font: inherit;
  font-size: 1rem;
}
.field textarea { resize: vertical; line-height: 1.6; min-height: 8rem; }

/*
 * A select that belongs to this product rather than to the operating system.
 *
 * Left alone, a <select> keeps `appearance: auto`, and no amount of border and
 * radius around it changes the control inside: it drew the platform's own
 * chevron in the platform's own grey, at the platform's own inset, sitting in
 * a row of custom-drawn inputs. Two of them are on the same screen as five
 * text fields on /people, and the mismatch is the first thing the eye finds.
 *
 * The arrow is two linear-gradients rather than an inline SVG because a
 * `data:` URI in a stylesheet is an image source, and this site's CSP does not
 * carry `data:` in `img-src` — a background image would simply not paint.
 * Gradients cost nothing and take `var(--muted)`, so the arrow flips with the
 * theme instead of needing a second copy of the rule.
 */
.field select {
  appearance: none;
  -webkit-appearance: none;
  /* Room for the arrow, so a long option does not run underneath it. */
  padding-right: 2.5rem;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--muted) 50%),
    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: right 1.35rem top 55%, right 1rem top 55%;
  background-size: 0.35rem 0.35rem;
  background-repeat: no-repeat;
}
.field input:focus, .field select:focus, .field textarea:focus { border-color: var(--violet); outline: none; }
.field input:focus-visible, .field select:focus-visible, .field textarea:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }
/* Placeholder text is a hint, not content, but it must still be readable. */
.field ::placeholder { color: var(--faint); opacity: 1; }

.hint { font-size: 0.8rem; color: var(--faint); }
/* Consecutive hints are separate remarks, not one paragraph. */
.hint + .hint { margin-top: 0.5rem; }

/* A hint that follows a row of controls rather than a label.
 *
 * Everything in this stylesheet starts at `margin: 0`, and `.hint` only ever
 * earned space back from the `.form` grid's gap. Outside a form there is no
 * gap, so on /dashboard and /profile the footnote sat hard against the bottom
 * edge of the buttons above it — close enough to read as a caption belonging
 * to the last button rather than a remark about the section. */
.actions + .hint,
.stat-grid + .hint { margin-top: 0.85rem; }

/* An empty state: "Nobody yet." straight under its heading. With every margin
 * reset to zero it touched the heading's descenders and read as a subtitle of
 * it rather than as the answer to it. */
h2 + .hint,
h3 + .hint { margin-top: 0.75rem; }

.date-fields { display: grid; gap: 0.5rem; margin: 1rem 0; }
.date-group { display: grid; gap: 0.5rem; }

/* Only the fields the chosen kind of date needs.
 *
 * Pure CSS on purpose: `:has()` reads the select's checked option, so this
 * works before — and without — any JavaScript, like every form here. Inside
 * @supports, so a browser without `:has()` shows every field rather than
 * none. The markup's `data-for` lists which precisions each group serves. */
@supports selector(:has(*)) {
  .date-group { display: none; }
  fieldset:has(select[name='precision'] option[value='day']:checked) .date-group[data-for~='day'],
  fieldset:has(select[name='precision'] option[value='month']:checked) .date-group[data-for~='month'],
  fieldset:has(select[name='precision'] option[value='year']:checked) .date-group[data-for~='year'],
  fieldset:has(select[name='precision'] option[value='season']:checked) .date-group[data-for~='season'],
  fieldset:has(select[name='precision'] option[value='decade']:checked) .date-group[data-for~='decade'],
  fieldset:has(select[name='precision'] option[value='range']:checked) .date-group[data-for~='range'],
  fieldset:has(select[name='precision'] option[value='age']:checked) .date-group[data-for~='age'],
  fieldset:has(select[name='precision'] option[value='relative']:checked) .date-group[data-for~='relative'] {
    display: grid;
  }
}

.actions { display: flex; flex-wrap: wrap; align-items: center; gap: 1.25rem; }
.text-link.quiet { margin-top: 0; padding: 0; border: 0; background: none; color: var(--muted); font-weight: 500; }
.text-link.quiet:hover { transform: none; color: var(--text); background: none; border-color: transparent; }

/* ─── Story list and timeline ─── */

/* A grid, so a shelf of stories uses the width instead of stacking down the
 * left of an empty page. `auto-fill` rather than `auto-fit`: with one story a
 * card that stretched to the full width would look like a mistake. */
.story-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr));
  gap: 1rem;
  margin: 2.5rem 0;
  padding: 0;
  list-style: none;
}

.story-list a {
  display: block;
  padding: 1.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  transition: transform 0.25s var(--spring), border-color 0.25s var(--spring), background 0.25s var(--spring);
}
/* The list already ends with 2.5rem of its own margin, and `.text-link` adds
 * another 2rem on top — so "Start another story" floated 4.5rem below the
 * shelf it belongs to while sitting only 2.5rem beneath the heading above it,
 * and read as orphaned rather than as the next thing to do. */
.story-list + .text-link { margin-top: 0; }

.story-list a:hover { transform: translateY(-3px); border-color: var(--border-bright); background: var(--surface-hover); }
.story-list h2 { font-size: 1.25rem; margin-bottom: 0.35rem; }
.story-list p { color: var(--muted); font-size: 0.925rem; }
.story-list .meta { margin-top: 0.75rem; font-size: 0.8rem; color: var(--faint); }

.timeline { display: grid; gap: 1.25rem; margin: 2.5rem 0 0; padding: 0; list-style: none; counter-reset: none; }
.timeline .memory { margin: 0; }
.timeline .memory .date { margin-top: 0; }
/* The original line breaks are part of what was written. */
.timeline .body { white-space: pre-wrap; color: var(--muted); font-size: 0.975rem; }

@media (max-width: 520px) {
  .actions { gap: 1rem; }
  /* A stacked pair, both the same width. `.button` alone went full width, so
   * a phone showed a primary action spanning the column with a secondary one
   * beside — under — it ending wherever its label happened to stop. Two
   * buttons stacked at two different widths look like two unrelated things.
   * Scoped to `.actions` so a `.text-link` sitting in prose keeps its own
   * width, which is the only place the full-width version would be wrong. */
  .button, .provider, .actions .text-link { width: 100%; }
  .actions .text-link { justify-content: center; }
}

/* ─── Photographs ──────────────────────────────────────────────────────────
 *
 * Tag rectangles read their position from four custom properties, set per tag
 * in a nonced <style> element the server emits. The production policy allows
 * no inline styles, and a rectangle needs per-instance coordinates.
 */

.gallery { display: grid; gap: 2rem; margin: 2rem 0 3rem; }

.photograph {
  margin: 0;
  padding: 1.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}

.photograph-frame {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-sm);
  background: rgba(0, 0, 0, 0.35);
  line-height: 0;
  /* The drag-to-tag gesture would otherwise be read as a scroll on touch. */
  touch-action: none;
}

.photograph-frame img { display: block; width: 100%; height: auto; }

.tag-region {
  position: absolute;
  left: var(--tag-x);
  top: var(--tag-y);
  width: var(--tag-w);
  height: var(--tag-h);
  border: 2px solid var(--cyan);
  border-radius: 6px;
  box-shadow: 0 0 0 1px rgba(10, 8, 24, 0.6), 0 0 18px rgba(103, 232, 249, 0.35);
  pointer-events: none;
}

.tag-region-draft { border-style: dashed; border-color: var(--fuchsia); }

.tag-name {
  position: absolute;
  left: 0;
  bottom: 100%;
  margin-bottom: 4px;
  padding: 0.15rem 0.5rem;
  border-radius: var(--radius-pill);
  background: var(--cyan);
  color: var(--on-accent);
  font-size: 0.7rem;
  font-weight: 650;
  line-height: 1.5;
  white-space: nowrap;
}

.photograph figcaption { margin-top: 1rem; }
.caption-text { font-size: 0.95rem; }
.photograph .meta { margin-top: 0.35rem; font-size: 0.75rem; color: var(--faint); }

.people { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 1rem 0 0; padding: 0; list-style: none; }

.people li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.35rem 0.5rem 0.35rem 0.85rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: rgba(167, 139, 250, 0.12);
  font-size: 0.8rem;
}
.person-name { font-weight: 600; }
.person-role, .person-note { color: var(--muted); }
.person-role::before, .person-note::before { content: '· '; }
.people form { display: inline; }
.people button { font-size: 0.75rem; }

.editor { margin-top: 1.25rem; border-top: 1px solid var(--border); padding-top: 1rem; }
.editor summary { cursor: pointer; font-size: 0.85rem; font-weight: 550; color: var(--muted); }
.editor summary:hover { color: var(--text); }
.form.compact { gap: 1.25rem; margin-top: 1.25rem; }

.region-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem 1rem; margin-top: 0.75rem; }

.revisions { display: grid; gap: 1.25rem; margin: 2rem 0 0; padding: 0; list-style: none; }
.revisions li { padding: 1.25rem; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); }
.revisions .meta { margin-bottom: 0.5rem; font-size: 0.75rem; color: var(--faint); }

/* Present for assistive technology, absent for everyone else. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

@media (max-width: 520px) {
  .region-fields { grid-template-columns: 1fr; }
}

/* The way in: "Sign in" on the app, "Start your story" on the public site.
 * Scoped to the anchor on purpose — see the sign-out rules below for what
 * happened when a <button> was given the same class. */
.site-nav a.nav-cta {
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border-bright);
}
.site-nav a.nav-cta:hover { border-color: var(--violet); background: var(--surface-hover); }

/*
 * Signing out: an action, not a destination, and the least important control
 * in a bar of eleven.
 *
 * This was a <button class="nav-cta">, which sounds like it inherited the
 * navigation's styling and did not: every rule that shapes a nav item is
 * written against `.site-nav a`, and a <button> is not an <a>. So it kept the
 * browser's own chrome and measured 16px/400/1px-6px with square corners
 * against its neighbours' 14px/500/7px-11px pills — larger type, a lighter
 * weight, a hard border, seven pixels shorter than the links and sitting off
 * their centre line. The most destructive thing in the header was drawn as
 * the loudest, which is the hierarchy exactly upside down.
 *
 * It is a nav item now, in the same type at the same size, and the separation
 * is made by position rather than by weight: `margin-inline-start: auto` on
 * the form parks it at the far end of the bar, away from the destinations,
 * which is also what makes the row read as deliberate when the nav wraps.
 */
.site-nav .signout {
  display: flex;
  align-items: center;
  /* Pushes the account action to the end of whichever row it lands on. The
   * form was `display: contents` to stop it stretching; a centred flex item
   * does that too, and unlike `contents` it can carry this margin. */
  margin-inline-start: auto;
}

.site-nav button.nav-signout {
  padding: 0.5rem 0.95rem;
  border: 0;
  border-radius: var(--radius-pill);
  background: none;
  font: inherit;
  font-size: 0.9rem;
  font-weight: 500;
  line-height: inherit;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.2s var(--spring), background 0.2s var(--spring);
}
.site-nav button.nav-signout:hover { color: var(--text); background: var(--surface-hover); }
/* The global focus ring rounds to 6px, which cuts the corners off a pill. */
.site-nav button.nav-signout:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
  border-radius: var(--radius-pill);
}

/* ─── Sharing and settings ─── */

.share-list { display: grid; gap: 0.75rem; margin: 1.5rem 0; padding: 0; list-style: none; }
/* A stacked card, not a two-column row.
 *
 * This was `display: flex; justify-content: space-between`, which reads well
 * for exactly two children — a title and a button — and turns every extra
 * paragraph into another squeezed column. The richer cards (a person with a
 * relationship, aliases and two counts) came out as six vertical slivers.
 * Stacking is right for all of them, and the action simply sits at the end. */
.share-list li {
  display: grid;
  justify-items: start;
  gap: 0.35rem;
  padding: 1rem 1.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
/* The action gets a little air, wherever in the card it lands. */
.share-list li form { margin-top: 0.5rem; }
.share-list li .actions { margin-top: 0.35rem; }
.share-list.ended li { opacity: 0.6; }
.share-title { font-weight: 600; }
.share-list .meta { font-size: 0.8rem; color: var(--faint); margin-top: 0.2rem; }

/* Self-sufficient, not a modifier.
 *
 * This used to carry only the overrides and rely on `.button` being present
 * alongside it. That is a class that works or does not depending on what else
 * someone remembered to type, and it duly broke: every secondary button added
 * later said `className="quiet-button"` alone and rendered as an unpadded
 * outline. `.button.quiet-button` still works, so nothing that already pairs
 * them had to change. */
.quiet-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  width: auto;
  padding: 0.6rem 1.1rem;
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 0.9rem;
  font-weight: 550;
  cursor: pointer;
  transition: transform 0.25s var(--spring), background 0.25s var(--spring), border-color 0.25s var(--spring);
}
.quiet-button:hover {
  transform: translateY(-1px);
  border-color: var(--fuchsia);
  background: var(--surface-hover);
}
.quiet-button:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }

/* Destructive: still one colour among several signals — the label and the
 * typed confirmation carry the meaning, the colour only agrees with them. */
.button.danger { background: linear-gradient(135deg, var(--fuchsia), var(--danger)); }

.link-reveal { border-color: var(--cyan); }
/* The same field surface as everywhere else. This was a hardcoded 30% black,
 * which on the dark theme is a well and on the light theme is a charcoal slab
 * carrying near-black `--text` — the one field in the product you are meant to
 * read a secret out of was the hardest one to read. */
.link-value input {
  width: 100%; padding: 0.7rem 0.9rem; border: 1px solid var(--border-bright);
  border-radius: var(--radius-sm); background: var(--field); color: var(--text);
  font: inherit; font-size: 0.85rem;
}
.link-reveal .actions { margin-top: 0.75rem; }
code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.9em; color: var(--cyan); }

/* ─── The timeline across everything ────────────────────────────────────── */

/* Wider than a reading column, because this page is a list with a rail beside
 * it rather than prose. */
.wide { max-width: 78rem; }

.timeline-layout {
  display: grid;
  gap: clamp(2rem, 4vw, 3.5rem);
  margin-top: 3rem;
  grid-template-columns: minmax(14rem, 18rem) minmax(0, 1fr);
  align-items: start;
}
/* One column on a phone, with the filters below the memories: someone on a
 * small screen wants to read first and narrow second. */
@media (max-width: 52rem) {
  .timeline-layout { grid-template-columns: 1fr; }
  .timeline-filters { order: 2; }
  .timeline-entries { order: 1; }
}

.timeline-filters {
  position: sticky;
  top: 5.5rem;
  display: grid;
  gap: 1.75rem;
  padding: 1.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
}
@media (max-width: 52rem) { .timeline-filters { position: static; } }

.timeline-filters .search { display: grid; gap: 0.6rem; justify-items: stretch; }
.timeline-filters .search .quiet-button { width: 100%; }
.timeline-filters .search label { font-size: 0.85rem; font-weight: 550; color: var(--muted); }
.timeline-filters .search input {
  width: 100%;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, 0.04);
  color: var(--text);
  font: inherit;
}
.timeline-filters .search input:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }

.facet h2 {
  margin-bottom: 0.75rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--faint);
}
.facet ul { display: flex; flex-wrap: wrap; gap: 0.4rem; list-style: none; padding: 0; margin: 0; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--muted);
  font-size: 0.8rem;
  text-decoration: none;
  transition: border-color 0.2s var(--spring), color 0.2s var(--spring);
}
.chip:hover { border-color: var(--border-bright); color: var(--text); }
.chip-on { border-color: var(--violet); background: rgba(167, 139, 250, 0.16); color: var(--text); }
.chip-count { font-size: 0.7rem; color: var(--faint); font-variant-numeric: tabular-nums; }
.chip-on .chip-count { color: var(--violet); }

/* Scoped to beat `.facet ul` above, which is a flex row and was turning this
 * into wrapping chips with the count jammed against the label. A one-class
 * selector could not win that, and the symptom looked like a typography bug
 * rather than a layout one. */
.facet .decade-list { display: grid; gap: 0.35rem; list-style: none; padding: 0; margin: 0; }
.facet .decade-list li {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  font-size: 0.85rem;
  color: var(--muted);
}
.decade-label { font-variant-numeric: tabular-nums; }

/* The spine: one line down the left, with each memory hanging off it. */
.timeline-spine { list-style: none; padding: 0; margin: 2rem 0 0; display: grid; gap: 1.5rem; }
.timeline-spine > li { position: relative; padding-left: 1.75rem; }
.timeline-spine > li::before {
  content: '';
  position: absolute;
  left: 0.3rem;
  top: 0.65rem;
  bottom: -1.5rem;
  width: 1px;
  background: linear-gradient(180deg, var(--border-bright), transparent);
}
.timeline-spine > li:last-child::before { bottom: auto; height: 0; }
.timeline-spine > li::after {
  content: '';
  position: absolute;
  left: 0;
  top: 0.5rem;
  width: 0.65rem;
  height: 0.65rem;
  border-radius: 50%;
  background: var(--violet);
  box-shadow: 0 0 12px var(--violet);
}

.timeline-entry {
  position: relative;
  padding: 1.25rem 1.5rem;
  border: 1px solid var(--border-bright);
  border-radius: var(--radius);
  background: var(--tile);
  backdrop-filter: blur(18px) saturate(150%);
  -webkit-backdrop-filter: blur(18px) saturate(150%);
  /* The aurora glow, behind the tile rather than under its face. Softer than
   * `.memory`, because twenty-five of these scroll past at once and the page
   * should not look like a fairground. Decorative: it carries no meaning. */
  box-shadow: var(--tile-glow);
  transition: transform 0.3s var(--spring), border-color 0.3s var(--spring),
              box-shadow 0.3s var(--spring);
}

.timeline-entry:hover {
  transform: translateY(-2px);
  box-shadow: var(--tile-glow-strong);
}

/* A life event is a fact rather than a recollection, so it glows cyan and
 * stays quieter than the memories around it. */
.timeline-event::before {
  background: linear-gradient(135deg, var(--cyan), var(--violet));
  opacity: 0.12;
}
.timeline-entry h2 { font-size: 1.15rem; margin: 0.35rem 0; }
.timeline-entry h2 a { color: var(--text); text-decoration: none; }
.timeline-entry h2 a:hover { text-decoration: underline; }

.entry-date {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.8rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.entry-certainty {
  padding: 0.1rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  font-size: 0.65rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--faint);
}
.entry-story { font-size: 0.8rem; color: var(--faint); }
.entry-story a { color: var(--muted); }
.entry-excerpt { margin-top: 0.75rem; font-size: 0.925rem; line-height: 1.6; color: var(--muted); }
.entry-meta { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.75rem; }
.entry-meta span {
  padding: 0.15rem 0.55rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  font-size: 0.7rem;
  color: var(--faint);
}

.pager { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-top: 2.5rem; }
.pager a { color: var(--muted); text-decoration: none; font-size: 0.9rem; }
.pager a:hover { color: var(--text); text-decoration: underline; }
.pager-position { font-size: 0.8rem; color: var(--faint); font-variant-numeric: tabular-nums; }

/* ─── Profile, settings and administration ──────────────────────────────── */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 1rem;
  margin-top: 1.5rem;
  list-style: none;
  padding: 0;
}
.stat-grid li {
  padding: 1.1rem 1.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  font-size: 0.85rem;
  color: var(--muted);
}
.stat-grid strong {
  display: block;
  font-size: 1.75rem;
  font-weight: 650;
  letter-spacing: -0.02em;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* A checkbox and its label read as one line; the hint sits under both. */
.field-check label {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--text);
  cursor: pointer;
}
.field-check input[type='checkbox'] {
  width: 1.1rem;
  height: 1.1rem;
  margin-top: 0.15rem;
  flex: 0 0 auto;
  accent-color: var(--violet);
}

.switch-list { display: grid; gap: 1rem; margin-top: 1.5rem; list-style: none; padding: 0; }
.switch {
  padding: 1.25rem 1.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
.switch-label { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; margin-bottom: 0.4rem; }
.switch form { display: grid; gap: 0.6rem; justify-items: start; }

.pill {
  padding: 0.15rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  font-size: 0.68rem;
  font-weight: 550;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--faint);
}
.pill-on { border-color: var(--cyan); background: rgba(34, 211, 238, 0.14); color: var(--cyan); }

.audit-list { display: grid; gap: 0.75rem; margin-top: 1.5rem; list-style: none; padding: 0; }
.audit-list li {
  padding: 0.9rem 1.1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
.audit-list .meta { font-size: 0.75rem; color: var(--faint); font-variant-numeric: tabular-nums; }

/* ─── The Story Guide ───────────────────────────────────────────────────── */

.guide-asks { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 1.5rem; }

.suggestion {
  margin-top: 1.5rem;
  padding: 1.25rem 1.5rem;
  border: 1px solid var(--cyan);
  border-radius: var(--radius-sm);
  background: rgba(34, 211, 238, 0.06);
  display: grid;
  gap: 0.9rem;
  justify-items: start;
}
.suggestion .body { font-size: 0.95rem; line-height: 1.7; }

/* ─── Dates that come round ─────────────────────────────────────────────── */

.upcoming-list { display: grid; gap: 0.75rem; margin-top: 1.5rem; list-style: none; padding: 0; }
.upcoming-list li {
  padding: 1rem 1.25rem;
  border: 1px solid var(--border);
  border-left: 2px solid var(--cyan);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
.upcoming-when {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--cyan);
}
.upcoming-list .share-title { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }

/* A life event on the timeline: the same tile, quieter, so the memories still
 * carry the page. The marker is a different colour so the eye can tell a fact
 * from a recollection without reading either. */
.timeline-event { border-style: dashed; }
.timeline-event h2 { font-size: 1.05rem; }
.timeline-spine > li:has(.timeline-event)::after {
  background: var(--cyan);
  box-shadow: 0 0 10px var(--cyan);
}

/* ─── Filing a memory ───────────────────────────────────────────────────── */

.organise-groups { display: grid; gap: 1.5rem; margin-top: 1.5rem; }
.organise-groups h3 {
  margin-bottom: 0.6rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--faint);
}

/* A row of toggles. Each is its own form, so the list is of forms rather than
 * of buttons — which is what makes the page work with no JavaScript. */
.chip-row { display: flex; flex-wrap: wrap; gap: 0.4rem; list-style: none; padding: 0; margin: 0; }
.chip-row form { display: contents; }

/* `.chip` is styled as a link elsewhere; as a button it needs the defaults
 * reset or the browser draws its own. */
button.chip { cursor: pointer; font: inherit; font-size: 0.8rem; }
button.chip:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }

/* One field and its button on a line, for the small corrections. */
.inline-form {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: 0.75rem;
  margin-top: 1.5rem;
}
.inline-form .field { flex: 1 1 16rem; }

/* ─── Hearts, comments and views ────────────────────────────────────────── */

.engagement-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
  margin-top: 1.5rem;
}
.engagement-summary form { display: contents; }

.comment-list { display: grid; gap: 0.75rem; margin-top: 1.5rem; list-style: none; padding: 0; }
.comment {
  display: grid;
  justify-items: start;
  gap: 0.35rem;
  padding: 0.9rem 1.1rem;
  border: 1px solid var(--border);
  border-left: 2px solid var(--violet);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
.comment .body { font-size: 0.95rem; line-height: 1.6; }
.comment form { margin-top: 0.4rem; }

/* Hidden by the owner: still visible to its author and to the owner, and
 * plainly marked as hidden rather than quietly vanishing. */
.hidden-comment { border-left-color: var(--faint); opacity: 0.65; }

/* ─── The application shell ─────────────────────────────────────────────────
 *
 * Everything in this block applies only inside `.app-shell`, which is the body
 * class on storythroughtime.app and on nothing else.
 *
 * Two problems, one cause. The app was inheriting the public site's vertical
 * rhythm — an 8rem gap between bands, a 7rem run-in before the first heading,
 * and a 5.5rem h1 — which is right for a page somebody scrolls once and wrong
 * for a page somebody works in every day. A dashboard with nothing written yet
 * rendered as one enormous heading floating in an empty window, and even a
 * full one pushed "Your story so far" below the fold for no reason.
 *
 * The same inheritance made the two sites indistinguishable: identical
 * chrome, identical spacing, identical type scale, so the only signal that you
 * had crossed from the public site into your own writing was the URL. Giving
 * the app its own density is the honest fix for both — a tool should not look
 * like a brochure, and the difference should be legible in the first second
 * rather than in the address bar.
 *
 * The palette, the mark and the type are deliberately *not* changed. This is
 * the same product, and a different accent colour per site would be costume
 * rather than architecture. What changes is how tightly it is packed.
 */

/* Roomy enough to breathe, close enough that the first real content is
 * visible without scrolling. */
.app-shell main { padding: 2.25rem 0 3.5rem; }

/* A working heading, not a billboard. It still opens the page; it no longer
 * occupies a third of it. */
.app-shell h1 {
  font-size: clamp(2rem, 4vw, 2.9rem);
  line-height: 1.05;
  letter-spacing: -0.03em;
  margin-bottom: 1rem;
}

.app-shell h2 { font-size: clamp(1.25rem, 2.2vw, 1.6rem); }

/* The single biggest source of dead space. On the public site a band is a new
 * chapter and earns its 8rem; here it is the next panel of the same screen. */
.app-shell .band {
  margin-top: 2.25rem;
  padding-top: 1.75rem;
}

.app-shell .lead { font-size: 1.05rem; margin-bottom: 1rem; }
.app-shell .hero { align-items: start; }

/* A solid bar rather than the public site's translucent blur, with a hairline
 * of the brand gradient under it. It reads as the top of an application
 * window, which is what it is, and it is the part a person sees first on every
 * page — so it is where the distinction has to be made.
 */
.app-shell .site-header {
  background: var(--header-solid);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-bottom: 1px solid var(--border);
  position: relative;
  padding-block: 0.7rem;
}

.app-shell .site-header::after {
  content: '';
  position: absolute;
  inset-inline: 0;
  bottom: -1px;
  height: 2px;
  background: linear-gradient(90deg, var(--violet), var(--fuchsia), var(--cyan));
  /* Decorative. Kept subtle so it marks the surface rather than decorating it. */
  opacity: 0.75;
}

/* The mark stays, the glow goes: a header worn all day should not shimmer. */
.app-shell .wordmark-mark { filter: none; }

/* The signed-in bar carries eleven destinations. At the public site's spacing
 * it wrapped to two lines on a laptop; this keeps it to one for longer without
 * making anything smaller than a comfortable target. */
.app-shell .site-nav { gap: 0.1rem; }
.app-shell .site-nav a,
.app-shell .site-nav button.nav-signout { padding: 0.45rem 0.7rem; font-size: 0.875rem; }

/* Twelve items never fit beside the wordmark, so the bar wraps on every screen
 * there is — which it was doing already, but by accident: `space-between` left
 * the second row ragged against the left edge with the whole right half empty,
 * and it read as a layout that had come apart rather than one that was meant.
 * Letting the nav claim the remaining width makes the wrap deliberate, and the
 * sign-out button's auto margin then anchors the far end of the row. */
.app-shell .site-nav { flex: 1 1 auto; }

/* ─── Alignment ─────────────────────────────────────────────────────────────
 *
 * A timeline indents its tiles by the width of the spine, so anything that
 * belongs *to* the list has to be indented with them or it hangs off the left
 * edge of the column it is describing. The caption under the example timeline
 * did exactly that, and against a stack of tiles a single flush-left line
 * reads as a mistake rather than a note.
 *
 * Keyed off the spine itself rather than applied to every caption, so a
 * caption anywhere else keeps the column it is in.
 */
.caption {
  margin-top: 1rem;
  font-size: 0.9rem;
  color: var(--faint);
}

.timeline-spine + .caption {
  /* The same 1.75rem the list items use for the spine gutter. A margin rather
   * than padding, so the caption's box lines up with the tiles above it and
   * not merely its text. */
  margin-left: 1.75rem;
}

/* ─── The photograph library ────────────────────────────────────────────────
 *
 * A grid of pictures rather than a list of tiles: at /media the photograph is
 * the thing being scanned, and the words around it are labels, not reading.
 *
 * `auto-fill` rather than `auto-fit`, which is the opposite of the choice the
 * card rows make. With `auto-fit` a library holding two photographs stretches
 * them across the full width and a snapshot becomes a mural; `auto-fill` keeps
 * the track size so two pictures look like two pictures.
 */
.photograph-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
  gap: 1.25rem;
  margin: 2rem 0 0;
  padding: 0;
  list-style: none;
}

.photograph-grid figure {
  margin: 0;
  display: grid;
  /* The caption is pinned to the bottom of the cell, so a row of pictures with
   * one- and two-line captions still has its images aligned along the top. */
  grid-template-rows: auto 1fr;
  gap: 0.6rem;
  height: 100%;
}

.photograph-grid img,
.photograph-missing {
  display: block;
  width: 100%;
  /* A fixed ratio, so the page does not reflow as each picture arrives and so
   * a portrait and a landscape shot sit in the same grid without one of them
   * dictating the row height. `cover` crops rather than distorts; the whole
   * photograph is always one click away in the memory itself. */
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
}

.photograph-grid a:hover img { border-color: var(--border-bright); }

/* Said, rather than drawn as a broken image icon. */
.photograph-missing {
  display: grid;
  place-items: center;
  font-size: 0.8rem;
  color: var(--faint);
}

.photograph-grid figcaption {
  font-size: 0.85rem;
  line-height: 1.45;
  color: var(--muted);
}

.photograph-grid figcaption a { text-decoration: underline; text-underline-offset: 2px; }
.photograph-grid figcaption a:hover { color: var(--text); }

/* A radio and its words, as one target.
 *
 * The label wraps the input rather than pointing at it by id, so the whole row
 * is clickable without a `for`/`id` pair to keep in step. Laid out as a row
 * with the control at a fixed size, because a radio left to its own devices
 * shrinks under `align-items: stretch` in the grid these sit in. */
.choice {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.55rem 0.2rem;
  font-size: 0.95rem;
  cursor: pointer;
}

.choice input {
  flex: none;
  width: 1.05rem;
  height: 1.05rem;
  accent-color: var(--violet);
  cursor: pointer;
}

/* The legend is the question; the choices under it are answers, so they are
 * indented from it the way a list is from its heading. */
.field legend + .choice { margin-top: 0.35rem; }

/* Where you are.
 *
 * Eleven navigation items that looked identical no matter which page was open;
 * the only thing saying where you were was the eyebrow at the top of the page.
 * Keyed off `aria-current`, which the header sets, so the thing a screen reader
 * announces and the thing a sighted person sees are the same fact rather than
 * two that can drift apart. */
.site-nav a[aria-current='page'] {
  color: var(--text);
  background: var(--surface-hover);
}

/* A weight change alone would be ambiguous against the hover state, so the
 * current item also carries a rule beneath it in the brand gradient. */
.site-nav a[aria-current='page']::after {
  content: '';
  display: block;
  height: 2px;
  margin: 0.3rem -0.35rem -0.15rem;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--violet), var(--fuchsia));
}

/* ─── Grouped navigation ────────────────────────────────────────────────────
 *
 * Built on `<details>`, so it opens and closes with no JavaScript at all and
 * arrives keyboard-operable and announced as expandable. The cost is that it
 * cannot close when you click elsewhere, which is the right trade: a menu that
 * needs script would be invisible to anybody script has not reached, and this
 * product's forms all work before it arrives.
 *
 * Positioned rather than inline, because an open drawer that pushes the rest
 * of the bar sideways moves the thing somebody was aiming at.
 */
.nav-group { position: relative; }

.nav-group > summary {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.45rem 0.7rem;
  border-radius: var(--radius-pill);
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--muted);
  cursor: pointer;
  list-style: none;
  white-space: nowrap;
  transition: color 0.2s var(--spring), background 0.2s var(--spring);
}

/* Both spellings: WebKit still wants the pseudo-element, everything else
 * honours `list-style: none`. Without them the browser draws its own triangle
 * beside the one below. */
.nav-group > summary::-webkit-details-marker { display: none; }
.nav-group > summary::marker { content: ''; }

/* The chevron, drawn in CSS rather than shipped as an icon. */
.nav-group > summary::after {
  content: '';
  width: 0.4rem;
  height: 0.4rem;
  margin-top: -0.2rem;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg);
  transition: transform 0.2s var(--spring);
}
.nav-group[open] > summary::after { transform: rotate(-135deg) translate(-0.15rem, -0.15rem); }

.nav-group > summary:hover { color: var(--text); background: var(--surface-hover); }
.nav-group > summary:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
}

/* Marks the group holding the page you are on, so a closed drawer still says
 * where you are. */
.nav-group > summary[data-current='true'] { color: var(--text); }

.nav-group-items {
  position: absolute;
  top: calc(100% + 0.4rem);
  right: 0;
  z-index: 60;
  display: grid;
  gap: 0.1rem;
  min-width: 11rem;
  padding: 0.4rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--panel-solid);
  box-shadow: 0 18px 40px -18px rgba(6, 5, 15, 0.7);
}

.nav-group-items a { display: block; }

/* On a phone the drawer is a full-width sheet hung from the bottom of the
 * header, over the page.
 *
 * It used to open in the flow, inside the wrapped row of links. That made the
 * row as tall as the drawer — Home, Timeline and Write slid down to its middle —
 * and, because the header is sticky, two open drawers left the page a strip at
 * the bottom of the screen. `position: static` on the group hands the
 * containing block to the header (sticky counts as positioned), so the sheet
 * spans the screen whichever button opened it, and the header keeps its height.
 */
@media (max-width: 40rem) {
  .nav-group { position: static; }
  .nav-group-items {
    top: 100%;
    left: 0;
    right: 0;
    min-width: 0;
    max-height: calc(100dvh - 100% - 1rem);
    overflow-y: auto;
    padding: 0.5rem 4vw 0.75rem;
    border-width: 0 0 1px;
    border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  }
  /* Thumb-sized rows: a 40px-tall link list is fine for a pointer and a
   * guessing game for a finger. */
  .nav-group-items a { padding: 0.8rem 0.9rem; font-size: 1rem; }
}

/* The group holding the page you are on carries the same underline as a
 * current link, so a closed drawer still says where you are. */
.nav-group > summary[data-current='true']::before {
  content: '';
  position: absolute;
  left: 0.7rem;
  right: 1.5rem;
  bottom: 0.15rem;
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--violet), var(--fuchsia));
}
.nav-group > summary { position: relative; }

/* A single, quiet navigation rhythm. Account holds the secondary actions. */
.app-shell .site-nav > a,
.app-shell .nav-group > summary {
  min-height: 44px;
  display: flex;
  align-items: center;
  position: relative;
  padding: 0.65rem 0.8rem;
  border-radius: 0.65rem;
}
.app-shell .site-nav > a[aria-current='page']::after {
  position: absolute;
  bottom: 0.25rem;
  left: 0.8rem;
  right: 0.8rem;
  margin: 0;
}
.site-nav .nav-group-items .signout {
  margin: 0.3rem 0 0;
  padding: 0.3rem 0 0;
  border-top: 1px solid var(--border);
  display: block;
}
.site-nav .nav-group-items button.nav-signout {
  width: 100%;
  min-height: 44px;
  text-align: left;
  justify-content: flex-start;
}

/* One moment is enough: space for the invitation, a thread still unfolding. */
.story-start {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  gap: clamp(1rem, 4vw, 4rem);
  align-items: center;
}
.story-start h1 { font-size: clamp(2.5rem, 5vw, 4.6rem); line-height: 1.08; }
.story-thread { width: 100%; height: auto; max-width: 440px; color: var(--violet); overflow: visible; }
.thread-orbit { opacity: 0.12; stroke-width: 1; transform: rotate(-20deg); transform-origin: center; }
.thread-paper { fill: var(--panel-solid); stroke: var(--border); stroke-width: 1.5; }
.thread-paper path { fill: none; stroke: var(--violet); opacity: 0.35; stroke-width: 2; stroke-linecap: round; }
.thread-paper circle { fill: var(--cyan); stroke: none; opacity: 0.5; }
.thread-paper-back { transform: rotate(-9deg); transform-origin: 138px 160px; }
.thread-paper-front { transform: rotate(7deg); transform-origin: 282px 130px; }
.thread-line { stroke: var(--violet); stroke-width: 2; stroke-linecap: round; stroke-dasharray: 1; }
.thread-moments { fill: var(--fuchsia); }
.thread-present { fill: var(--panel-solid); stroke: var(--cyan); stroke-width: 3; }
.story-thread-compact { width: 130px; flex: 0 0 auto; }
.capture-choices { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.65rem; margin-top: 1.8rem; }
.capture-choice { display: grid; align-content: start; gap: 0.35rem; padding: 1rem; color: var(--text); text-decoration: none; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); transition: border-color 180ms, transform 180ms; }
.capture-choice > span { color: var(--violet); font-size: 1.5rem; }
.capture-choice small { color: var(--muted); line-height: 1.45; }
.capture-choice:hover { border-color: var(--violet); transform: translateY(-3px); }
.capture-choice:focus-visible { outline: 2px solid var(--cyan); outline-offset: 4px; }
.prompt-invitation { margin-top: 3rem; padding: 1.5rem 0; border-block: 1px solid var(--border); }
.prompt-invitation h2 { margin-top: 0; }
.welcome-exit { margin-top: 1.5rem; }
.guided-progress { display: flex; gap: 1rem; margin: 1.5rem 0 2rem; flex-wrap: wrap; }
.guided-progress a { display: flex; align-items: center; gap: 0.4rem; min-height: 44px; color: var(--muted); text-decoration: none; }
.guided-progress span { display: grid; place-items: center; width: 1.6rem; height: 1.6rem; border: 1px solid var(--border); border-radius: 50%; font-size: 0.8rem; }
.guided-progress [aria-current='step'] { color: var(--text); font-weight: 600; }
.guided-progress [aria-current='step'] span { border-color: var(--violet); background: var(--surface-hover); }

.story-composer { max-width: 52rem; margin: 1.5rem auto 0; gap: 1rem; }
.story-composer > .hint, .story-composer > .composer-status { margin: 0; }
.composer-intro > .story-thread { float: right; margin: 0 0 1rem 1rem; }
.composer-intro > .story-composer { clear: both; }
.composer-intro > .composer-tools { display: block; font-size: 0.9rem; }
.composer-intro h1 { margin-bottom: 0.5rem; }
.composer-modes { display: flex; gap: 0.4rem; flex-wrap: wrap; margin: 0.25rem 0 0.75rem; padding: 0.35rem; width: fit-content; max-width: 100%; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); }
.composer-mode { min-height: 44px; padding: 0.6rem 1rem; border: 1px solid transparent; border-radius: 0.5rem; background: transparent; color: var(--muted); font: inherit; cursor: pointer; text-decoration: none; }
.composer-mode:hover, .composer-mode[aria-pressed='true'], .composer-mode[aria-current='true'], .composer-mode[aria-selected='true'] { color: var(--text); background: var(--surface-hover); border-color: var(--border); }
.composer-mode:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }
.composer-content { padding-block: 0.5rem 1rem; scroll-margin-top: 10rem; }
.composer-content textarea { min-height: 10rem; font-size: 1.05rem; line-height: 1.7; }
.composer-record, .composer-photo { scroll-margin-top: 10rem; padding: 0.65rem 1rem; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); }
.composer-attachment > summary { cursor: pointer; color: var(--text); min-height: 44px; display: list-item; align-content: center; }
.composer-attachment[open] > summary { margin-bottom: 0.8rem; }
.composer-attachment > summary:focus-visible { outline: 2px solid var(--cyan); outline-offset: 4px; }
.story-composer[data-mode='record'] .composer-record,
.story-composer[data-mode='photo'] .composer-photo { border-color: var(--violet); }
.composer-details { margin-block: 1rem; border-block: 1px solid var(--border); padding: 0.6rem 0; }
.composer-details > summary { padding: 0.65rem 0.1rem; min-height: 44px; color: var(--text); cursor: pointer; }
.composer-details[open] > summary { margin-bottom: 0.8rem; }
.composer-details > summary:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }
.composer-footer { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; padding-block: 1rem; border-top: 1px solid var(--border); }
.composer-status { font-size: 0.85rem; color: var(--muted); min-height: 1.5em; }
.composer-preview { max-width: 100%; border-radius: var(--radius-sm); }
.composer-preview img { display: block; width: auto; max-width: 100%; max-height: 22rem; object-fit: contain; border-radius: var(--radius-sm); }
.composer-prompt { margin-block: 1rem 1.5rem; border-left: 3px solid var(--violet); padding: 0.6rem 1rem; color: var(--muted); }
.composer-error { border-left: 3px solid var(--fuchsia); padding: 0.8rem 1rem; background: var(--surface); }
.composer-tools { display: flex; gap: 0.75rem; flex-wrap: wrap; align-items: center; }
@media (prefers-reduced-motion: no-preference) {
  .thread-line { animation: story-thread-draw 900ms ease-out both; }
  .thread-paper-front { animation: story-paper-arrive 650ms ease-out both; }
  .thread-moments { animation: story-moments-arrive 650ms 250ms ease-out both; }
  .nav-group[open] .nav-group-items { animation: story-menu-arrive 160ms ease-out; }
}
@keyframes story-thread-draw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
@keyframes story-paper-arrive { from { opacity: 0; transform: translateY(8px) rotate(7deg); } to { opacity: 1; transform: translateY(0) rotate(7deg); } }
@keyframes story-moments-arrive { from { opacity: 0; } to { opacity: 1; } }
@keyframes story-menu-arrive { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
@media (max-width: 48rem) {
  .story-start { grid-template-columns: 1fr; }
  .story-start > .story-thread { display: none; }
  .story-thread-compact { width: 95px; }
}
@media (max-width: 40rem) {
  .app-shell .site-nav { justify-content: space-between; gap: 0; }
  .app-shell .site-nav > a, .app-shell .nav-group > summary { padding-inline: 0.45rem; font-size: 0.8rem; }
  .capture-choices { gap: 0.4rem; }
  .capture-choice { padding: 0.8rem 0.55rem; }
  .capture-choice small { font-size: 0.75rem; }
  .composer-intro .story-thread { display: none; }
  .composer-mode { padding-inline: 0.7rem; }
  .composer-footer > .button { flex: 1 1 100%; justify-content: center; }
  .composer-footer { background: var(--panel-solid); padding: 0.8rem; border: 1px solid var(--border); border-radius: var(--radius-sm); }
}
@media (prefers-reduced-motion: reduce) {
  .story-thread *, .nav-group-items, .capture-choice { animation: none !important; transition: none !important; }
  .capture-choice:hover { transform: none; }
}

/* A second label inside one field, for a pair that belongs together — a name
 * and who that person was to you. Quieter than the field's own label so the
 * pair still reads as one question rather than two. */
.sub-label {
  margin-top: 0.4rem;
  font-size: 0.78rem;
  font-weight: 450;
  color: var(--faint);
}

/* Voice memories on a memory page. */
.recording-list { display: grid; gap: 1.25rem; margin: 1.5rem 0 2rem; padding: 0; list-style: none; }
.recording-list > li {
  padding: 1.1rem 1.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
}

/* The browser's own player, given room. Its internals cannot be styled
 * consistently across engines, so the surround is styled and the control is
 * left alone rather than half-themed into something that looks broken in
 * Firefox. */
.recording-list audio {
  display: block;
  width: 100%;
  margin: 0.6rem 0 0.4rem;
}

/* What a recording said. Set as reading text rather than as a form's help
 * text: for a lot of these, this is the only version of the story anybody
 * will ever read. A rule down the left ties it to the player above it. */
.transcript { margin-top: 0.9rem; }
.transcript-label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--muted);
}
.transcript-text {
  margin: 0.5rem 0 0.9rem;
  padding-left: 0.9rem;
  border-left: 2px solid var(--violet);
  line-height: 1.65;
}
.transcript-text p + p { margin-top: 0.75rem; }
.transcript-actions { display: grid; gap: 0.25rem; }
.transcript-actions .editor { margin-top: 0.5rem; }

/* Who has been added so far, on the guided start. */
.added-people { margin: 0.4rem 0 0; padding-left: 1.2rem; }
.added-people li + li { margin-top: 0.2rem; }
.added-people span { color: var(--muted); }

/* "Does it come round again?" opens its follow-up questions in place when the
 * answer is yes. Pure CSS, so it works with no JavaScript; without `:has()`
 * the questions always show, which is what the page did before. */
.comes-round { display: grid; gap: 0; }
@supports selector(:has(*)) {
  .comes-round { display: none; }
  .recurrence:has(input[name='repeats'][value='yes']:checked) .comes-round {
    display: grid;
    padding-left: 1rem;
    border-left: 2px solid var(--violet);
  }
}

/* Add: three kinds of story, each a whole card to tap. */
.add-choices { list-style: none; padding: 0; }
.add-choices .card { display: block; height: 100%; color: inherit; text-decoration: none; }
.add-choices h2 { margin: 0 0 0.5rem; font-size: 1.2rem; }

/* Edit and delete under a story's title, where people look for them. */
.story-actions { display: flex; flex-wrap: wrap; gap: 0.5rem 1.5rem; margin: 0.25rem 0 1.25rem; font-size: 0.9rem; }
/* Quiet links, not buttons: they are the way to change a story, not the story. */
.story-actions a { color: var(--muted); text-decoration: underline; text-underline-offset: 0.2em; }
.story-actions a:hover { color: var(--text); }
.story-actions a.danger-link { color: var(--danger); }
.manage-links { font-size: 0.9rem; line-height: 1.8; }
.date-facts { margin: 1.5rem 0; }

/* The recorder: one large, thumb-sized button, and what it is doing in words. */
.voice-recorder { display: grid; gap: 0.75rem; margin: 0.5rem 0 1rem; }
.recorder-status { margin: 0; color: var(--muted); }
.record-button { display: inline-flex; align-items: center; gap: 0.6rem; justify-self: start; min-height: 3.25rem; padding-inline: 1.6rem; font-size: 1.05rem; }
.record-dot { width: 0.8rem; height: 0.8rem; border-radius: 50%; background: #ff4d6d; box-shadow: 0 0 0 3px rgba(255, 77, 109, 0.25); }
.record-button.recording .record-dot { animation: record-pulse 1.2s ease-in-out infinite; }
@keyframes record-pulse { 50% { opacity: 0.35; transform: scale(0.85); } }
.recorder-preview { width: 100%; }
/* While recording, Stop is the one control that matters: solid, red and
 * unmistakable in every state — including the pressed state a phone keeps
 * after the tap that started it, which left it washed out. */
.record-button.recording,
.record-button.recording:hover,
.record-button.recording:active,
.record-button.recording:focus-visible {
  /* Fixed, not var(--danger): that token is a light pink in the dark theme,
   * which fails contrast against white. This passes AA in both. */
  background: #c21d45;
  color: #fff;
  opacity: 1;
  border-color: transparent;
}
.record-button.recording .record-dot { background: #fff; box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.35); }

/* ─── Manage timeline ───────────────────────────────────────────────────────
 * One obvious control, top right of the timeline: a gear and words, because an
 * icon alone is a guessing game and words alone were missed in a sidebar. */
.timeline-head { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 1rem 2rem; }
.timeline-head h1 { margin-bottom: 0; }
.manage-menu { position: relative; margin-bottom: 0.4rem; }
.manage-menu > summary {
  display: inline-flex; align-items: center; gap: 0.5rem;
  padding: 0.6rem 1.1rem;
  border: 1px solid var(--border-bright); border-radius: var(--radius-pill);
  background: var(--surface); color: var(--text);
  font-weight: 550; cursor: pointer; list-style: none; white-space: nowrap;
}
.manage-menu > summary::-webkit-details-marker { display: none; }
.manage-menu > summary::marker { content: ''; }
.manage-menu > summary:hover { background: var(--surface-hover); }
.manage-menu > summary:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }
.manage-menu[open] .manage-icon { transform: rotate(45deg); }
.manage-icon { transition: transform 0.3s var(--spring); }
.manage-menu-items {
  position: absolute; right: 0; top: calc(100% + 0.4rem); z-index: 40;
  display: grid; min-width: 14rem; padding: 0.4rem;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--panel-solid); box-shadow: 0 18px 40px -18px rgba(6, 5, 15, 0.7);
}
.manage-menu-items a { padding: 0.6rem 0.8rem; border-radius: var(--radius-sm); color: var(--text); text-decoration: none; }
.manage-menu-items a:hover { background: var(--surface-hover); }
/* Set apart from the rest: the one item in the menu that cannot be undone. */
.manage-menu-items .manage-danger { margin-top: 0.3rem; border-top: 1px solid var(--border); border-radius: 0; color: var(--danger); font-weight: 550; }
@media (max-width: 40rem) {
  .manage-menu { width: 100%; }
  .manage-menu > summary { width: 100%; justify-content: center; }
  .manage-menu-items { left: 0; right: 0; min-width: 0; }
  .manage-menu-items a { padding: 0.85rem 0.9rem; font-size: 1rem; }
}


/* ─── The timeline's side panel ─────────────────────────────────────────────
 * Quiet section labels rather than shouting headings, one search field with
 * its icon inside it, a three-way "Show" switch, and decades as bars. */
.timeline-filters .facet h2 {
  margin: 0 0 0.6rem;
  font-size: 0.72rem;
  font-weight: 650;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
.timeline-filters .facet + .facet { padding-top: 1.25rem; border-top: 1px solid var(--border); }
.timeline-filters .facet ul { display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0; padding: 0; list-style: none; }
.timeline-filters .chip { display: inline-flex; align-items: center; gap: 0.4rem; }

.search-field { position: relative; }
.timeline-filters .search .search-field input { padding-left: 2.6rem; }
.search-icon { position: absolute; left: 0.85rem; top: 50%; transform: translateY(-50%); color: var(--muted); pointer-events: none; }

.clear-all { display: inline-block; margin: 0.9rem 0 0.2rem; font-size: 0.85rem; color: var(--violet); text-decoration: none; }
.clear-all:hover { text-decoration: underline; }

.segmented {
  display: grid; grid-template-columns: repeat(3, 1fr);
  padding: 0.25rem; gap: 0.2rem;
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  background: var(--surface);
}
.segment {
  padding: 0.45rem 0.4rem; border-radius: var(--radius-pill);
  text-align: center; font-size: 0.85rem; font-weight: 550;
  color: var(--muted); text-decoration: none;
  transition: background 0.2s var(--spring), color 0.2s var(--spring);
}
.segment:hover { color: var(--text); background: var(--surface-hover); }
.segment-on, .segment-on:hover { color: var(--on-accent); background: linear-gradient(135deg, var(--violet), var(--fuchsia)); }

/* Out-ranks the chip rows above: decades are a list of bars, not chips. */
.timeline-filters .facet ul.decade-list { display: grid; gap: 0.15rem; flex-wrap: nowrap; }
.timeline-filters .decade-list > li { display: block; }
.timeline-filters .decade {
  display: grid; grid-template-columns: 3.4rem 1fr auto; align-items: center; gap: 0.6rem;
  padding: 0.4rem 0.5rem; border-radius: var(--radius-sm);
  color: var(--text); text-decoration: none; font-size: 0.9rem;
}
.decade:hover { background: var(--surface-hover); }
.decade-on { background: var(--surface-hover); box-shadow: inset 0 0 0 1px var(--violet); }
.decade-bar { height: 0.45rem; border-radius: 999px; background: var(--border); overflow: hidden; }
.decade-bar span { display: block; height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--violet), var(--fuchsia)); }
.decade-bar .fill-1 { width: 10%; }
.decade-bar .fill-2 { width: 20%; }
.decade-bar .fill-3 { width: 30%; }
.decade-bar .fill-4 { width: 40%; }
.decade-bar .fill-5 { width: 50%; }
.decade-bar .fill-6 { width: 60%; }
.decade-bar .fill-7 { width: 70%; }
.decade-bar .fill-8 { width: 80%; }
.decade-bar .fill-9 { width: 90%; }
.decade-bar .fill-10 { width: 100%; }

.facet-more { margin-top: 0.5rem; }
.facet-more summary { cursor: pointer; font-size: 0.85rem; color: var(--muted); list-style: none; }
.facet-more summary::-webkit-details-marker { display: none; }
.facet-more summary:hover { color: var(--text); }
.facet-more ul { margin-top: 0.5rem !important; }

/* ─── Writing, with formatting ─────────────────────────────────────────────
 * The editor and its server-rendered document share typography. Toolbar
 * controls stay in normal flow so large text and narrow screens can wrap. */
.rich-field { min-width: 0; width: 100%; }
.rich-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  padding: 0.5rem;
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  background: var(--panel-solid);
}
.rich-toolbar-group { display: flex; flex-wrap: wrap; gap: 0.15rem; align-items: center; }
.rich-toolbar-group { border: 1px solid var(--border); border-radius: 0.65rem; padding: 0.15rem; background: var(--field); }
.rich-toolbar button, .rich-toolbar select {
  width: auto;
  min-width: 44px;
  min-height: 44px;
  margin: 0;
  padding: 0.5rem 0.65rem;
  border: 1px solid transparent;
  border-radius: 0.5rem;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 0.875rem;
  line-height: 1.3;
  cursor: pointer;
}
.rich-toolbar button { display: inline-flex; align-items: center; justify-content: center; gap: 0.45rem; }
.rich-toolbar svg { flex: 0 0 20px; }
.rich-toolbar select { border-color: var(--border); background: var(--field); max-width: 100%; }
.rich-toolbar .rich-emoji-trigger { border-color: var(--border); margin-inline-start: auto; }

.rich-toolbar button:hover, .rich-toolbar button[aria-pressed='true'] {
  background: var(--surface-hover);
  border-color: var(--violet);
}
.rich-toolbar button[aria-pressed='true'] { box-shadow: inset 0 -2px 0 var(--violet); }
.rich-toolbar button:disabled, .rich-toolbar button[aria-disabled='true'] { opacity: 0.55; cursor: not-allowed; }
.rich-toolbar button:focus-visible, .rich-toolbar select:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }
/* Tooltips share their trigger's hover area and stay readable at narrow widths. */
.rich-tool { position: relative; display: inline-flex; }
.rich-tool:has([role='tooltip']) { z-index: 30; }
.rich-tooltip {
  position: absolute; top: 100%; inset-inline-start: 50%; transform: translateX(-50%);
  width: max-content; max-width: min(14rem, calc(50vw - 1rem));
  padding: 0.55rem 0.7rem; border: 1px solid var(--border-bright);
  border-radius: 0.5rem; background: var(--panel-solid); color: var(--text);
  box-shadow: 0 4px 16px rgb(0 0 0 / 0.22); font-size: 0.8rem;
  font-weight: 500; line-height: 1.45; text-align: start; overflow-wrap: anywhere;
}
.rich-tooltip-start { inset-inline-start: 0; transform: none; }
.rich-tooltip-end { inset-inline-start: auto; inset-inline-end: 0; transform: none; }
.rich-editor {
  min-height: 12rem;
  max-width: 100%;
  padding: 1rem;
  border: 1px solid var(--border-bright);
  border-top: 0;
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  background: var(--field);
  color: var(--text);
  cursor: text;
}
.rich-editor:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }
.rich-editor[aria-disabled='true'] { cursor: wait; }
.rich-content, .rich-editor { line-height: 1.7; overflow-wrap: anywhere; white-space: pre-wrap; }
:is(.rich-content, .rich-editor) > * + * { margin-top: 0.8em; }
:is(.rich-content, .rich-editor) :is(h2, h3) { color: var(--text); line-height: 1.3; letter-spacing: -0.02em; }
:is(.rich-content, .rich-editor) h2 { font-size: 1.5em; }
:is(.rich-content, .rich-editor) h3 { font-size: 1.2em; }
:is(.rich-content, .rich-editor) :is(ul, ol) { padding-inline-start: 1.5rem; list-style-position: outside; }
:is(.rich-content, .rich-editor) ul { list-style-type: disc; }
:is(.rich-content, .rich-editor) ol { list-style-type: decimal; }
:is(.rich-content, .rich-editor) li + li { margin-top: 0.25em; }
:is(.rich-content, .rich-editor) blockquote { margin-inline: 0; padding: 0.5rem 1rem; border-inline-start: 3px solid var(--violet); background: var(--surface); }
:is(.rich-content, .rich-editor) a { color: var(--cyan); text-decoration: underline; text-underline-offset: 0.18em; }
:is(.rich-content, .rich-editor) code { padding: 0.1em 0.3em; border-radius: 0.3rem; background: var(--surface-hover); font-size: 0.9em; }
:is(.rich-content, .rich-editor) pre { max-width: 100%; padding: 0.85rem; border: 1px solid var(--border); border-radius: 0.5rem; background: var(--surface); white-space: pre-wrap; }
:is(.rich-content, .rich-editor) pre code { padding: 0; background: transparent; }
:is(.rich-content, .rich-editor) hr { margin-block: 1.5rem; border: 0; border-top: 1px solid var(--border-bright); }
.rich-count { margin-top: 0.5rem; color: var(--muted); font-size: 0.8rem; }
.rich-link-form { display: flex; flex-wrap: wrap; align-items: end; gap: 0.6rem; padding: 0.75rem; border-inline: 1px solid var(--border-bright); background: var(--panel-solid); }
.rich-link-form label { flex: 1 1 14rem; }
.rich-link-form input { min-width: 0; }
.rich-link-form button { min-height: 44px; }
.rich-fallback { width: 100%; }

/* Emoji stays in document flow: no clipped overlay, remote sprites or font dependency. */
.emoji-picker {
  padding: 1rem;
  border: 1px solid var(--border-bright);
  border-top: 0;
  background: var(--panel-solid);
  color: var(--text);
  min-width: 0;
}
.emoji-picker-header { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
.emoji-picker-header h3 { margin: 0; font-size: 1rem; }
.emoji-picker button { min-height: 44px; min-width: 44px; cursor: pointer; }
.emoji-picker-header button, .emoji-picker > button {
  padding: 0.5rem 0.75rem; background: var(--field); color: var(--text);
  border: 1px solid var(--border-bright); border-radius: 0.5rem; font: inherit;
}
.emoji-picker label { display: block; margin-block: 0.6rem 0.25rem; font-size: 0.85rem; }
.emoji-picker input, .emoji-picker select {
  display: block; width: 100%; min-width: 0; min-height: 44px; padding: 0.65rem;
  background: var(--field); color: var(--text); border: 1px solid var(--border-bright);
  border-radius: 0.5rem; font: inherit; font-size: 1rem;
}
.emoji-picker-filters { display: flex; flex-wrap: wrap; gap: 0.5rem 1rem; }
.emoji-picker-filters > * { flex: 1 1 11rem; min-width: 0; }
.emoji-picker-filters > :first-child { flex-basis: 100%; }
.rich-toolbar select, .emoji-picker select {
  appearance: none; -webkit-appearance: none; padding-inline-end: 2.25rem;
  background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: right 1.1rem top 55%, right 0.75rem top 55%;
  background-size: 0.35rem 0.35rem; background-repeat: no-repeat;
}
.emoji-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(44px, 1fr));
  gap: 0.35rem; max-height: 18rem; overflow-y: auto; overscroll-behavior: contain;
  padding: 0.3rem; margin-block: 0.5rem; scrollbar-gutter: stable;
}
.emoji-grid button {
  display: flex; align-items: center; justify-content: center; width: 100%; margin: 0;
  padding: 0.4rem; background: var(--field); border: 1px solid transparent;
  border-radius: 0.5rem; font-size: 1.5rem; line-height: 1.2; color: var(--text);
  font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif;
}
.emoji-grid button:hover { background: var(--surface-hover); border-color: var(--violet); }
.emoji-picker :is(button, input, select):focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }
.emoji-status, .emoji-picker .hint, .emoji-help { margin-block: 0.5rem; color: var(--muted); font-size: 0.8rem; }
@media (prefers-reduced-motion: no-preference) {
  .emoji-picker { animation: story-feedback 180ms var(--spring); }
}


/* ─── A little life, without a demand for attention ─────────────────────────
 * Finite entrances settle fully. No opacity animation on readable content,
 * no scroll listeners, and no animation applied to closed-details children.
 * Individual translate keeps entrance movement separate from hover transforms. */
.story-emoji { display: inline-block; font-size: 1em; line-height: 1.25; vertical-align: baseline; }
.capture-choice > .story-emoji { font-size: 1.65rem; }
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
  .capture-choice, .cards > .card, .timeline-spine > li, .upcoming-list > li {
    animation: story-settle 650ms var(--spring) both;
  }
  :is(.capture-choices, .cards, .timeline-spine, .upcoming-list) > :nth-child(2) { animation-delay: 70ms; }
  :is(.capture-choices, .cards, .timeline-spine, .upcoming-list) > :nth-child(3) { animation-delay: 140ms; }
  .timeline-spine > :nth-child(4) { animation-delay: 210ms; }
  .notice, .composer-error { animation: story-feedback 300ms var(--spring); }
  .story-emoji { transition: transform 240ms var(--spring); }
  :is(.capture-choice, .card):is(:hover, :focus-within) .story-emoji { transform: rotate(-7deg) translateY(-2px); }
  .timeline-entry:focus-within { border-color: var(--violet); }
  .rich-toolbar button { transition: background 160ms ease-out, border-color 160ms ease-out; }
  .button:active, .text-link:active, .capture-choice:active { transform: translateY(1px); }
  .nav-group > summary::after { transition: transform 180ms var(--spring); }
}
@keyframes story-settle { from { translate: 0 8px; } to { translate: 0 0; } }
@keyframes story-feedback { from { translate: -4px 0; } to { translate: 0 0; } }
@media (prefers-reduced-motion: reduce) {
  /* Explicitly finish rather than freeze an entrance at its initial keyframe. */
  .reveal, body::before, .capture-choice, .cards > .card, .timeline-spine > li,
  .upcoming-list > li, .notice, .composer-error { animation: none !important; translate: none; }
  .story-emoji, .nav-group > summary::after { transition: none !important; }
  .button:hover, .provider:hover, .capture-choice:active, .story-emoji { transform: none !important; }
}
.rich-editor:has(> p:only-child > br:only-child)::before {
  content: attr(data-placeholder);
  float: left;
  height: 0;
  color: var(--muted);
  pointer-events: none;
}
/* Links remain editable inline, with a target that also fits Linux font metrics. */
.rich-editor a { display: inline-block; min-height: 24px; max-width: 100%; }

/* Account administration uses operational metadata only; keep long identifiers
 * and confirmation labels readable without horizontal scrolling on phones. */
.admin-users-search { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: end; margin: 2rem 0 1rem; }
.admin-users-search .field { flex: 1 1 18rem; min-width: 0; }
.admin-user-list { list-style: none; display: grid; gap: 1.25rem; padding: 0; margin: 1.5rem 0; }
.admin-user-card { min-width: 0; padding: clamp(1rem, 3vw, 1.75rem); border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); overflow-wrap: anywhere; }
.admin-user-card h2 { margin: 0 0 0.75rem; font-size: clamp(1.1rem, 2vw, 1.35rem); }
.admin-user-badges { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 0.75rem; }
.admin-user-counts { display: grid; grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr)); gap: 1rem; margin: 1.25rem 0; }
.admin-user-counts dt { font-size: 0.8rem; color: var(--muted); }
.admin-user-counts dd { margin: 0.15rem 0 0; font-size: 1.3rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.admin-user-controls summary { cursor: pointer; min-height: 44px; padding: 0.6rem 0; }
.admin-user-controls summary:focus-visible { outline: 2px solid var(--cyan); outline-offset: 3px; }
.admin-user-actions { display: grid; gap: 1rem; margin-top: 0.75rem; }
.admin-user-action { display: grid; gap: 0.8rem; justify-items: start; border-top: 1px solid var(--border); padding-top: 1rem; }
.admin-user-action .field-check { min-height: 44px; align-items: center; }
.admin-user-action button { max-width: 100%; white-space: normal; }
.admin-filter { margin: 0.5rem 0 1rem; }
.admin-export { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center; margin: 0.5rem 0 1rem; }
.admin-export .hint { flex: 1 1 100%; margin: 0; }
.admin-filter .chip { min-height: 44px; display: inline-flex; align-items: center; }
.admin-account-title { overflow-wrap: anywhere; }
.admin-facts { display: grid; gap: 0.75rem; margin: 1rem 0; }
.admin-facts > div { display: grid; grid-template-columns: minmax(8rem, 12rem) 1fr; gap: 1rem; }
.admin-facts dt { color: var(--muted); font-size: 0.85rem; }
.admin-facts dd { margin: 0; overflow-wrap: anywhere; }
@media (max-width: 560px) { .admin-facts > div { grid-template-columns: 1fr; gap: 0.15rem; } }

/* Contact form honeypot: off-screen for people, still in the DOM for bots. */
.contact-trap { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }
