/* Mori — mori.anggler.com
 *
 * One stylesheet for the marketing page and the legal pages both. No build
 * step, no external request of any kind: the CSP is `default-src 'none'` with
 * `style-src 'self'` and `img-src 'self'`, so a CDN font or a remote image
 * would be blocked rather than slow. Everything here is served from this
 * origin.
 *
 * The palette is Midnight Biolume, hand-copied from `mori_colors.dart` — the
 * only file in the app permitted a hex literal. This is a static site outside
 * the Flutter build and cannot import it, so the two can drift. They change
 * about once a year; that is the accepted trade.
 *
 * The governing feeling, from the design system: **a quiet nighttime room, not
 * a productivity app.** Restraint is the aesthetic — the glow is precious
 * because it is rare. No drop shadows anywhere: depth comes from surface
 * lightness, a 1px low-contrast border, and glow. Shadows read as daylight UI
 * and break the atmosphere immediately.
 */

/* ── Fonts ────────────────────────────────────────────────────────────────
 * Subset to Latin + punctuation, ~52 KB for all three. Playfair is for
 * emotional moments only — the concept board uses serif for exactly four
 * things, and that ratio is the rule. Inter does everything functional.
 * `swap` so text is readable before the font lands. */

@font-face {
  font-family: 'Inter';
  src: url('/assets/Inter-400.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}

@font-face {
  font-family: 'Inter';
  src: url('/assets/Inter-600.woff2') format('woff2');
  font-weight: 600;
  font-display: swap;
}

@font-face {
  font-family: 'Playfair';
  src: url('/assets/PlayfairDisplay-500.woff2') format('woff2');
  font-weight: 500;
  font-display: swap;
}

/* ── Tokens ─────────────────────────────────────────────────────────────── */

:root {
  --bg: #090A0F;
  --surface: #141620;
  --surface-raised: #1B1E2A;
  --text: #F4EFE8;
  --muted: #9295A3;

  /* `primary` is the mascot; `accent` is anything the reader can press. That
   * separation is a hard rule in the app and it holds here — using primary for
   * a button is the bug that made every screen one shade of violet. */
  --primary: #8B5CF6;
  --accent: #A78BFA;
  --accent-surface: #3D2C6B;
  --accent-surface-deep: #201735;

  --blue: #6EA8FF;
  --lavender: #C7B8FF;
  --teal: #45DDD3;
  --rose: #F3A8CE;
  --warning: #E5B673;

  --border: rgb(244 239 232 / 0.08);
  --border-strong: rgb(199 184 255 / 0.22);
  --glass: rgb(20 22 32 / 0.6);

  --sans: 'Inter', ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI',
          Roboto, sans-serif;
  --serif: 'Playfair', Georgia, 'Times New Roman', serif;

  /* 4pt base scale, matching the app. */
  --xs: 4px;  --sm: 8px;  --md: 12px; --lg: 16px;
  --xl: 24px; --xxl: 32px; --xxxl: 48px;

  --gutter: clamp(20px, 5vw, 32px);
  --measure: 68rem;
}

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

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

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.65;
  overflow-x: hidden;
}

/* ── The room ─────────────────────────────────────────────────────────────
 * `mori_cosmos.dart`: "Midnight Biolume was never a dark theme; the concept
 * board is a *place* — nebula, star dust, light pooling on a wet floor.
 * Painting the canvas flat black turned that place into a widget on a page."
 * The same argument applies to a web page, so the site gets the same sky.
 *
 * Two fixed layers behind everything: nebula washes as gradients, and a
 * starfield baked into SVG by `tools/generate_site_stars.py`. Fixed rather
 * than scrolling, because the app installs its cosmos once above the router
 * and moving between screens does not restart it. */

body::before,
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}

/* Nebula. Violet from above, a cooler pool low and left, and a faint warm
 * horizon at the very bottom standing in for light on a wet floor. */
body::before {
  background:
    radial-gradient(90ch 60ch at 50% -10%, rgb(139 92 246 / 0.16), transparent 62%),
    radial-gradient(70ch 50ch at 12% 88%, rgb(110 168 255 / 0.08), transparent 60%),
    radial-gradient(120ch 26ch at 50% 104%, rgb(199 184 255 / 0.07), transparent 70%);
}

/* Star dust. `cover` scales the 1600x1000 artboard to any viewport, and the
 * two layers are offset so the twinkling one does not sit on top of the still
 * one. Only the small layer animates: pulsing the whole sky at once reads as a
 * flicker rather than a night. */
body::after {
  background-image:
    url('/assets/stars-twinkle.svg'),
    url('/assets/stars.svg');
  background-size: cover, cover;
  background-position: 30% 0, 0 0;
  opacity: 0.55;
  animation: twinkle 7s ease-in-out infinite alternate;
}

@keyframes twinkle {
  from { opacity: 0.42; }
  to   { opacity: 0.68; }
}

@media (prefers-reduced-motion: reduce) {
  body::after { animation: none; opacity: 0.55; }
}

img { max-width: 100%; display: block; }

a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: color 120ms ease-out;
}

a:hover { color: var(--lavender); }

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

.wrap {
  width: 100%;
  max-width: var(--measure);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.narrow { max-width: 46rem; }

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

h1, h2, h3 { margin: 0; font-weight: 500; }

h1 {
  font-family: var(--serif);
  font-size: clamp(2.4rem, 7vw, 4.1rem);
  line-height: 1.08;
  letter-spacing: -0.02em;
}

h2 {
  font-family: var(--serif);
  font-size: clamp(1.7rem, 4vw, 2.4rem);
  line-height: 1.2;
  letter-spacing: -0.01em;
}

h3 {
  font-family: var(--sans);
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: -0.005em;
}

p { margin: 0; }

.eyebrow {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}

.lede {
  font-size: clamp(1.05rem, 2.2vw, 1.25rem);
  color: var(--muted);
  line-height: 1.6;
}

.muted { color: var(--muted); }

.serif-line {
  font-family: var(--serif);
  font-size: clamp(1.3rem, 3vw, 1.75rem);
  line-height: 1.45;
  letter-spacing: 0.01em;
}

/* ── The mascot ───────────────────────────────────────────────────────────
 * Composited exactly as the app does it (Design System §4.1): the glow mask
 * is tinted with the state colour and blended over the render with
 * `mix-blend-mode: hue`, which takes hue and saturation from the tint and
 * **luminosity from the render**.
 *
 * That last part is the whole point. An *additive* glow makes every state
 * brighter than the last until the darkness that makes the artwork good is
 * washed out. A hue blend cannot brighten anything, so the stone stays as dark
 * and rich as it was rendered, at any colour. Changing a state here is one hex
 * value, exactly as in the app. */

.mori {
  position: relative;
  width: min(340px, 68vw);
  aspect-ratio: 1;
  margin-inline: auto;
  isolation: isolate;
}

/* The companion section's second instance. Same two layers, different tint. */
.mori.small { width: min(220px, 52vw); margin-block: var(--xxl) 0; }

/* The hero is `happy`: rose, arched eyes, a smile. A landing page's mascot
 * should be pleased to see you. */
.hero .mori { --mori-colour: var(--rose); }

/* ── Touching Mori ────────────────────────────────────────────────────────
 * The app answers a poke in the hand and on screen, so a page with Mori on it
 * that ignores being touched feels dead by comparison. All of this is CSS: the
 * CSP is `default-src 'none'`, so a script would be blocked, and `:active`
 * covers mouse, touch and a held finger alike.
 *
 * Three things happen, matching the app's own reaction:
 *   press   — a 2.5% squash, 120ms. The design system forbids bounce, elastic
 *             and overshoot, so anything larger starts to read as one of them.
 *   hold    — the light swells for as long as the finger stays down, over
 *             1.4s, then sits at the top. This is the part that makes holding
 *             feel different from tapping rather than just longer.
 *   release — everything eases back; the bloom's animation is dropped and the
 *             transition takes it home.
 */
.mori {
  cursor: pointer;
  transition: transform 240ms cubic-bezier(0.33, 0, 0.2, 1);
  -webkit-tap-highlight-color: transparent;
}

.mori .bloom,
.mori::before { transition: opacity 420ms ease-out; }

.mori:active {
  transform: scale(0.975);
  transition-duration: 120ms;
}

/* The swell. `forwards` holds the peak while the finger is still down. */
.mori:active .bloom { animation: hold-glow 1.4s ease-out forwards; }
.mori:active::before { opacity: 0.72; }

@keyframes hold-glow {
  from { opacity: 0.34; }
  to   { opacity: 0.9; }
}

/* Breathing pauses under a finger, the way holding something still does. */
.mori.breathing:active { animation-play-state: paused; }

@media (hover: hover) {
  .mori:hover .bloom { opacity: 0.5; }
  .mori:hover { transform: translateY(-2px); }
}

@media (prefers-reduced-motion: reduce) {
  .mori,
  .mori:hover { transform: none; }
  .mori:active { transform: scale(0.99); }
  .mori:active .bloom { animation: none; opacity: 0.7; }
}

/* **Every layer, not just the images.** This selector used to be `.mori img`,
 * which silently left `.tint` (a div) as a static, zero-height box that
 * painted nothing at all. The mascot rendered as the bare stone in every
 * state, and because that stone has its own violet cast it looked plausible
 * rather than broken, so it survived several rounds of review. A blend mode on
 * an unpositioned element is a no-op, not an error. */
.mori img,
.mori .tint,
.mori .bloom,
.mori .face {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.mori img { object-fit: contain; }

/* Floor pool — the state colour, blurred, beneath the stone. */
.mori::before {
  content: '';
  position: absolute;
  left: 50%;
  bottom: 4%;
  width: 62%;
  height: 13%;
  transform: translateX(-50%);
  background: radial-gradient(closest-side, var(--mori-colour, var(--primary)), transparent);
  opacity: 0.42;
  filter: blur(14px);
}

.mori .stone { z-index: 1; }

/* Tint: the state colour through the glow mask. `mask-image` restricts the
 * flat colour to exactly where the light lives. */
.mori .tint {
  z-index: 2;
  background: var(--mori-colour, var(--primary));
  /* `color`, not `hue`. The design system asks for hue **and saturation** from
   * the tint with luminosity from the render, which is exactly `color`; `hue`
   * alone leaves the tint barely visible on the swirl's near-white highlight,
   * because there is little saturation there to rotate. Luminosity still comes
   * from the artwork either way, so this cannot brighten or wash it out. */
  mix-blend-mode: color;
  -webkit-mask-image: url('/assets/mori_glow.webp');
  mask-image: url('/assets/mori_glow.webp');
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
}

/* Bloom — "the tint again, pre-blurred, additive at low opacity" (§4.2 layer
 * 5). **The tint again**, which is the part worth getting right: rendering the
 * bloom as the raw white-ish image adds uncoloured light on top of the tinted
 * stone and washes the state colour back out, which is why rose read as mauve.
 * Masked with the same asset so the additive pass carries the colour. It is
 * the one additive layer the spec allows, hence kept faint. */
.mori .bloom {
  z-index: 3;
  background: var(--mori-colour, var(--primary));
  mix-blend-mode: plus-lighter;
  opacity: 0.34;
  -webkit-mask-image: url('/assets/mori_bloom.webp');
  mask-image: url('/assets/mori_bloom.webp');
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
}

/* Layer 8, above the bloom. `preserveAspectRatio` letterboxes the 385x730
 * artboard inside the square container exactly as `object-fit: contain` does
 * for the images, so the face lands on the face at every size. */
.mori .face {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 4;
}

/* Breathing: 4s, easeInOutSine, reversing — the app's loop. Forbidden
 * everywhere in this design system: bounce, elastic, spin, overshoot. */
.mori.breathing { animation: breathe 4s ease-in-out infinite alternate; }

@keyframes breathe {
  from { transform: translateY(0) scale(1); }
  to   { transform: translateY(-6px) scale(1.012); }
}

/* Glow pulse, 2.6s, offset from the breath so they do not beat together. */
.mori.breathing .bloom {
  animation: pulse 2.6s ease-in-out infinite alternate;
}

@keyframes pulse {
  from { opacity: 0.24; }
  to   { opacity: 0.46; }
}

/* Reduced motion stops the loops at rest. Nothing becomes invisible or
 * unusable — decoration is removed, information is not. */
@media (prefers-reduced-motion: reduce) {
  .mori.breathing,
  .mori.breathing .bloom { animation: none; }
}

/* ── Hero ─────────────────────────────────────────────────────────────── */

.hero {
  padding-block: clamp(48px, 9vw, 96px) clamp(56px, 10vw, 112px);
  text-align: center;
}

.hero h1 { margin-block: var(--sm) var(--lg); }

/* The invitation, and the way out of it. Both are small: the hint is there
 * for people who would not otherwise think to touch a picture, and the mute
 * is there because a page that makes noise without an off switch is a page
 * people close. */
.mori-hint {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--md);
  margin-top: var(--xl);
  font-size: 0.85rem;
  color: var(--muted);
}

.mute {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 13px 5px 10px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--glass);
  color: var(--muted);
  font: inherit;
  font-size: 0.82rem;
  cursor: pointer;
  transition: color 120ms ease-out, border-color 120ms ease-out;
}

.mute:hover { color: var(--text); border-color: var(--border-strong); }

/* One icon at a time, chosen by the state the script writes back. */
.mute .off,
.mute[data-mute="off"] .on { display: none; }
.mute[data-mute="off"] .off { display: block; }
.mute[data-mute="off"] { color: var(--muted); opacity: 0.72; }

.hero .lede {
  max-width: 34rem;
  margin-inline: auto;
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--serif);
  font-size: 1.15rem;
  letter-spacing: 0.06em;
  color: var(--text);
  text-decoration: none;
}

.wordmark::before {
  content: '';
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--primary);
  box-shadow: 0 0 12px var(--primary);
}

/* ── Buttons ──────────────────────────────────────────────────────────── */

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--md);
  justify-content: center;
  margin-top: var(--xl);
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 52px;
  padding: 0 22px;
  border-radius: 999px;
  border: 1px solid transparent;
  font-family: var(--sans);
  font-size: 0.95rem;
  font-weight: 600;
  text-decoration: none;
  transition: border-color 120ms ease-out, background 120ms ease-out,
              color 120ms ease-out;
}

/* The primary button's slab, lit from above — `accentSurface` in the app.
 * Ivory on it measures 10.2:1. */
.btn-primary {
  background: linear-gradient(180deg, var(--accent-surface), var(--accent-surface-deep));
  border-color: var(--border-strong);
  color: var(--text);
}

.btn-primary:hover {
  color: var(--text);
  border-color: var(--lavender);
}

.btn-secondary {
  background: transparent;
  border-color: var(--border-strong);
  color: var(--lavender);
}

.btn-secondary:hover { border-color: var(--lavender); color: var(--text); }

/* Store buttons, not yet live. Deliberately not styled as if they work — a
 * button that looks pressable and is not is worse than an honest label. */
.store-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--md);
  justify-content: center;
  margin-top: var(--xl);
}

.store-btn {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 12px 22px;
  min-height: 56px;
  border-radius: 14px;
  border: 1px dashed var(--border-strong);
  background: rgb(20 22 32 / 0.5);
  color: var(--muted);
  cursor: default;
  text-align: start;
}

.store-btn svg { flex: none; opacity: 0.62; }

.store-btn .small {
  display: block;
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1.4;
}

.store-btn .big {
  display: block;
  font-size: 0.98rem;
  font-weight: 600;
  color: var(--text);
  line-height: 1.25;
}

.store-note {
  margin-top: var(--lg);
  font-size: 0.85rem;
  color: var(--muted);
}

/* ── Sections ─────────────────────────────────────────────────────────── */

/* Two adjacent sections stack their padding, so this figure lands twice
 * between them. 88 reads as deliberate space; 104 read as a gap. */
section { padding-block: clamp(48px, 7vw, 88px); }

.section-head { max-width: 40rem; margin-bottom: var(--xxl); }

.section-head .eyebrow { display: block; margin-bottom: var(--md); }

.section-head h2 + p { margin-top: var(--lg); }

.centered { margin-inline: auto; text-align: center; }

.rule { border: 0; border-top: 1px solid var(--border); margin: 0; }

/* ── Cards ────────────────────────────────────────────────────────────── */

.grid {
  display: grid;
  gap: var(--lg);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
}

.card {
  background: var(--glass);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: var(--xl);
  transition: border-color 200ms ease-out;
}

.card:hover { border-color: var(--border-strong); }

.card h3 { margin-bottom: var(--sm); }

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

.card .icon {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  margin-bottom: var(--lg);
  border-radius: 11px;
  background: rgb(139 92 246 / 0.12);
  border: 1px solid var(--border);
  color: var(--accent);
}

.card.feature .icon { color: var(--tint, var(--accent)); }

/* ── The loop (three steps) ───────────────────────────────────────────── */

.steps {
  display: grid;
  gap: var(--xl);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  counter-reset: step;
  list-style: none;
  margin: 0;
  padding: 0;
}

.steps li { counter-increment: step; }

.steps li::before {
  content: counter(step);
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  margin-bottom: var(--lg);
  border-radius: 50%;
  border: 1px solid var(--border-strong);
  color: var(--accent);
  font-size: 0.88rem;
  font-weight: 600;
}

.steps h3 { margin-bottom: var(--sm); }
.steps p { color: var(--muted); font-size: 0.96rem; }

/* ── Privacy panel ────────────────────────────────────────────────────── */

.panel {
  background: linear-gradient(180deg, rgb(27 30 42 / 0.7), rgb(20 22 32 / 0.5));
  border: 1px solid var(--border-strong);
  border-radius: 20px;
  padding: clamp(24px, 5vw, 48px);
}

.claims {
  display: grid;
  gap: var(--lg);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  margin: 0;
  padding: 0;
  list-style: none;
  margin-top: var(--xl);
}

.claims li {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  font-size: 0.96rem;
  color: var(--muted);
}

.claims strong { color: var(--text); font-weight: 600; }

.claims .tick {
  flex: none;
  margin-top: 3px;
  color: var(--teal);
}

/* ── Mood / state swatches ────────────────────────────────────────────── */

.states {
  display: flex;
  flex-wrap: wrap;
  gap: var(--md);
  margin-top: var(--xl);
  padding: 0;
  list-style: none;
}

.states li {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 8px 16px 8px 12px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--glass);
  font-size: 0.88rem;
  color: var(--muted);
}

.states .dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--c);
  box-shadow: 0 0 10px var(--c);
}

.states { justify-content: center; }

/* ── The interactive picker ───────────────────────────────────────────────
 * No JavaScript, and not as a compromise: the CSP is `default-src 'none'`, so
 * a script would be blocked outright. A radio per state with the chips as its
 * labels does the whole thing in CSS, and being a real radio group means
 * keyboard and screen-reader support come for free rather than being bolted
 * on afterwards. */

.mori-picker { position: relative; }

.state-radio {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

fieldset.states { border: 0; margin: 0; padding: 0; }

.states li { padding: 0; border: 0; background: none; }

.states label {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 8px 16px 8px 12px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--glass);
  font-size: 0.88rem;
  color: var(--muted);
  cursor: pointer;
  transition: border-color 160ms ease-out, color 160ms ease-out;
}

.states label:hover { color: var(--text); border-color: var(--border-strong); }

/* Every face is in the DOM; exactly one is shown. */
.mori .f { display: none; }

/* Driven by `data-face`, which the picker's radios and `mori.js` both set. */
.mori[data-face="idle"]      .f-idle,
.mori[data-face="listening"] .f-listening,
.mori[data-face="thinking"]  .f-thinking,
.mori[data-face="happy"]     .f-happy,
.mori[data-face="calm"]      .f-calm,
.mori[data-face="sad"]       .f-sad,
.mori[data-face="anxious"]   .f-anxious,
.mori[data-face="sleeping"]  .f-sleeping,
.mori[data-face="discreet"]  .f-discreet { display: block; }

/* ── Reactions ────────────────────────────────────────────────────────────
 * `mori_reaction.dart`'s table, transcribed. Each reaction borrows the
 * colour, the face and the body for its duration and then hands all three
 * back. Amplitudes are the app's own, and the motions squash and stretch
 * rather than uniformly scaling: "a single uniform scale can only make Mori
 * bigger or smaller, and volume is what sells weight. A body that widens as
 * it flattens has mass; one that shrinks evenly is a picture being resized." */

.mori[data-react="giggle"]   { --mori-colour: #F3A8CE; animation: r-bounce 2s cubic-bezier(0.33,0,0.2,1); }
.mori[data-react="startled"] { --mori-colour: #45DDD3; animation: r-jolt 1.7s cubic-bezier(0.2,0,0.1,1); }
.mori[data-react="annoyed"]  { --mori-colour: #E5B673; animation: r-shiver 1.7s ease-in-out; }
.mori[data-react="soothed"]  { --mori-colour: #C7B8FF; animation: r-lean 2.4s ease-in-out; }
.mori[data-react="curious"]  { --mori-colour: #6EA8FF; animation: r-tilt 2.1s ease-in-out; }

/* The light swells with the reaction and settles again, which is the part that
 * reads as Mori *answering* rather than merely changing colour. */
.mori[data-react] .bloom { animation: r-glow 1.2s ease-out; }

@keyframes r-glow {
  0%   { opacity: 0.34; }
  22%  { opacity: 0.92; }
  100% { opacity: 0.34; }
}

/* bounce — lands, compresses, draws out as it leaves the ground. amp 0.150 */
@keyframes r-bounce {
  0%   { transform: translateY(0) scale(1, 1); }
  14%  { transform: translateY(6%) scale(1.09, 0.91); }
  38%  { transform: translateY(-15%) scale(0.94, 1.07); }
  62%  { transform: translateY(3%) scale(1.05, 0.95); }
  82%  { transform: translateY(-4%) scale(0.98, 1.02); }
  100% { transform: translateY(0) scale(1, 1); }
}

/* jolt — a single sharp flinch upward. amp 0.165 */
@keyframes r-jolt {
  0%   { transform: translate(0, 0) scale(1, 1); }
  8%   { transform: translate(0, -16.5%) scale(0.92, 1.10); }
  26%  { transform: translate(0, 4%) scale(1.07, 0.93); }
  48%  { transform: translate(0, -3%) scale(0.98, 1.02); }
  100% { transform: translate(0, 0) scale(1, 1); }
}

/* shiver — small, fast, sideways. Unimpressed, not hurt. amp 0.075 */
@keyframes r-shiver {
  0%, 100% { transform: translateX(0) rotate(0); }
  12% { transform: translateX(-3.5%) rotate(-1.4deg); }
  26% { transform: translateX(3.5%) rotate(1.4deg); }
  40% { transform: translateX(-2.6%) rotate(-1deg); }
  54% { transform: translateX(2.2%) rotate(0.8deg); }
  70% { transform: translateX(-1.2%) rotate(-0.4deg); }
}

/* lean — slow settle into the touch, eyes shut. amp 0.055 */
@keyframes r-lean {
  0%   { transform: translateY(0) rotate(0) scale(1, 1); }
  35%  { transform: translateY(2.4%) rotate(-2.4deg) scale(1.03, 0.97); }
  70%  { transform: translateY(1.6%) rotate(-1.6deg) scale(1.02, 0.98); }
  100% { transform: translateY(0) rotate(0) scale(1, 1); }
}

/* tilt — leans in to look closer. amp 0.060 */
@keyframes r-tilt {
  0%   { transform: rotate(0) translateY(0); }
  30%  { transform: rotate(4.2deg) translateY(-3%); }
  62%  { transform: rotate(3deg) translateY(-2%); }
  100% { transform: rotate(0) translateY(0); }
}

/* A held finger keeps the light up until it lifts. */
.mori.is-held .bloom { animation: hold-glow 1.4s ease-out forwards; }

@media (prefers-reduced-motion: reduce) {
  .mori[data-react] { animation: none; }
  .mori[data-react] .bloom { animation: none; opacity: 0.7; }
}

/* One rule per state: tint the mascot, reveal its face, light its chip.
 * `~` reaches the mascot and the fieldset because both follow the inputs. */
#st-idle:checked      ~ .mori { --mori-colour: #8B5CF6; }
#st-listening:checked ~ .mori { --mori-colour: #45DDD3; }
#st-thinking:checked  ~ .mori { --mori-colour: #6EA8FF; }
#st-happy:checked     ~ .mori { --mori-colour: #F3A8CE; }
#st-calm:checked      ~ .mori { --mori-colour: #C7B8FF; }
#st-sad:checked       ~ .mori { --mori-colour: #6F8FB8; }
#st-anxious:checked   ~ .mori { --mori-colour: #C2A85A; }
#st-sleeping:checked  ~ .mori { --mori-colour: #5F4FB0; }

#st-idle:checked      ~ .mori .f-idle,
#st-listening:checked ~ .mori .f-listening,
#st-thinking:checked  ~ .mori .f-thinking,
#st-happy:checked     ~ .mori .f-happy,
#st-calm:checked      ~ .mori .f-calm,
#st-sad:checked       ~ .mori .f-sad,
#st-anxious:checked   ~ .mori .f-anxious,
#st-sleeping:checked  ~ .mori .f-sleeping { display: block; }

#st-idle:checked      ~ .states label[for="st-idle"],
#st-listening:checked ~ .states label[for="st-listening"],
#st-thinking:checked  ~ .states label[for="st-thinking"],
#st-happy:checked     ~ .states label[for="st-happy"],
#st-calm:checked      ~ .states label[for="st-calm"],
#st-sad:checked       ~ .states label[for="st-sad"],
#st-anxious:checked   ~ .states label[for="st-anxious"],
#st-sleeping:checked  ~ .states label[for="st-sleeping"] {
  color: var(--text);
  border-color: var(--border-strong);
  background: var(--surface-raised);
}

/* Keyboard focus has to land on the label, because the input it belongs to is
 * hidden. Without this the radio group is operable but invisible to anyone
 * tabbing through. */
.state-radio:focus-visible ~ .states label[for],
.states label:focus-visible {
  outline: 2px solid var(--lavender);
  outline-offset: 3px;
}

/* `sleeping` dims the light rather than only recolouring it, which is what
 * makes it read as switched off rather than merely blue. */
#st-sleeping:checked ~ .mori .bloom { opacity: 0.12; }
#st-sleeping:checked ~ .mori::before { opacity: 0.2; }

/* Mori's ten states, and the app's rule that no two share a colour holds
 * here too. These are the same hex values as `mori_colors.dart`. */
.dot-idle      { --c: #8B5CF6; }
.dot-listening { --c: #45DDD3; }
.dot-thinking  { --c: #6EA8FF; }
.dot-happy     { --c: #F3A8CE; }
.dot-calm      { --c: #C7B8FF; }
.dot-sad       { --c: #6F8FB8; }
.dot-anxious   { --c: #C2A85A; }
.dot-sleeping  { --c: #5F4FB0; }

/* Feature-card icon tints. Classes rather than inline `style`, because the
 * CSP is `style-src 'self'` — which governs style *attributes* too, so an
 * inline custom property is dropped on the live site and every icon silently
 * falls back to violet. Worth keeping the CSP strict for eight colours. */
.t-teal     { --tint: var(--teal); }
.t-blue     { --tint: var(--blue); }
.t-rose     { --tint: var(--rose); }
.t-lavender { --tint: var(--lavender); }
.t-accent   { --tint: var(--accent); }
.t-warning  { --tint: var(--warning); }
.t-primary  { --tint: var(--primary); }

.flush { margin-bottom: 0; }

.pull-quote { max-width: 32rem; margin-top: var(--xxxl); }

.foot-blurb {
  margin-top: var(--md);
  max-width: 22rem;
  font-size: 0.9rem;
}

/* ── Honest section ───────────────────────────────────────────────────── */

.plain {
  display: grid;
  gap: var(--lg);
  margin: 0;
  padding: 0;
}

.plain > div {
  padding-inline-start: var(--lg);
  border-inline-start: 2px solid var(--border-strong);
}

.plain dt { font-weight: 600; margin-bottom: var(--xs); }
.plain dd { margin: 0; color: var(--muted); font-size: 0.96rem; }

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

footer {
  border-top: 1px solid var(--border);
  padding-block: var(--xxl) var(--xxxl);
  font-size: 0.9rem;
}

.foot-grid {
  display: flex;
  flex-wrap: wrap;
  gap: var(--xl);
  justify-content: space-between;
  align-items: flex-start;
}

.foot-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--lg) var(--xl);
  list-style: none;
  margin: 0;
  padding: 0;
}

.foot-links a { color: var(--muted); text-decoration: none; }
.foot-links a:hover { color: var(--text); text-decoration: underline; }

.colophon { color: var(--muted); margin-top: var(--xl); font-size: 0.85rem; }

/* ── Legal pages ──────────────────────────────────────────────────────── */

.doc { max-width: 46rem; margin-inline: auto; }

.doc h2 {
  font-family: var(--sans);
  font-size: 1.3rem;
  font-weight: 600;
  margin-top: var(--xxl);
  margin-bottom: var(--md);
}

.doc h3 {
  margin-top: var(--xl);
  margin-bottom: var(--sm);
  color: var(--accent);
}

.doc p, .doc li { margin-block: 0.7em; }

.doc ul, .doc ol { padding-inline-start: 1.4rem; }

.doc header { padding-block: clamp(40px, 8vw, 72px) var(--xl); }

.doc header h1 {
  font-size: clamp(2rem, 5.5vw, 2.9rem);
  margin-bottom: var(--md);
}

.updated { color: var(--muted); font-size: 0.88rem; }

code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.88em;
  background: rgb(244 239 232 / 0.07);
  padding: 0.12em 0.4em;
  border-radius: 4px;
}

.callout {
  background: var(--glass);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: var(--xl);
  margin-block: var(--xl);
}

.callout.strong { border-color: var(--border-strong); }

.callout .contact-head {
  margin-top: 0;
  margin-bottom: var(--md);
  color: var(--accent);
}

/* ── FAQ ──────────────────────────────────────────────────────────────────
 * A flat run of question/answer pairs is hard to scan once there are more
 * than about six. Grouping them under topic headings, giving each pair its
 * own card, and putting a jump list at the top turns a wall into something
 * you can find your own question in. */

.jump {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sm) var(--md);
  margin-block: var(--xl);
  padding-block: var(--lg);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}

.jump a {
  padding: 6px 14px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--glass);
  color: var(--muted);
  font-size: 0.86rem;
  text-decoration: none;
}

.jump a:hover { color: var(--text); border-color: var(--border-strong); }

.faq-group { padding-block: 0; margin-top: var(--xxl); }

.faq-group > h2 {
  font-family: var(--serif);
  font-size: 1.45rem;
  font-weight: 500;
  margin-bottom: var(--lg);
  padding-bottom: var(--md);
  border-bottom: 1px solid var(--border);
}

.faq {
  padding: var(--lg) 0 var(--lg) var(--lg);
  border-inline-start: 2px solid var(--border);
}

.faq + .faq { margin-top: var(--sm); }

.faq > h3 {
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 600;
  color: var(--text);
  margin: 0 0 var(--sm);
}

.faq p { color: var(--muted); }
.faq p + p { margin-top: var(--md); }

/* The two answers people arrive at this page frightened by: a forgotten PIN
 * and a forgotten passphrase. They get the warning colour because the honest
 * answer to both is partly "no", and burying that would be worse. */
.faq.important { border-inline-start-color: var(--warning); }
.faq.important > h3 { color: var(--warning); }
.callout > :first-child { margin-top: 0; }
.callout > :last-child { margin-bottom: 0; }

/* Wide content scrolls inside itself — the page body never scrolls sideways
 * on a phone, which is where most of these pages get read. */
.scroll { overflow-x: auto; margin-block: var(--xl); }

table {
  border-collapse: collapse;
  width: 100%;
  min-width: 30rem;
  font-size: 0.94rem;
}

th, td {
  text-align: start;
  padding: 0.65rem 1rem 0.65rem 0;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}

th {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.steps-numbered { counter-reset: step; list-style: none; padding: 0; }

.steps-numbered li {
  counter-increment: step;
  position: relative;
  padding-inline-start: 2.7rem;
  margin-block: var(--lg);
}

.steps-numbered li::before {
  content: counter(step);
  position: absolute;
  inset-inline-start: 0;
  top: -1px;
  display: grid;
  place-items: center;
  width: 1.9rem;
  height: 1.9rem;
  border-radius: 50%;
  background: var(--accent-surface);
  border: 1px solid var(--border-strong);
  color: var(--text);
  font-size: 0.85rem;
  font-weight: 600;
}

/* ── Top bar ──────────────────────────────────────────────────────────── */

.topbar {
  position: sticky;
  top: 0;
  z-index: 10;
  backdrop-filter: blur(14px);
  background: rgb(9 10 15 / 0.72);
  border-bottom: 1px solid var(--border);
}

.topbar .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--lg);
  padding-block: var(--md);
}

.topbar nav {
  display: flex;
  gap: var(--xl);
  font-size: 0.92rem;
}

.topbar nav a { color: var(--muted); text-decoration: none; }
.topbar nav a:hover { color: var(--text); }

@media (max-width: 34rem) {
  .topbar nav { display: none; }
}
