/* ── Brand font ────────────────────────────────────────────────────────────
   Only Cal Sans is a webfont — it carries the brand on titles. Body/UI text uses
   the SYSTEM font stack (client decision 2026-07-26): one fewer font file to
   download on a page opa/oma often open on mobile data, and it matches the
   native app, which moved body copy to SF in the same change. */
@font-face {
  font-family: "Cal Sans";
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  src:
    local("CalSans-Regular"),
    url("fonts/CalSans-Regular.ttf") format("truetype");
}

/* ── Canonical Heare design tokens (match Heare/Theme.swift) ───────────────── */
:root {
  --on-accent: #ffffff;     /* ink on accent/dark fills — never inverts */
  --font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-display: "Cal Sans", var(--font-body);

  --room: #ffffff;          /* page (redesign v2: white, was cream) */
  --white: #ffffff;         /* tap-cards */
  --flamingo: #6c5cff;      /* primary action (redesign v2: purple, was orange) */
  --ebony: #1c1840;         /* title ink (redesign v2: #1C1840) */
  --story-blue: #88c5fe;
  --text-muted: #978e8e;
  --text-medium: #5b616d;
  --text-low: #c3c6cc;
  --tile-peach: #fff3e7;    /* beige card / secondary surface (redesign v2) */
  --tile-taupe: #c9bba9;    /* dashed photo tile stroke + icon */

  /* frame-24 chip + badge tints */
  --peach-badge: #fce3d9;
  --peach-chip-text: #d5481f;
  --mint-chip: #eaf6ee;
  --mint-chip-text: #2c7a4b;
  --ice-badge: #e2f0fa;
  --ice-chip-text: #2e93c9;
  --card-subtitle: #8e8a9c;

  /* Meelezen (read-along): "not yet read" stays legible, "already read" is the
     strongest ink. Same intent as the dark tokens below, just the other way up
     (dark starts from white-on-black; light starts from ink-on-white). */
  --reading-ink: #1c1840;
  --reading-ink-upcoming: rgba(28, 24, 64, 0.35);
  --ra-word-read: #1c1840;
  --ra-word-upcoming: rgba(28, 24, 64, 0.55);

  /* Golfvorm/tekst-toggle tijdens opnemen: ink-based in light, white-based in
     dark (theme-dark override below) — was hardcoded white-on-white, onzichtbaar
     op de witte .recording-panel achtergrond in light mode (client 2026-08-17). */
  --toggle-bg: rgba(28, 24, 64, 0.06);
  --toggle-ink: rgba(28, 24, 64, 0.55);

  /* Tekstpaneel en golfvorm zijn ÉÉN weergave die in dezelfde cel wisselt, dus
     ze delen één oppervlak — twee net-iets-andere vlakken lazen als twee
     schermen (client report, eerder al voor dark opgelost). */
  --reading-surface: #eaf3fe;

  --radius-pill: 1000px;
  --radius-card: 20px;
  --radius-button: 10px;
  --radius-input: 16px;
  --screen-margin: 25px;
  --check-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
}

* {
  box-sizing: border-box;
}

html {
  /* Rubber-band overscroll reveals whatever is behind body — without this,
     the bounce past the top/bottom of the page shows raw white even in
     dark mode, since only body/.flow-shell carry a background. */
  background: var(--room);
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--room);
  color: var(--ebony);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
}

button,
input,
a {
  font: inherit;
}

.flow-shell {
  min-height: 100vh;
  background: var(--room);
  /* The step-push/step-pop animations slide a full screen-width in from the
     edge; without this the incoming step briefly widens the page and shows a
     horizontal scrollbar/bounce mid-animation. */
  overflow-x: hidden;
}

.nav {
  height: 80px;
  display: grid;
  grid-template-columns: 96px 1fr 96px;
  align-items: center;
  padding: env(safe-area-inset-top) 20px 0;
}

.nav h1 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 30px;
  font-weight: 400;
  line-height: 1.2;
  text-align: center;
}
/* Standalone/browser recorder shows the real Heare wordmark logo, not text. */
.nav-wordmark img { display: block; height: 26px; margin: 0 auto; }

/* Ingebed in de app (iOS of Android) is de schil zelf al Heare, so the recorder
   drops its redundant "Heare" wordmark (the × close stays). */
html.embedded-app .nav h1 {
  display: none;
}

/* …but keep the "Heare" wordmark on the recording screen even in-app (client) —
   it's the one full-screen recorder step where the brand should still show. */
html.embedded-app:has(.step.recording-step.active) .nav h1 {
  display: block;
}

.icon-button {
  width: 44px;
  height: 44px;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--ebony);
  font-size: 34px;
  line-height: 1;
}

.step {
  display: none;
  min-height: calc(100vh - 80px);
  padding: 16px var(--screen-margin) calc(25px + env(safe-area-inset-bottom));
}

.step.active {
  display: grid;
  align-items: start;
}

.content-card {
  width: min(100%, 340px);
  margin: 0 auto;
  padding-top: 0;
}

.eyebrow {
  margin: 0 0 8px;
  color: var(--text-muted);
  font-size: 15px;
  font-weight: 700;
}

h2 {
  margin: 0 0 30px;
  font-family: var(--font-display);
  font-size: 32px;
  font-weight: 400;
  line-height: 1.2;
  color: var(--ebony);
}

/* Draft-recovery title is constrained so "klaar" wraps to line 2 (frame 10). */
.recovery-title {
  max-width: 300px;
}

label,
.field-label {
  display: grid;
  gap: 9px;
  margin-top: 16px;
  color: var(--text-muted);
  font-size: 15px;
  font-weight: 700;
}

.field-label {
  font-family: var(--font-body);
  font-size: 18px;
  font-weight: 400;
}

.field-label.centered {
  justify-items: center;
  text-align: center;
}

.req {
  color: var(--flamingo);
}

input {
  width: 100%;
  min-height: 52px;
  border: 1px solid rgba(0, 0, 0, 0.03);
  border-radius: 12px;
  background: var(--white);
  color: var(--text-medium);
  font-family: var(--font-body);
  font-size: 17px;
  font-weight: 500;
  outline: 0;
  padding: 0 16px;
  box-shadow: none;
}

input::placeholder {
  color: var(--text-low);
}

.primary-button,
.primary-link-button,
.secondary-button,
.dark-button,
.text-button,
.text-link-button,
.control-circle,
.photo-tile,
.choice-button,
.choice-card {
  cursor: pointer;
}

.primary-button,
.primary-link-button {
  width: 100%;
  min-height: 62px;
  margin-top: 30px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--radius-button);
  background: var(--flamingo);
  color: var(--on-accent);
  font-family: var(--font-body);
  font-size: 18px;
  font-weight: 600;
  text-align: center;
  text-decoration: none;
}

.primary-link-button[hidden],
.primary-button[hidden],
.dark-button[hidden],
.text-link-button[hidden] {
  display: none;
}

.primary-button:disabled {
  opacity: 0.72;
}

.choice-stack {
  display: grid;
  gap: 30px;
  margin-top: 30px;
}

.choice-button {
  width: 100%;
  min-height: 128px;
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 20px 60px 20px 20px;
  border: 0;
  border-radius: var(--radius-button);
  color: var(--ebony);
  text-align: left;
}

.choice-button-parent { background: var(--tile-peach); }
.choice-button-gift { background: rgba(114, 200, 254, 0.15); }

.choice-icon {
  width: 77px;
  height: 77px;
  flex-shrink: 0;
  border-radius: var(--radius-button);
  background: var(--white);
  object-fit: cover;
}

.choice-copy {
  display: grid;
  gap: 5px;
  min-width: 0;
}

.choice-button strong {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 400;
  line-height: 1.1;
}

.choice-copy span {
  color: rgba(28, 24, 64, 0.5);
  font-size: 13px;
  line-height: 1.35;
}

.choice-chevron {
  position: absolute;
  right: 22px;
  top: 50%;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  color: rgba(28, 24, 64, 0.4);
}

.secondary-button {
  width: 100%;
  min-height: 62px;
  margin-top: 12px;
  border: 0;
  border-radius: var(--radius-button);
  background: var(--tile-peach);
  color: var(--ebony);
  font-size: 18px;
  font-weight: 600;
}

.dark-button {
  width: 100%;
  min-height: 62px;
  margin-top: 17px;
  border: 0;
  border-radius: var(--radius-button);
  background: var(--ebony);
  color: var(--on-accent);
  font-family: var(--font-body);
  font-size: 18px;
  font-weight: 600;
}

.text-button,
.text-link-button {
  width: 100%;
  min-height: 52px;
  margin-top: 10px;
  display: grid;
  place-items: center;
  border: 0;
  background: transparent;
  color: var(--ebony);
  font-weight: 700;
  text-align: center;
  text-decoration: none;
}

.body-copy,
.muted {
  color: var(--text-muted);
  font-size: 17px;
  line-height: 1.45;
}

/* ── 09 WebView opnemen — active recording ───────────────────────────────── */
.recording-step {
  padding: 0;
}

.recording-step.active {
  display: block;
}

.recording-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  /* lock to the viewport so only the story panel scrolls — never the page */
  height: calc(100dvh - 80px);
  overflow: hidden;
  padding: 0 var(--screen-margin) calc(14px + env(safe-area-inset-bottom));
  background: var(--room);
  text-align: center;
}

.recording-header {
  margin: 4px auto 0;
  max-width: 332px;
}

.recording-header h2 {
  margin: 0 0 10px;
  text-align: center;
  font-size: 32px;
}

.recording-subtitle {
  width: 100%;
  max-width: 300px;
  margin: 0 auto;
  color: var(--text-muted);
  font-family: var(--font-body);
  font-size: 18px;
  line-height: 1.2;
}

/* 7942-897 has no title/child context line. */
.context { display: none; }

/* Editor body: a flex column that fills the panel; the story panel flexes to
   absorb the slack and scrolls internally (so the page itself never scrolls). */
.record-controls {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  flex: 1;
  min-height: 0;
  gap: 0;
  margin-top: 14px;
}
/* Keep every editor control rigid; ONLY the story panel flexes/scrolls — otherwise
   flexbox shrinks the fixed items (the waveform band collapsed to a few px). */
.record-controls > *:not(.story-text-panel) { flex: 0 0 auto; }

.timer-block {
  display: grid;
  justify-items: center;
  gap: 5px;
  width: 100%;
}

.timer-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
}

.timer-dot {
  width: 10.4px;
  height: 10.4px;
  border-radius: 50%;
  background: var(--flamingo);
}

.timer {
  font-family: var(--font-display);
  font-size: 32px;
  line-height: 1.2;
  color: var(--ebony);
}

.timer-caption {
  margin: 0;
  color: rgba(28, 24, 64, 0.5);
  font-family: var(--font-body);
  font-size: 13px;
}

/* Recording indicator: purple mic orb with a soft 10px ring (7942-908). */
.record-orb {
  display: grid;
  place-items: center;
  box-sizing: border-box;
  width: 106px;
  height: 106px;
  border-radius: 50%;
  background: var(--flamingo);
  border: 10px solid rgba(108, 92, 255, 0.2);
  animation: orb-pulse 2.4s ease-in-out infinite;
}
.record-orb.is-paused { animation: none; opacity: 0.8; }
/* Before the mic is live: no pulsing halo, and the orb invites a tap. The halo
   (orb-pulse) only appears once recording actually starts. */
.record-orb.is-idle { animation: none; cursor: pointer; }
.record-orb.is-idle:active { transform: scale(0.96); }
@keyframes orb-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(108, 92, 255, 0); }
  50%      { box-shadow: 0 0 0 12px rgba(108, 92, 255, 0.12); }
}

/* 7942-897 has no waveform; the canvas stays (hidden) for the JS analyser. */
.waveform { display: none; }

/* Pauze / Opnieuw / Markeer — beige circle controls. */
.transport-row {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: 32px;
}

.transport-control {
  display: grid;
  justify-items: center;
  gap: 2px;
  width: 55px;
}

.control-circle {
  display: grid;
  place-items: center;
  width: 55px;
  height: 55px;
  border: 0;
  border-radius: 50%;
  background: var(--tile-peach);
  color: var(--ebony);
  padding: 0;
}

.control-circle:disabled { opacity: 0.45; }

/* Default: pause glyph visible, resume (play triangle) hidden. */
.control-circle .icon-pause { fill: currentColor; display: block; }
.control-circle .icon-resume { fill: currentColor; display: none; }

/* Paused: the pause control becomes a resume (play) affordance. */
.control-circle.is-resume .icon-pause { display: none; }
.control-circle.is-resume .icon-resume { display: block; }

.control-label {
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 400;
  color: var(--ebony);
  line-height: 1.1;
}

/* JS toggles these classes; keep them inert on the flat controls. */
.control-label.highlight-stop,
.control-label.highlight-resume {
  font-weight: 400;
  color: var(--ebony);
}

/* Blue marker chip with the count (7944-932). */
.marker-chip {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin: 16px auto 0;
  padding: 6px 12px;
  border-radius: var(--radius-button);
  background: rgba(114, 200, 254, 0.5);
  color: #1b6796;
  /* Always reserve the chip's space; toggling visibility (not display) means a
     newly-added marker never shoves the text + Stoppen button below it down. */
  visibility: hidden;
}
.marker-chip.is-visible { visibility: visible; }
/* Same gotcha: `label { display: grid }` was beating [hidden], so the iOS/invite
   "Start opname" gate still showed the already-known name+title fields (a
   duplicate of the native form). Make hiding those labels reliable. */
label[hidden] { display: none; }

.marker-chip svg { display: block; flex: none; }

.marker-chip span {
  font-family: var(--font-body);
  font-size: 13px;
  color: #1b6796;
}

.cap-notice {
  margin: 16px auto 0;
  max-width: 300px;
  color: var(--flamingo);
  font-size: 14px;
  font-weight: 600;
  line-height: 1.35;
}

/* Tip! speech-bubble card with a downward tail. */
.tip-card {
  position: relative;
  width: min(332px, calc(100vw - 48px));
  margin: 40px auto 0;
  padding: 16px 24px 18px 17px;
  border-radius: var(--radius-card);
  background: var(--white);
  text-align: left;
  box-shadow: 0 14px 30px -18px rgba(43, 39, 66, 0.35);
}

.tip-card::after {
  content: "";
  position: absolute;
  right: 34px;
  bottom: -10px;
  width: 22px;
  height: 16px;
  background: var(--white);
  clip-path: polygon(0 0, 100% 0, 60% 100%);
}

.tip-heading {
  display: block;
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 400;
  line-height: 1.2;
  color: var(--ebony);
}

.tip-body {
  margin: 8px 0 0;
  color: var(--text-muted);
  font-size: 13px;
  line-height: 1.4;
}

.autosave {
  margin: auto 0 0;   /* push the reassurance + Klaar bar to the bottom */
  padding-top: 24px;
  color: var(--text-muted);
  font-size: 13px;
}

/* Bottom purple finish bar (7942-949). */
.klaar-bar {
  width: 100%;
  align-self: center;
  margin: 14px 0 0;
  height: 62px;
  border: 0;
  border-radius: var(--radius-button);
  background: var(--flamingo);
  color: #fff;
  font-family: var(--font-body);
  font-size: 18px;
  font-weight: 600;
  cursor: pointer;
}

/* ── Preview player (brand play/pause; the <audio> is engine-only) ───────── */
.preview-player {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 10px 0 4px;
}

.preview-player audio {
  display: none;
}

.preview-play {
  flex: none;
  width: 56px;
  height: 56px;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--flamingo);
  display: grid;
  place-items: center;
  cursor: pointer;
  box-shadow: 0 10px 20px -10px rgba(108, 92, 255, 0.55);
}

/* White Lucide play/pause on the purple preview circle (claim pill keeps purple). */
.preview-play .play-glyph { color: var(--on-accent); }

.preview-time {
  color: var(--text-muted);
  font-size: 15px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ── Preview scrubber + marker pins ──────────────────────────────────────── */

.scrubber {
  position: relative;
  width: 100%;
  height: 28px;
  margin: 4px 0 6px;
  cursor: pointer;
  touch-action: none;
}

.scrubber-track {
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  height: 6px;
  transform: translateY(-50%);
  border-radius: var(--radius-pill);
  background: rgba(43, 39, 66, 0.12);
  overflow: hidden;
}
/* A dark tint stays visible on the light page but nearly vanishes against the dark
   one — the seek track becomes hard to find, not unreadable text but still a real
   usability miss on the same screen as the other post-Klaar bugs. */
html.theme-dark .scrubber-track { background: rgba(255, 255, 255, 0.14); }

.scrubber-progress {
  height: 100%;
  width: 0;
  border-radius: var(--radius-pill);
  background: var(--flamingo);
}

.scrubber-markers {
  position: absolute;
  inset: 0;
}

.marker-pin {
  position: absolute;
  top: 50%;
  width: 12px;
  height: 12px;
  margin-left: -6px;
  transform: translateY(-50%);
  padding: 0;
  border: 2px solid var(--white);
  border-radius: var(--radius-pill);
  background: var(--ebony);
  box-shadow: 0 2px 6px -2px rgba(43, 39, 66, 0.5);
}

/* ── 11 WebView afronden — photo + nickname ──────────────────────────────── */
/* Finish step (frame 11): keep the whole column centered. */
[data-step="reader"] h2 {
  text-align: left;
}

.photo-section {
  display: grid;
  justify-items: stretch;
  gap: 10px;
  margin-top: 30px;
}
/* `display: grid` would otherwise beat the [hidden] attribute (same gotcha as the
   marker chip) — picking a known narrator hides the whole photo step. */
.photo-section[hidden] { display: none; }

/* De uitleg hoort tegen zijn label aan te staan, niet op de rasterafstand van
   10px die voor label→tegel bedoeld is. Vandaar de negatieve marge: het label
   is zelf een grid-item en kan de hint niet omvatten zonder extra wikkel. */
.photo-hint {
  margin-top: -6px;
  color: var(--text-muted);
  font-size: 13px;
  font-weight: 400;
  line-height: 1.35;
}

.photo-tile {
  position: relative;
  width: 100%;
  height: 145px;
  display: grid;
  place-items: center;
  padding: 16px 16px 12px;
  border: 3px dashed var(--tile-taupe);
  border-radius: 12px;
  background: var(--tile-peach);
  color: var(--tile-taupe);
  overflow: hidden;
}

.photo-icon {
  display: block;
  color: var(--text-muted);
}

/* The chosen photo shows as a CIRCLE inside the dashed frame (client request);
   the cropped source is already square, so border-radius 50% gives a clean circle. */
.photo-preview {
  width: 110px;
  height: 110px;
  border-radius: 50%;
  object-fit: cover;
  display: none;
}

.photo-tile.has-photo {
  background: var(--white);
}

.photo-tile.has-photo .photo-icon {
  display: none;
}

.photo-tile.has-photo .photo-preview {
  display: block;
}

/* Pencil affordance to re-pick / re-crop; tapping anywhere on the tile re-opens
   the picker, the pencil just signals it's editable. */
.photo-edit {
  position: absolute;
  right: 12px;
  bottom: 12px;
  display: none;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--flamingo);
  color: var(--on-accent);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
}

.photo-tile.has-photo .photo-edit {
  display: grid;
}

.nickname-section {
  display: grid;
  gap: 6px;
  margin-top: 30px;
}
.nickname-section[hidden] { display: none; }

/* Keep the question + required asterisk on one line. */
.nickname-section .field-label {
  display: block;
  margin-top: 0;
}

.nickname-section input {
  margin-top: 0;
}

/* Custom reader-name autocomplete (iOS WKWebView has no <datalist> dropdown). */
.reader-input-wrap { position: relative; }
.reader-suggestions {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(100% + 4px);
  z-index: 20;
  margin: 0;
  padding: 4px;
  list-style: none;
  background: var(--white);
  border: 1px solid rgba(28, 24, 64, 0.12);
  border-radius: 14px;
  box-shadow: 0 10px 28px rgba(28, 24, 64, 0.14);
  max-height: 220px;
  overflow-y: auto;
}
.reader-suggestions[hidden] { display: none; }
.reader-suggestions li {
  padding: 12px;
  border-radius: 9px;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: 15px;
  color: var(--ebony, #2b2742);
}
.reader-suggestions li:active,
.reader-suggestions li:hover { background: rgba(114, 200, 254, 0.18); }

/* ── 24 Web keuze na opname — choice cards ───────────────────────────────── */
.choice-stack-cards {
  gap: 30px;
  margin-top: 30px;
}

.choice-card {
  width: 100%;
  display: grid;
  justify-items: center;
  gap: 5px;
  padding: 22px 18px 18px;
  border: 0;
  border-radius: 22px;
  background: var(--white);
  color: var(--ebony);
  text-align: center;
  box-shadow: 0 14px 30px -20px rgba(43, 39, 66, 0.45);
}

.choice-card .badge {
  width: 58px;
  height: 58px;
  display: grid;
  place-items: center;
  border-radius: var(--radius-pill);
}

.badge-peach { background: var(--peach-badge); color: var(--flamingo); }
.badge-blue { background: var(--ice-badge); color: #2e93c9; }

.choice-card strong {
  margin-top: 7px;
  font-family: var(--font-body);
  font-size: 19px;
  font-weight: 600;
  color: var(--ebony);
  line-height: 1.2;
}

.choice-card .card-subtitle {
  color: var(--card-subtitle);
  font-size: 13px;
  font-weight: 400;
  line-height: 1.4;
}

/* ── 23 Web afronden — finish mascot (7942:1920) ─────────────────────────── */
.reader-step { position: relative; }
.reader-mascot {
  display: block;
  width: 260px;
  max-width: 80%;
  align-self: flex-start;
  margin: 16px 0 -36px -34px;
  pointer-events: none;
  user-select: none;
}

/* ── 24 Web keuze na opname — who-pays choice cards (7942:1956) ───────────── */
.pay-choice {
  width: 100%;
  display: grid;
  gap: 15px;
  padding: 20px;
  border-radius: 10px;
  text-align: left;
}
.pay-choice-self { background: var(--tile-peach); }
.pay-choice-parent { background: rgba(114, 200, 254, 0.15); }
.pay-choice-badge {
  justify-self: start;
  padding: 3px 8px;
  border-radius: 10px;
  font-size: 13px;
  line-height: 1.2;
}
.badge-self { background: rgba(114, 200, 254, 0.15); color: #72c8fe; }
.badge-parent { background: rgba(108, 92, 255, 0.25); color: var(--flamingo); }
.pay-choice-row { display: flex; gap: 12px; align-items: flex-start; }
.pay-choice-icon {
  width: 77px;
  height: 77px;
  flex-shrink: 0;
  border-radius: 10px;
  background: var(--white);
  object-fit: cover;
}
.pay-choice-text { display: grid; gap: 5px; }
.pay-choice-text strong {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 400;
  line-height: 1.1;
  color: var(--ebony);
}
.pay-choice-text .card-subtitle {
  font-size: 13px;
  font-weight: 400;
  line-height: 1.4;
  color: rgba(28, 24, 64, 0.5);
}
.pay-choice-button {
  width: 100%;
  min-height: 42px;
  line-height: 42px;       /* centre the single-line label (display:grid split it onto 2 rows) */
  text-align: center;
  white-space: nowrap;
  padding: 0 14px;
  border: 0;
  border-radius: 10px;
  background: var(--flamingo);
  color: var(--on-accent);
  font-family: var(--font-body);
  font-size: 18px;
  font-weight: 600;
  cursor: pointer;
}

/* ── Web invite intro (7942-1326) ────────────────────────────────────────── */
.intro-card { text-align: center; }
.intro-mascot {
  display: block;
  width: 200px;
  max-width: 64%;
  margin: 4px auto 22px;
  pointer-events: none;
  user-select: none;
}
.intro-title { margin: 0 0 12px; }
.intro-copy {
  margin: 0 auto;
  max-width: 300px;
  line-height: 1.4;
}
/* Two short reassurances, checkmarked. A list rather than prose: someone
   scanning before they commit reads marks, not sentences. */
.intro-reassure {
  list-style: none;
  margin: 22px auto 0;
  max-width: 300px;
  padding: 0;
  display: grid;
  gap: 8px;
  width: 100%;
  text-align: left;
}
.intro-reassure li {
  display: grid;
  grid-template-columns: 20px 1fr;
  gap: 10px;
  align-items: start;
  font-size: 15px;
  color: var(--text-medium);
}
.intro-reassure li::before {
  content: "";
  width: 20px;
  height: 20px;
  margin-top: 1px;
  background: var(--flamingo);
  -webkit-mask: var(--check-mask) center / 12px 12px no-repeat;
  mask: var(--check-mask) center / 12px 12px no-repeat;
  border-radius: 999px;
  opacity: .9;
}

.intro-note {
  display: flex;
  align-items: center;
  gap: 13px;
  width: 100%;
  margin-top: 36px;
  text-align: left;
}
.intro-note-icon {
  width: 48px;
  height: 48px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  border-radius: var(--radius-pill);
  background: rgba(136, 197, 254, 0.2);
  color: #4aa3e8;
}
.intro-note-text { display: grid; gap: 2px; }
.intro-note-text strong { font-size: 15px; font-weight: 700; color: var(--ebony); }
.intro-note-text span { font-size: 14px; color: var(--text-muted); }
.intro-footer {
  margin: 16px 0 0;
  font-size: 13px;
  color: var(--text-muted);
}

/* ── Web invite "Wat wil je voorlezen?" (7942-1984) ──────────────────────── */
.step[data-step="voorlees-choice"] h2 { margin-bottom: 10px; }
.voorlees-sub { margin: 0; }
.voorlees-stack {
  display: grid;
  gap: 16px;
  margin-top: 26px;
}
.voorlees-card {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 18px;
  border: 0;
  border-radius: 10px;
  text-align: left;
  cursor: pointer;
}
.voorlees-card-heare { background: rgba(136, 197, 254, 0.22); }     /* blue — matches the app (storyBlue @ 0.22); the Heare-original path stands out */
.voorlees-card-eigen { background: var(--tile-peach); }            /* beige */
.voorlees-icon {
  width: 64px;
  height: 64px;
  flex-shrink: 0;
  border-radius: 10px;
  background: var(--white);
  object-fit: contain;
}
.voorlees-text {
  display: grid;
  gap: 4px;
  flex: 1;
  min-width: 0;
}
.voorlees-text strong {
  font-family: var(--font-display);
  font-size: 21px;
  font-weight: 400;
  line-height: 1.1;
  color: var(--ebony);
}
.voorlees-text .card-subtitle {
  font-size: 13px;
  line-height: 1.4;
  color: rgba(28, 24, 64, 0.5);
}
.voorlees-chevron {
  flex-shrink: 0;
  display: grid;
  place-items: center;
  color: rgba(28, 24, 64, 0.4);
}

/* B#4: standalone web (not embedded in the iOS app) — the terminal share screen's
   Done (Klaar) and Close (×) lead nowhere, so hide them. */
html:not(.embedded-app) #doneButton { display: none; }
html:not(.embedded-app):has(.step[data-step="intro"].active) #cancelButton,
html:not(.embedded-app):has(.step[data-step="intent"].active) #cancelButton,
html:not(.embedded-app):has(.step[data-step="share"].active) #cancelButton { visibility: hidden; }

/* Client: the explicit Kopieer/WhatsApp/E-mail pills are redundant — "Deel link"
   opens the system share sheet with every channel. */
#shareActions { display: none; }

/* Checkout cover play/pause glyph swap (the cover is tappable to hear the take). */
.pay-cover-play .glyph-pause { display: none; }
.pay-cover-play.is-playing .glyph-play { display: none; }
.pay-cover-play.is-playing .glyph-pause { display: block; }
.pay-cover { cursor: pointer; }

.chip-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 7px;
  margin-top: 5px;
}

.chip {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  border-radius: 20px;
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 600;
  line-height: 1;
}

.chip-orange { background: var(--peach-badge); color: var(--peach-chip-text); }
.chip-green { background: var(--mint-chip); color: var(--mint-chip-text); }
.chip-blue { background: var(--ice-badge); color: var(--ice-chip-text); }

/* ── Payment + share ─────────────────────────────────────────────────────── */
.pay-preview {
  display: grid;
  justify-items: center;
  gap: 12px;
  margin: 8px 0 18px;
  text-align: center;
}

.pay-preview[hidden] {
  display: none;
}

/* Brand cover (Figma 7804:942): PNG tile, duration chip, play. */
.pay-cover {
  position: relative;
  width: 203px;
  height: 202px;
  border-radius: 20px;
  background: var(--tile-peach); /* fallback; JS sets the PNG cover per story */
  overflow: hidden;
  /* Figma 7942-2006: flat tile, no shadow. */
}

/* Figma 7942-2006: the gift-checkout title is centred. */
.pay-card h2 { text-align: center; }

/* One of 3 brand colours × 3 shapes, chosen per story (like the native tile). */
/* The PNG cover tile fills the card; its art sits bottom-left (like the app). */
.pay-cover-shape {
  position: absolute;
  inset: 0;
}

.pay-cover-shape img {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: auto;
}

/* Sits over the (possibly colorful) cover artwork, not the plain card surface —
   so, like the catalog's own badge-duration chip, it uses a FIXED dark-translucent
   background with white text rather than an inverting token pair. Inverting would
   put white-on-white in dark mode, the same failure as everything else on this
   screen, just via a different route (my first attempt at this fix did exactly
   that before catching it). */
.pay-cover-chip {
  position: absolute;
  left: 12px;
  top: 12px;
  display: inline-flex;
  align-items: center;
  height: 24px;
  padding: 0 10px;
  border-radius: 1000px;
  background: rgba(28, 24, 64, 0.55);
  color: #ffffff;
  font-size: 11px;
  font-weight: 600;
}

.pay-cover-play {
  position: absolute;
  right: 12px;
  bottom: 12px;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.9);
  color: var(--ebony);   /* play glyph black, not purple (client) */
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
}

.pay-cover-title {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 400;
  line-height: 1.2;
  text-align: center;
  color: var(--ebony);
}

/* Byline row (Figma 7942:2006): small reader avatar + "door {reader}". */
.pay-cover-by {
  display: flex;
  align-items: center;
  gap: 5px;
}

.pay-cover-avatar {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  border-radius: 50%;
  background-color: var(--tile-peach);
  background-size: cover;
  background-position: center;
}

.pay-cover-byline {
  color: var(--text-medium);
  font-size: 18px;
}

/* Horizontal price pill: prijs · label | description. */
.pay-price {
  display: flex;
  gap: 20px;
  align-items: center;
  width: 100%;
  margin-top: 4px;
  padding: 20px;
  border-radius: 10px;
  background: var(--white, #fff);
  border: 1px solid var(--tile-peach);
}

.pay-price-amount strong {
  font-family: var(--font-display);
}

.pay-price[hidden] {
  display: none;
}

.pay-price-amount {
  flex-shrink: 0;
  white-space: nowrap;
  font-size: 13px;
  font-weight: 700;
  color: var(--card-subtitle);
}

/* Het prijsbedrag zelf — de tekst die in donkere modus zwart-op-zwart stond
   (client 2026-07-29): een hardgecodeerd donkerblauw #2C2A46 zonder
   thema-variant, op een kaart die zelf donker wordt. */
.pay-price-amount strong {
  font-size: 24px;
  font-weight: 800;
  color: var(--ebony);
}

.pay-price-desc {
  flex: 1;
  font-size: 13px;
  line-height: 1.3;
  color: var(--text-medium);
}

.pay-stripe {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 12px;
  color: var(--text-muted);
  font-size: 12px;
  font-weight: 700;
}

/* EU digital-content consent line under the pay/save button (client legal copy). */
.pay-terms {
  margin: 14px 0 0;
  text-align: center;
  font-size: 11px;
  line-height: 1.45;
  color: var(--text-medium);
}

.pay-stripe[hidden] {
  display: none;
}

/* The label display rule otherwise beats the [hidden] attribute. */
#emailField[hidden] {
  display: none;
}

/* Kortingscode ("Heb je een code?") — collapsed link between the email field
   and the price block; reveal is a small inline input + apply button rather
   than a full-width form, since it's an optional secondary action. */
.discount-toggle {
  width: auto;
  min-height: 0;
  margin: 14px auto 0;
  padding: 4px 0;
  font-size: 14px;
  font-weight: 700;
  text-decoration: underline;
  color: var(--text-medium);
}

.discount-row {
  margin-top: 14px;
}

.discount-row label {
  margin-top: 0;
}

.discount-input-group {
  display: flex;
  gap: 8px;
  align-items: center;
}

.discount-input-group input {
  flex: 1;
  text-transform: uppercase;
}

.discount-input-group input::placeholder {
  text-transform: none;
}

/* Same fill as `.dark-button` — deliberately NOT the purple primary, which
   belongs to "Sla verhaal op" one block below; applying a code is a secondary
   action next to its input. It inherits that button's dark-theme override too
   (see html.theme-dark below): `--ebony` inverts to white in dark mode, so on
   its own this rule painted white text on a white fill (client 2026-08-20). */
.discount-apply-button {
  flex-shrink: 0;
  min-height: 52px;
  padding: 0 18px;
  border: 0;
  border-radius: var(--radius-button);
  background: var(--ebony);
  color: var(--on-accent);
  font-family: var(--font-body);
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
}

.discount-apply-button:disabled {
  opacity: 0.5;
  cursor: default;
}

.discount-status {
  margin: 8px 0 0;
  font-size: 13px;
  line-height: 1.4;
}

.discount-status.is-error {
  color: #d64545;
}

.discount-status.is-success {
  color: var(--ebony);
  font-weight: 700;
}

.discount-row[hidden],
.discount-toggle[hidden] {
  display: none;
}

.claim-link {
  overflow-wrap: anywhere;
  margin: 22px 0 4px;
  padding: 16px;
  border-radius: var(--radius-input);
  background: var(--tile-peach);
  color: var(--ebony);
  font-size: 15px;
}

/* #fff2ed was one digit off from --tile-peach (#fff3e7) — close enough that it was
   clearly meant to be the same warm card, just hardcoded separately and so never
   inverted in dark mode. color: var(--ebony) DID invert (to white), which is what
   produced the white-on-light-peach bug shared with everything else on this
   screen. Using the actual token fixes both at once. */
.error-box {
  margin: 18px 0 0;
  padding: 16px;
  border-radius: var(--radius-input);
  background: var(--tile-peach);
  color: var(--ebony);
  text-align: left;
}

.error-box strong {
  display: block;
  font-size: 16px;
}

.error-box p {
  margin: 6px 0 0;
  color: var(--text-muted);
  font-size: 14px;
  line-height: 1.4;
}

.technical-error {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px !important;
  overflow-wrap: anywhere;
}

/* ── 26 Web cadeau success + claim page — Hearo gift illustration ──────────── */
.gift-illustration {
  position: relative;
  width: 100%;
  height: 196px;
  margin: 6px auto 18px;
  display: grid;
  place-items: center;
}

.gift-illustration .hearo {
  position: relative;
  z-index: 1;
  width: 168px;
  height: auto;
}

.gift-illustration .hearo.with-photo {
  width: 156px;
}

.confetti {
  position: absolute;
  pointer-events: none;
}

.confetti-heart {
  width: 30px;
  height: 28px;
  background: url("/assets/blue-heart.png") center / contain no-repeat;
}

.confetti-star {
  width: 38px;
  height: 38px;
  background: url("/assets/purple-star.png") center / contain no-repeat;
}

.confetti-dot {
  width: 11px;
  height: 11px;
  border-radius: 50%;
}

.confetti-dot-orange { background: var(--flamingo); }
.confetti-dot-blue { background: var(--story-blue); }

.confetti-tl { top: 16%; left: 9%; transform: rotate(10deg); }
.confetti-tr { top: 20%; right: 8%; transform: rotate(-13deg); }
.confetti-dot.confetti-dot-orange { bottom: 28%; left: 9%; }
.confetti-dot.confetti-dot-blue { bottom: 24%; right: 11%; }

/* Reader's photo (optional) — a small circular "from" badge on the Hearo's
   lower-right, like the in-app narrator chip. */
.gift-illustration .reader-photo {
  position: absolute;
  z-index: 2;
  bottom: 8px;
  right: 19%;
  width: 60px;
  height: 60px;
  border-radius: 50%;
  object-fit: cover;
  border: 3px solid var(--room);
  box-shadow: 0 6px 14px -6px rgba(43, 39, 66, 0.4);
}

/* ── Share success (frame 26) + claim page cards ──────────────────────────── */
.share-card,
.claim-card {
  text-align: center;
}

.share-card h2,
.claim-card h2 {
  text-align: center;
  margin-bottom: 14px;
}

.share-card .body-copy,
.claim-card .body-copy,
.claim-status {
  text-align: center;
}

/* Copy / WhatsApp / E-mail row on the gift success screen. */
.share-actions {
  display: flex;
  justify-content: center;
  gap: 10px;
  margin-top: 16px;
}

.share-actions[hidden] {
  display: none;
}

.share-pill {
  flex: 1;
  min-height: 50px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--radius-input);
  background: var(--white);
  color: var(--ebony);
  font-size: 15px;
  font-weight: 600;
  text-align: center;
  text-decoration: none;
  box-shadow: 0 6px 14px -8px rgba(43, 39, 66, 0.3);
}

/* ── Claim page — in-browser preview player ───────────────────────────────── */
.claim-shell .step {
  align-items: start;
}

.claim-player {
  display: grid;
  justify-items: center;
  margin-top: 22px;
}

.claim-player[hidden] {
  display: none;
}

.claim-player audio {
  display: none;
}

.play-pill {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  min-height: 56px;
  padding: 0 26px;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--white);
  color: var(--ebony);
  font-size: 16px;
  font-weight: 600;
  box-shadow: 0 8px 18px -10px rgba(43, 39, 66, 0.4);
}

.play-glyph {
  display: grid;
  place-items: center;
  line-height: 0;
  color: var(--flamingo);   /* claim play-pill default; preview overrides to white */
}
.play-glyph .glyph-pause { display: none; }
.play-glyph .glyph-play { margin-left: 2px; }   /* optical centring of the triangle */
.is-playing .glyph-play { display: none; }
.is-playing .glyph-pause { display: block; }

/* Play/pause swap handled by the shared .is-playing glyph rules above. */

.claim-card .primary-link-button {
  margin-top: 22px;
}

.claim-card .dark-button {
  margin-top: 12px;
}

/* Microphone-denied recovery card (never a frozen 0:00 screen). Blocking by
   design (client ask): `position: fixed` + an oversized box-shadow dims the
   WHOLE viewport behind the card (no extra markup needed for a scrim) so the
   waveform/save controls read as unreachable, not just de-emphasised — the
   record/save buttons are ALSO actually disabled underneath (recorder-ui.js),
   this is the "you can't miss it" half. */
.mic-error {
  position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: calc(100% - 2 * var(--screen-margin)); max-width: 340px;
  margin: 0;
  padding: 20px;
  background: var(--white);
  border-radius: 20px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.08), 0 0 0 9999px rgba(0, 0, 0, 0.45);
  text-align: center;
  z-index: 5;
}
.mic-error-title {
  margin: 0 0 6px;
  font-weight: 700;
  font-size: 18px;
  color: var(--ebony, #2b2742);
}
.mic-error-body {
  margin: 0 0 16px;
  font-size: 15px;
  line-height: 1.4;
  color: var(--text-medium, #5b616d);
}
.mic-retry {
  appearance: none;
  border: none;
  cursor: pointer;
  width: 100%;
  min-height: 48px;
  padding: 14px 20px;
  border-radius: 24px;
  background: var(--flamingo, #6c5cff);
  color: #fff;
  font-weight: 600;
  font-size: 16px;
}
.mic-retry:active { opacity: 0.9; }

/* ── Saving overlay (Stoppen → preview feedback; blocks the transport) ──
   Shown immediately on tapping "Klaar" — the very first thing a dark-mode
   listener would have seen, and until this fix it was a white-on-white card,
   the first stop on the chain of dark-mode contrast bugs after "Klaar". */
.saving-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.88);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
html.theme-dark .saving-overlay { background: rgba(29, 27, 56, 0.88); }
.saving-overlay[hidden] { display: none; }
.saving-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding: 28px 32px;
  background: var(--white);
  border-radius: 22px;
  box-shadow: 0 18px 40px rgba(43, 39, 66, 0.14);
}
.saving-spinner {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 4px solid rgba(108, 92, 255, 0.18);
  border-top-color: var(--flamingo);
  animation: saving-spin 0.9s linear infinite;
}
@keyframes saving-spin { to { transform: rotate(360deg); } }
.saving-text {
  margin: 0;
  color: var(--ebony);
  font-weight: 600;
  font-size: 16px;
}

/* Branded confirm dialog (destructive "Opnieuw" guard). */
.confirm-overlay {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(43, 39, 66, 0.45);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}
.confirm-overlay[hidden] { display: none; }
.confirm-card {
  width: 100%;
  max-width: 360px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 28px 24px 18px;
  background: var(--white);
  border-radius: 22px;
  box-shadow: 0 22px 50px rgba(43, 39, 66, 0.22);
  text-align: center;
}
.confirm-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 24px;
  color: var(--ebony);
}
.confirm-body {
  margin: 0 0 8px;
  font-size: 16px;
  line-height: 1.45;
  color: var(--text-medium);
}
.confirm-card .confirm-accept { margin-top: 4px; }

/* ── Photo crop overlay (square crop, circular badge hint) ── */
.crop-overlay {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(43, 39, 66, 0.6);
  padding: 20px;
}
.crop-overlay[hidden] { display: none; }
.crop-sheet {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  width: min(92vw, 380px);
  padding: 22px;
  background: var(--room);
  border-radius: 22px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.28);
}
.crop-title {
  margin: 0;
  color: var(--ebony);
  font-size: 19px;
}
.crop-viewport {
  position: relative;
  width: min(76vw, 300px);
  aspect-ratio: 1;
  overflow: hidden;
  border-radius: 16px;
  background: #1d1a2e;
  touch-action: none;       /* the viewport owns the drag */
  cursor: grab;
}
.crop-viewport:active { cursor: grabbing; }
.crop-image {
  position: absolute;
  top: 0;
  left: 0;
  max-width: none;
  user-select: none;
  -webkit-user-select: none;
  pointer-events: none;     /* drags land on the viewport */
}
/* Darkened corners with a clear circle — the photo shows as a round badge. */
.crop-mask {
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: 50%;
  box-shadow: 0 0 0 9999px rgba(43, 39, 66, 0.45);
}
.crop-zoom {
  width: min(76vw, 300px);
  accent-color: var(--flamingo);
}
.crop-actions {
  display: flex;
  flex-direction: column;
  gap: 4px;
  width: 100%;
}

/* ── Heare-story mode: hide markeer, show the read-aloud story panel (7942-1349) ── */
.story-text-panel {
  min-height: 0;            /* allow it to shrink so ONLY it scrolls, not the page */
  width: 100%;
  max-height: none;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  background: var(--reading-surface);
  border-radius: 14px;
  padding: 18px 20px;
  text-align: left;
  color: var(--ebony);
  font-family: var(--font-display);
  font-size: 19px;
  line-height: 1.55;
  letter-spacing: 0.03em;
  /* Alleen de laatste paar procent verzachten, zodat de eerstvolgende regel die
     de lezer nodig heeft nooit wegvaagt. */
  -webkit-mask-image: linear-gradient(to bottom, #000 0, #000 96%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 0, #000 96%, transparent 100%);
}
.story-text-panel p { margin: 0 0 12px; }
.story-text-panel p:last-child { margin-bottom: 0; }

/* Placeholder lines while the real story loads (loadHeareStoryContent /
   selectCatalogStory) — pulsing bars, never borrowed text from another story.
   Same animation as the catalog skeleton, so the two loading moments read as
   one visual language. */
.story-text-skeleton-line {
  height: 16px;
  margin-bottom: 12px !important;
  border-radius: 4px;
  background: rgba(28, 24, 64, 0.08);
  animation: catalog-pulse 1.2s ease-in-out infinite;
}
.story-text-skeleton-line.story-text-skeleton-short { width: 60%; }
.story-text-title.story-text-skeleton-line { width: 75%; height: 19px; }
html.theme-dark .story-text-skeleton-line { background: rgba(255, 255, 255, 0.1); }
.story-text-title {
  font-family: var(--font-display);
  font-size: 19px;
  margin: 0 0 14px !important;
}
body.heare-story .transport-control--marker,
body.heare-story .marker-chip,
body.heare-story .ed-mark { display: none; }

/* ── Recorder editor (8029-1315): waveform + scrub + replace ──────────────── */
.nav-check { visibility: hidden; }
body:has(.step[data-step="recording"].active) .nav-check { visibility: visible; }
body:has(.step[data-step="recording"].active) .nav h1 { visibility: hidden; }
.nav-check { justify-self: end; }
/* De vink erft de kleur van de knop, zodat hij in beide standen meeschakelt. */
#editorSaveButton {
  width: auto;
  min-width: 88px;
  padding: 0 14px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: #d7d3e6;
  border-radius: 1000px;
  /* Was #fff, en dat is op deze lichte vulling 1,46:1 — onleesbaar (testgroep
     2026-08-27). De "nog niets opgenomen"-stand hoort rustig te zijn, niet
     onzichtbaar; het verschil met "klaar om te bewaren" zit in de VULLING die
     paars wordt, niet in het al dan niet kunnen lezen van het woord.

     Een vaste waarde en NIET `var(--ebony)`: die keert in het donkere thema om
     naar wit (zie de override onderaan dit bestand), en dan sta je weer met wit
     op lichtpaars — precies de val waar `.discount-apply-button` hierboven al
     een opmerking over heeft staan. De vulling van deze knop is in beide
     thema's dezelfde lichte kleur, dus de inkt hoort dat ook te zijn. 11,4:1. */
  color: #1c1840;
  font-size: 15px;
  font-weight: 700;
  transition: background .15s, color .15s;
}
#editorSaveButton.is-active { background: var(--flamingo); color: var(--on-accent); }
#editorSaveButton:disabled { cursor: default; }

/* De golfvorm zit in dezelfde grid-cel als de voorleestekst (zie .record-controls
   verderop) en deelt daarmee hetzelfde oppervlak en dezelfde hoogte — vandaar
   volle celbreedte in plaats van de oude edge-to-edge band. */
.wave-view { align-self: stretch; width: 100%; }

.wave-area {
  position: relative; align-self: stretch; width: auto; height: 130px;
  background: var(--reading-surface); border-radius: 14px;
  overflow: hidden; touch-action: none;
}
.wave-canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
/* now-line: fixed at the horizontal center, full band height, dots top & bottom */
.wave-playhead {
  position: absolute; top: 0; bottom: 0; width: 1.5px; margin-left: -0.75px;
  background: var(--flamingo); pointer-events: none;
}
.wave-playhead::before, .wave-playhead::after {
  content: ""; position: absolute; left: 50%; width: 8px; height: 8px; margin-left: -4px;
  border-radius: 50%; background: var(--flamingo);
}
.wave-playhead::before { top: -4px; } .wave-playhead::after { bottom: -4px; }
.wave-markers { position: absolute; inset: 0; pointer-events: none; }
/* Page marker (8029-1315): a THIN blue line with a dot at BOTH ends, mirroring the
   now-line — was a thicker 3px bar with only a top dot. */
.wave-pin { position: absolute; top: 0; bottom: 0; width: 2px; margin-left: -1px; background: #72c8fe; }
.wave-pin::before, .wave-pin::after {
  content: ""; position: absolute; left: 50%; width: 7px; height: 7px; margin-left: -3.5px;
  border-radius: 50%; background: #72c8fe;
}
.wave-pin::before { top: -3.5px; }
.wave-pin::after { bottom: -3.5px; }
/* scrolling timeline ticks (canvas), full-bleed under the band */
.wave-ticks {
  display: block; align-self: stretch; width: auto; height: 22px;
  margin: 4px calc(-1 * var(--screen-margin)) 0;
}

.editor-timer {
  margin: 16px 0 4px; text-align: center; font-family: var(--font-display);
  font-size: 32px; font-weight: 400; color: var(--ebony); letter-spacing: 1px;
}
/* Vaste rij-verdeling: play 25% / markeer 25% / opnemen altijd 50% — ongeacht
   welke tekst de knop toont (Opnemen, Pauzeer, Ga door, Vervang), zelfde
   hoogte voor alle drie (client, met screenshot-referentie). justify-content:
   center zodat play+opnemen samen als één geheel gecentreerd blijven staan
   wanneer de markeer-knop verborgen is (heareStory/read-along tekst-modus,
   `body.heare-story .ed-mark`) — zonder deze regel bleef flex's default
   flex-start over en schoven de resterende twee knoppen naar links (client). */
.editor-controls { display: flex; align-items: stretch; justify-content: center; gap: 12px; margin: 8px 0 0; width: 100%; }
.editor-controls:empty { display: none; }
.ed-circle {
  flex: 0 0 25%; height: 56px; display: grid; place-items: center; border: 0; border-radius: 1000px;
  background: var(--tile-peach); color: var(--ebony); cursor: pointer;
}
.ed-circle:disabled { opacity: .5; cursor: default; }
.ed-play .ed-glyph-pause { display: none; }
.ed-play.is-playing .ed-glyph-play { display: none; }
.ed-play.is-playing .ed-glyph-pause { display: block; }
.ed-record-slot { flex: 0 0 50%; min-width: 0; display: flex; }
.ed-mic {
  width: 100%; height: 56px; display: grid; place-items: center; border: 0;
  border-radius: 1000px; background: var(--flamingo); cursor: pointer;
}
.ed-mic-label { display: none; }
.ed-mic.is-recording .ed-mic-glyph { display: none; }
/* While recording the button PAUSES (engine keeps the take, resumable via Ga door),
   so it shows a pause glyph (two bars), not a stop square. Stays the same 50%
   slot / height in every state — it never resizes as its label changes (client). */
.ed-mic.is-recording {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  width: 100%; height: 56px; border-radius: 1000px;
  color: #fff; font-size: 17px; font-weight: 700;
}
.ed-mic.is-recording::before { content: ""; width: 6px; height: 20px; border-radius: 2.5px; background: #fff; box-shadow: 10px 0 0 #fff; margin-right: 10px; }
.ed-mic.is-recording::after { content: none; }
.ed-mic.is-recording .ed-mic-label { display: inline; }
.ed-pill {
  display: block; width: 100%; height: 56px; padding: 0 26px;
  border: 0; border-radius: 1000px; background: #ede9ff; color: var(--flamingo);
  font-family: var(--font-body); font-size: 17px; font-weight: 600; cursor: pointer;
}
.ed-pill.is-replace { background: var(--flamingo); color: #fff; }
.ed-pill:disabled { opacity: .5; cursor: default; }   /* greyed while the take plays back */
/* class display rules beat the [hidden] attribute — restore it for editor controls.
   !important because html.theme-dark .ed-mic (element+2 classes) outranks this
   otherwise (2 classes) — without it, [hidden] silently lost in dark theme and
   mic + pill rendered at the same time, overflowing their shared 50% slot
   (client report: "Opnemen" and "Ga door" both visible side by side). */
.ed-mic[hidden], .ed-pill[hidden], .ed-circle[hidden] { display: none !important; }

/* ── v1.1 guided story selection (flow=v2) ────────────────────────────────────
   Catalog browse + book search. Light system, same tokens as the rest of the
   recorder — the accent (--flamingo #6c5cff) already matches the Figma redesign;
   only the dark surface treatment is deferred (whole-flow decision). */

/* The step is a grid whose single column is content-sized by default, so the
   horizontally scrolling tile row would widen the whole page. Cap the column at
   the viewport and let the row scroll inside it. */
[data-step="catalog"].step.active { grid-template-columns: minmax(0, 1fr); }

.catalog-rows {
  display: grid;
  gap: 30px;
  margin-top: 30px;
  min-width: 0;
}

.catalog-row { min-width: 0; }

.catalog-tile-enter { animation: catalog-fade-in 380ms cubic-bezier(0.32, 0.72, 0, 1) both; }
@keyframes catalog-fade-in {
  from { opacity: 0; transform: translateY(14px) scale(0.97); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .catalog-tile-enter { animation: none; }
}

.catalog-row h3 {
  margin: 0 0 14px;
  font-family: var(--font-display);
  font-size: 21px;
  font-weight: 400;
  color: var(--ebony);
}

/* Tiles scroll horizontally and bleed past the 340px column so the row reads as
   scrollable without a scrollbar. */
.catalog-tiles {
  display: flex;
  align-items: flex-start;   /* covers stay flush top even when a title wraps to 2 lines */
  min-width: 0;
  gap: 10px;
  overflow-x: auto;
  scrollbar-width: none;
  margin-right: calc(-1 * var(--screen-margin));
  padding-right: var(--screen-margin);
  -webkit-overflow-scrolling: touch;
}

.catalog-tiles::-webkit-scrollbar { display: none; }

.catalog-tile {
  width: 140px;
  flex-shrink: 0;
  border: 0;
  padding: 0;
  background: transparent;
  text-align: left;
  cursor: pointer;
}

.catalog-tile:disabled { opacity: 0.6; }

.catalog-cover {
  width: 140px;
  height: 160px;
  position: relative;
  border-radius: var(--radius-card);
  background: rgba(136, 197, 254, 0.22);
  overflow: hidden;
}

.catalog-cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.catalog-duration {
  position: absolute;
  top: 10px;
  left: 10px;
  padding: 3px 8px;
  border-radius: var(--radius-pill);
  background: rgba(28, 24, 64, 0.45);
  color: var(--on-accent);
  font-size: 11px;
  font-weight: 500;
}

.catalog-tile-title {
  display: block;
  margin-top: 10px;
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 400;
  line-height: 1.2;
  color: var(--ebony);
}

.catalog-tile.skeleton .catalog-cover { animation: catalog-pulse 1.2s ease-in-out infinite; }
.catalog-tile.skeleton .catalog-tile-title {
  width: 80%;
  height: 14px;
  border-radius: 4px;
  background: rgba(136, 197, 254, 0.22);
}

/* Stand-in for the row's h3 heading while categories are still loading — same
   box, no text, so 3 skeleton rows read as 3 categories, not 3 anonymous grids. */
.catalog-row-title-skeleton {
  width: 140px;
  height: 21px;
  margin: 0 0 14px;
  border-radius: 4px;
  background: rgba(136, 197, 254, 0.22);
  animation: catalog-pulse 1.2s ease-in-out infinite;
}

@keyframes catalog-pulse { 50% { opacity: 0.5; } }

.catalog-error { margin-top: 20px; }

/* ── Boek-detail (vóór opnemen) — zelfde kleurtaal als de catalogus-tegels
   hierboven (cover-tint + donkere pil), maar los i.p.v. tegel, gecentreerd. */
.story-detail-card { text-align: center; }

.story-detail-cover {
  width: min(211px, 60%);
  aspect-ratio: 1;
  margin: 0 auto 20px;
  border-radius: var(--radius-card);
  background: rgba(136, 197, 254, 0.22);
  overflow: hidden;
}

html.theme-dark .story-detail-cover { background: rgba(108, 92, 255, 0.22); }

.story-detail-cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.story-detail-title {
  margin: 0 0 4px;
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 400;
  line-height: 1.2;
  color: var(--ebony);
}

.story-detail-attribution {
  margin: 0 0 14px;
  color: var(--text-muted);
  font-size: 12px;
}

.story-detail-pills {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-bottom: 18px;
}

.story-detail-pill {
  padding: 3px 8px;
  border-radius: var(--radius-pill);
  background: rgba(28, 24, 64, 0.45);
  color: var(--on-accent);
  font-size: 12px;
  font-weight: 500;
}

.story-detail-pills[hidden],
.story-detail-pill[hidden] { display: none; }

.story-detail-description {
  margin: 0 0 4px;
  color: var(--text-muted);
  font-size: 15px;
  line-height: 1.4;
  /* Max 2 regels, zelfde als de native omschrijving. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* "Bekijk verhaal" — een link, geen knop: de actie op deze stap blijft
   "Start opname". Layout thema-neutraal; kleur komt uit --flamingo. */
.story-read-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin: 16px auto 0;
  padding: 6px 4px;
  border: 0;
  background: transparent;
  color: var(--flamingo);
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 600;
}

.story-read-link[hidden] { display: none; }

/* De leespagina zelf: hetzelfde leesoppervlak en dezelfde typografie als het
   tekstpaneel in de opnamestand, zodat het één verhaal blijft — alleen hier
   rustig op een eigen scherm i.p.v. naast de opnameknoppen. */
.story-read-title {
  margin: 0 0 16px;
  font-family: var(--font-display);
  font-size: 25px;
  font-weight: 400;
  line-height: 1.2;
  text-align: left;
  color: var(--ebony);
}

.story-read-body {
  background: var(--reading-surface);
  border-radius: 14px;
  padding: 18px 20px;
  text-align: left;
  color: var(--ebony);
  font-family: var(--font-display);
  font-size: 19px;
  line-height: 1.55;
  letter-spacing: 0.03em;
}

.story-read-body p { margin: 0 0 12px; }
.story-read-body p:last-child { margin-bottom: 0; }

html.theme-dark .story-read-body { color: var(--reading-ink); }

.story-detail-credits { margin-top: 20px; text-align: center; }

.story-detail-credits-heading {
  margin: 0 0 4px;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
}

.story-detail-credits-text {
  margin: 0;
  font-size: 12px;
  line-height: 1.4;
  color: var(--text-muted);
}

.story-detail-credits-text a {
  color: inherit;
  text-decoration: underline;
}

.catalog-error[hidden] { display: none; }

.book-search-field {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 52px;
  margin-top: 20px;
  padding: 0 16px;
  border: 1px solid rgba(0, 0, 0, 0.03);
  border-radius: 12px;
  background: var(--white);
  color: var(--text-low);
}

.book-search-field input {
  flex: 1;
  min-height: 0;
  border: 0;
  padding: 14px 0;
  background: transparent;
}

.book-search-field input::-webkit-search-cancel-button { display: none; }

.book-search-clear {
  border: 0;
  padding: 4px;
  background: transparent;
  color: var(--text-muted);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}

.book-results-label {
  margin: 26px 0 4px;
  font-family: var(--font-display);
  font-size: 21px;
  font-weight: 400;
  color: var(--ebony);
}

.book-results-label[hidden] { display: none; }

.book-row {
  width: 100%;
  position: relative;
  display: grid;
  gap: 2px;
  padding: 14px 34px 14px 0;
  border: 0;
  border-bottom: 1px solid rgba(28, 24, 64, 0.08);
  background: transparent;
  text-align: left;
  cursor: pointer;
}

.book-row strong {
  font-size: 17px;
  font-weight: 600;
  color: var(--ebony);
}

.book-row-author {
  color: var(--text-muted);
  font-size: 14px;
}

.book-row-badge {
  justify-self: start;
  margin-top: 6px;
  padding: 3px 8px;
  border-radius: var(--radius-pill);
  background: rgba(108, 92, 255, 0.12);
  color: var(--flamingo);
  font-size: 12px;
  font-weight: 500;
}

.book-row .choice-chevron { right: 4px; }

.book-fallback { margin-top: 30px; }

/* ══ v1.1 dark theme (flow=v2) ═══════════════════════════════════════════════
   Client decision 2026-07-26: the v1.1 design IS the dark theme; today's look
   stays as the light theme. Everything below is a token override on
   <html class="theme-dark"> plus the few surfaces that need a different
   treatment on dark — component rules stay shared between both themes. */

html.theme-dark {
  --room: #1d1b38;
  --white: #19172f;              /* cards sit just above the page, not on top of it */
  --ebony: #ffffff;              /* title/body ink inverts */
  --tile-peach: rgba(108, 92, 255, 0.15);
  --text-muted: rgba(255, 255, 255, 0.5);
  --text-medium: rgba(255, 255, 255, 0.7);
  --text-low: rgba(255, 255, 255, 0.35);
  --card-subtitle: rgba(255, 255, 255, 0.5);

  --reading-ink: #e0e0e0;
  --reading-ink-upcoming: rgba(219, 219, 219, 0.2);
  --ra-word-read: #ffffff;
  --ra-word-upcoming: rgba(255, 255, 255, 0.5);
  --panel-tint: rgba(108, 92, 255, 0.15);

  --toggle-bg: rgba(255, 255, 255, 0.08);
  --toggle-ink: rgba(255, 255, 255, 0.55);
  --reading-surface: rgba(108, 92, 255, 0.12);
}

/* Light falls from above the bed — the page is a touch lighter at the top. */
html.theme-dark body,
html.theme-dark .flow-shell {
  background: linear-gradient(to bottom, #26234a, var(--room) 42%);
  background-attachment: fixed;
}

html.theme-dark .choice-button-parent { background: rgba(108, 92, 255, 0.15); }
html.theme-dark .choice-button-gift { background: rgba(114, 200, 254, 0.12); }
html.theme-dark .choice-copy span,
html.theme-dark .choice-chevron { color: rgba(255, 255, 255, 0.5); }
html.theme-dark .voorlees-card-heare { background: rgba(114, 200, 254, 0.12); }
html.theme-dark .voorlees-card-eigen { background: rgba(108, 92, 255, 0.15); }
html.theme-dark input { border-color: rgba(255, 255, 255, 0.06); color: #ffffff; }
html.theme-dark .book-search-field { border-color: rgba(255, 255, 255, 0.06); }
html.theme-dark .book-row { border-bottom-color: rgba(255, 255, 255, 0.08); }
html.theme-dark .catalog-cover { background: rgba(108, 92, 255, 0.22); }
html.theme-dark .secondary-button { background: rgba(255, 255, 255, 0.08); color: #ffffff; }
html.theme-dark .dark-button,
html.theme-dark .discount-apply-button { background: rgba(255, 255, 255, 0.1); }

/* ── Nav: back arrow, not a cross ──────────────────────────────────────────
   Figma 527-3047 shows the arrow on every screen of the guided flow; the × is
   only for a step that actually leaves the recorder. */
.icon-button .nav-glyph-back { display: none; }
.icon-button.is-back .nav-glyph-back { display: block; }
.icon-button.is-back .nav-glyph-close { display: none; }
#cancelButton { display: grid; place-items: center; }

/* ── iOS-style push / pop ──────────────────────────────────────────────────
   UIKit's own easing; the incoming screen carries the motion. */
/* A 28px nudge read as a fade with barely-there motion, not a push (client
   report: "alles verschijnt gewoon in één keer"). A real UIKit push slides the
   full screen width — this now does the same, so the incoming step visibly
   swipes in from the trailing/leading edge instead of just brightening. */
@keyframes step-push-in {
  from { opacity: 0; transform: translate3d(100%, 0, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}

@keyframes step-pop-in {
  from { opacity: 0; transform: translate3d(-100%, 0, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}

.step.step-push { animation: step-push-in 380ms cubic-bezier(0.32, 0.72, 0, 1) both; }
.step.step-pop  { animation: step-pop-in 340ms cubic-bezier(0.32, 0.72, 0, 1) both; }

@media (prefers-reduced-motion: reduce) {
  .step.step-push,
  .step.step-pop { animation: none; }
}

/* ── Recorder, dark (Figma 522-2763 / 520-2488 / 523-2815) ─────────────────
   The chosen story titles the screen, the text to read aloud is the centre of
   gravity, and the controls are two pills: white = play, purple = the verb. */

html.theme-dark .recording-panel { background: transparent; }

html.theme-dark .recording-header h2 {
  color: #ffffff;
  font-size: 32px;
}

html.theme-dark .recording-subtitle {
  color: rgba(255, 255, 255, 0.5);
  font-size: 18px;
}

/* Surface, radius, typografie en masker staan in de gedeelde .story-text-panel
   (beide thema's delen de layout); hier alleen nog de inkt die per thema
   verschilt. */
html.theme-dark .story-text-panel {
  border: 0;
  color: var(--reading-ink);
}

html.theme-dark .story-text-panel .story-text-title {
  font-family: var(--font-display);
  color: #ffffff;
}

/* Everything not yet read sits back; the reader's eye stays on the top lines. */
html.theme-dark .story-text-panel p { color: var(--reading-ink-upcoming); }
html.theme-dark .story-text-panel p:nth-of-type(-n + 4) { color: var(--reading-ink); }

html.theme-dark .editor-timer {
  font-family: var(--font-display);
  font-size: 32px;
  color: #ffffff;
}

/* Same 25/25/50 row as the light theme (shared .ed-circle / .ed-record-slot
   rules) — dark only recolors, sizing comes from the shared rules. */
html.theme-dark .editor-controls .ed-play {
  background: #ffffff;
  color: var(--room);
}

html.theme-dark .editor-controls .ed-play:disabled { opacity: 0.45; }

html.theme-dark .ed-mic {
  border-radius: var(--radius-pill);
  background: var(--flamingo);
  gap: 8px;
}

html.theme-dark .ed-mic-label {
  display: inline;
  color: #ffffff;
  font-size: 18px;
  font-weight: 600;
}

/* De Figma-pil is tekst, geen icoon: met een microfoon bóven het woord wordt een
   pil van 55px krap en leest hij als twee dingen. */
html.theme-dark .ed-mic .ed-mic-glyph { display: none; }

html.theme-dark .ed-mic {
  display: flex;
  align-items: center;
  justify-content: center;
}

html.theme-dark .ed-pill {
  border-radius: var(--radius-pill);
  background: var(--flamingo);
  color: #ffffff;
  font-weight: 600;
}

html.theme-dark .marker-chip {
  background: rgba(255, 255, 255, 0.1);
  color: rgba(255, 255, 255, 0.7);
}

html.theme-dark .marker-chip span {
  color: rgba(255, 255, 255, 0.85);
}

html.theme-dark .wave-area,
html.theme-dark .wave-ticks { opacity: 0.85; }

/* The wordmark is a dark-ink SVG; invert it on the dark theme. */
html.theme-dark .nav-wordmark img { filter: invert(1) brightness(1.6); }

/* Subtitles inside cards are hard-coded ebony-on-light in several places; on the
   dark theme they need the inverted muted ink instead. */
html.theme-dark .choice-copy span,
html.theme-dark .voorlees-text .card-subtitle,
html.theme-dark .choice-card .card-subtitle,
html.theme-dark .pay-choice-text .card-subtitle,
html.theme-dark .body-copy,
html.theme-dark .muted {
  color: rgba(255, 255, 255, 0.55);
}

html.theme-dark .voorlees-chevron,
html.theme-dark .choice-chevron {
  color: rgba(255, 255, 255, 0.45);
}

/* ── Recorder layout, beide thema's (Figma 522-2763) ───────────────────────
   Figma order is: title → text to read → timeline → timer → two pills side by
   side. The DOM order differs, so this grid re-places the same elements with
   grid areas — no DOM or recorder-logic changes, so the engine keeps working
   exactly as it does today.

   Stond tot 2026-08-18 alleen achter `html.theme-dark`, waardoor LIGHT nog op de
   oude flex-kolom viel en de voorleestekst onderaan het scherm belandde in plaats
   van bovenaan ("dit is oud", client). Layout is nu thema-neutraal; alleen
   KLEUREN verschillen nog per thema. */
.record-controls {
  display: grid;
  grid-template-columns: 155px 155px;
  grid-template-areas:
    "story story"
    "wave  wave"
    "ticks ticks"
    "timer timer"
    "controls controls";
  justify-content: center;
  align-content: start;
  gap: 14px 15px;
  padding-bottom: 12px;
}

.story-text-panel { grid-area: story; margin: 0; max-height: 46vh; }
.editor-timer { grid-area: timer; justify-self: center; margin: 6px 0 2px; }
/* opnemen/play/mark live in .editor-controls's own 25/25/50 row now (shared
   rules above) — spans the full grid width instead of a separate "rec" column. */
.editor-controls { grid-area: controls; margin: 0; width: 100%; }

.marker-chip { grid-column: 1 / -1; justify-self: center; }
.story-text-panel { align-self: start; }

/* In story mode the markers are hidden anyway; keep the grid tight. */
body.heare-story .marker-chip { display: none; }

/* Keep the waveform inside its grid cell — the old light theme let it bleed past
   the screen margin with a negative margin, which inside this grid overlapped the
   story text. */
.wave-area {
  margin: 0;
  width: auto;
  align-self: stretch;
}

/* Figma's left control is a WHITE pill (dark glyph), including while it is still
   disabled — a grey pill reads as broken rather than "nothing to play yet". */
html.theme-dark .editor-controls .ed-play:disabled {
  opacity: 1;
  background: #ffffff;                 /* stays the white pill from the Figma */
  color: rgba(29, 27, 56, 0.32);       /* only the glyph reads as "nothing yet" */
}

/* The panel's row must be allowed to shrink, or a long story overflows its grid
   row and slides under the waveform. minmax(0,1fr) + min-height:0 is what lets
   the text scroll INSIDE its row instead of spilling out of it. */
.record-controls {
  grid-template-rows: minmax(0, 1fr) auto auto auto auto;
  min-height: 0;
}

.story-text-panel {
  min-height: 0;
  max-height: none;
  overflow-y: auto;
}

/* minmax(0,1fr) only constrains a row when the grid itself has a definite height.
   The recorder step is content-sized, so give it a real height via the flex
   chain — then the story row can shrink and scroll internally. */
.step.recording-step.active {
  display: flex;
  min-height: 0;
  height: calc(100dvh - 80px);
  padding-bottom: calc(12px + env(safe-area-inset-bottom));
}

.recording-panel {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  width: min(100%, 340px);
  margin: 0 auto;
}

.record-controls {
  flex: 1;
  min-height: 0;
}

/* align-self:start sizes the panel to its content, which is exactly what makes it
   overflow its row — it must stretch to the row and scroll inside it. */
.story-text-panel {
  align-self: stretch;
  height: 100%;
}

/* ── Meelezen (flow=v2) ────────────────────────────────────────────────────
   Twee woordstates, allebei afgeleid van hoe ver de lezer zelf gescrold heeft.
   Bewerken zit in de golfvorm, dus hier valt niets te selecteren. */

.ra-host { position: relative; }

.ra-section {
  margin: 0 0 14px;
  color: var(--reading-ink-upcoming);
}

.ra-word { transition: color 240ms ease; }

/* Gelezen tekst is de sterkste inkt, nog te lezen tekst blijft LEESBAAR. Dat
   laatste is de kern: op 20% dekking kon de lezer niet verder zodra hij vooruit
   las, en dan blokkeert de hulp precies waar hij zou moeten helpen. Both tokens
   flip with the theme — this used to be hardcoded white, invisible the moment
   a light session reached it (client report, 2026-07-31: "De drie biggetjes"
   read as a near-blank card). */
.ra-word.is-read { color: var(--ra-word-read); }
.ra-word.is-upcoming { color: var(--ra-word-upcoming); }

.record-controls {
  grid-template-areas:
    "story story"
    "toggle toggle"
    "timer timer"
    "controls controls";
  grid-template-rows: minmax(0, 1fr) auto auto auto;
}

.view-toggle { grid-area: toggle; justify-self: center; }

/* Tekst en golfvorm delen ÉÉN cel, zodat ze per definitie even hoog zijn: wisselen
   toont hetzelfde vak, alleen anders weergegeven. */
.story-text-panel,
.wave-view { grid-area: story; }

.wave-view {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 0;
  /* Net als het tekstpaneel: uitrekken tot de rij, anders krimpt de weergave tot
     zijn eigen inhoud en zijn de twee views niet meer even hoog. */
  align-self: stretch;
  height: 100%;
}

.wave-view[hidden] { display: none; }

.wave-area {
  flex: 1;
  min-height: 0;
  height: auto;
}


/* ── Native tekstselectie uit ──────────────────────────────────────────────
   De blauwe OS-selectie en het "Kopiëren/Opzoeken"-menu schieten los bij het scrollen
   door de verhaaltekst en laten de app kapot lijken. Uit dus — behalve waar iemand
   tekst juist MOET kunnen kopiëren of bewerken. */

body {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

/* De claim-link is de handmatige uitweg als de deel-knop faalt; invoervelden
   moeten bewerkbaar blijven; en een technische fout wil je kunnen doorsturen. */
input,
textarea,
.claim-link,
.technical-error {
  -webkit-user-select: text;
  user-select: text;
  -webkit-touch-callout: default;
}


/* ── Weergave-toggle ───────────────────────────────────────────────────────
   Alleen zichtbaar als er tekst is om naartoe te wisselen. */

.view-toggle {
  display: inline-flex;
  gap: 4px;
  padding: 4px;
  border-radius: var(--radius-pill);
  background: var(--toggle-bg);
}

.view-toggle[hidden] { display: none; }

.view-toggle-btn {
  width: 46px;
  height: 34px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--toggle-ink);
  cursor: pointer;
  transition: background 200ms ease, color 200ms ease;
}

.view-toggle-btn.is-active {
  background: var(--flamingo);
  color: #ffffff;
}

/* ── Opname-indicator ──────────────────────────────────────────────────────
   Alleen in de tekstweergave, waar geen golfvorm laat zien dat er iets binnenkomt.
   Ademt mee met het niveau in plaats van blind te knipperen: stilte en een dode
   microfoon zijn zo zichtbaar. */

.editor-timer { display: flex; align-items: center; justify-content: center; gap: 10px; }

.rec-dot {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--flamingo);
  transition: transform 120ms ease-out, opacity 120ms ease-out;
}

.rec-dot[hidden] { display: none; }

/* Zonder niveau-input (of bij reduced motion) blijft het een rustige puls in
   plaats van een sprong per frame. */
@media (prefers-reduced-motion: reduce) {
  .rec-dot { transition: none; }
}

/* Doorgestreept prijs-anker naast de introductieprijs. Bewust gedempt: het
   bedrag dat wordt afgeschreven moet het grootst en donkerst zijn — Apple
   keurt paywalls af waar de marketingprijs de echte overschreeuwt. */
.price-was {
  color: var(--text-muted);
  font-weight: 600;
  margin-right: 2px;
}

/* De rij met wat je zelf typte. Hij LEENT .book-row, dus vorm, hoogte en chevron
   zijn gelijk aan een echte treffer; het verschil is wat er ontbreekt (auteur,
   badge). Geen eigen kleur meer: die gestippelde paarse knop maakte het scherm
   onrustig terwijl dit gewoon een keuze in de lijst is. */
.book-typed[hidden] { display: none; }

/* De echte boekomslag op de bewaarkaart: vullend en bijgesneden, zodat een
   staand omslag hetzelfde vierkant vult als de gegenereerde tegels. */
.pay-cover-photo {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
