/* Hitou US — warm "handcrafted letter" aesthetic (W10). Buildless, no CDN, system fonts only.
   Ivory paper canvas with subtle grain, deep-plum ink, rose accent, gold hairlines, serif heads.
   Compact by design: the whole entry (offer + quiz + companion) fits one screen. Zero added page
   weight — texture is an inline SVG data-URI, the logo flourish is inline SVG, type is system serif. */

:root {
  --paper: #fbf5ef;
  --paper-2: #f3e7dd;
  --card: #ffffff;
  --ink: #2a2024;
  --ink-soft: #6b5b62;
  --rose: #b23a64;
  --rose-deep: #8e2c4f;
  --plum: #6d3b6e;
  --gold: #c9a24b;
  --rec: #7a4a9c;               /* W33.1 — "recommended" chip accent: a third lane, distinct from --rose (.sel) and --gold (.ex badge) */
  --line: #ecdfd5;
  --shadow: 0 16px 44px -24px rgba(64, 24, 44, 0.42);
  --shadow-soft: 0 8px 26px -20px rgba(64, 24, 44, 0.38);
  --radius: 16px;
  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, "Times New Roman", serif;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  /* W19 theming layer — every card design (html[data-design]) overrides a subset of the
     variables above plus these. Components must never hardcode a themed color. */
  --glow: #ffffff;              /* the "lamp": body's radial highlight */
  --on-accent: #ffffff;         /* text/icons sitting on rose/plum fills */
  --font-display: var(--serif); /* headings; designs swap in their webfont */
  --font-body: var(--sans);    /* EVERYTHING but headings; designs swap in their body face (W19.x) */
  --grain-opacity: .5;          /* paper grain visibility (dark designs dial it down) */
  --bg-layer: none;             /* design background image/pattern, layered over the gradient */
  --bg-layer-size: auto;
  --bg-layer-repeat: repeat;
}

/* W19: registering the palette as <color> lets the design swap CROSS-FADE — transitions animate
   registered custom properties, so even gradients/shadows built from them glide. Older browsers
   skip @property and simply switch instantly (fine). Applied only under html.design-anim, which
   gift.js adds on a swatch tap — never on first paint (the recipient must not watch a morph). */
@property --paper   { syntax: "<color>"; inherits: true; initial-value: #fbf5ef; }
@property --paper-2 { syntax: "<color>"; inherits: true; initial-value: #f3e7dd; }
@property --card    { syntax: "<color>"; inherits: true; initial-value: #ffffff; }
@property --ink     { syntax: "<color>"; inherits: true; initial-value: #2a2024; }
@property --ink-soft{ syntax: "<color>"; inherits: true; initial-value: #6b5b62; }
@property --rose    { syntax: "<color>"; inherits: true; initial-value: #b23a64; }
@property --rose-deep { syntax: "<color>"; inherits: true; initial-value: #8e2c4f; }
@property --plum    { syntax: "<color>"; inherits: true; initial-value: #6d3b6e; }
@property --gold    { syntax: "<color>"; inherits: true; initial-value: #c9a24b; }
@property --line    { syntax: "<color>"; inherits: true; initial-value: #ecdfd5; }
@property --glow    { syntax: "<color>"; inherits: true; initial-value: #ffffff; }
@property --on-accent { syntax: "<color>"; inherits: true; initial-value: #ffffff; }
html.design-anim, html.design-anim body {
  transition: --paper .5s ease, --paper-2 .5s ease, --card .5s ease, --ink .5s ease,
    --ink-soft .5s ease, --rose .5s ease, --rose-deep .5s ease, --plum .5s ease,
    --gold .5s ease, --line .5s ease, --glow .5s ease, --on-accent .5s ease;
}

* { box-sizing: border-box; }

/* height on <html> only — base for body's min-height:100%. Putting height:100% on <body> too
   pins the document scrollHeight to the viewport, so content taller than the screen (a long gift
   page with lyrics) can't scroll and gets clipped. min-height lets the body grow with content.
   background-color here is load-bearing, not decoration: without it the body's background
   propagates to the canvas and gets positioned against the html box (= one viewport tall), so the
   radial gradient TILES every screenful on long pages (lyrics review) — a visible seam where the
   "lamp" restarts. An own background on <html> stops the propagation; the body then paints its
   gradient over its full content height. */
html { height: 100%; background-color: var(--paper-2); }

body {
  margin: 0;
  font-family: var(--font-body);
  color: var(--ink);
  background-color: var(--paper);
  /* W19: --bg-layer is the design's pattern/texture slot (none by default) riding OVER the lamp
     gradient. `none` is a valid image layer, so the classic default costs nothing. */
  background-image:
    var(--bg-layer),
    radial-gradient(1100px 560px at 50% -8%, var(--glow) 0%, var(--paper) 46%, var(--paper-2) 100%);
  background-size: var(--bg-layer-size), auto;
  background-repeat: var(--bg-layer-repeat), no-repeat;
  min-height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20px 16px 36px;
}
/* paper grain — inline SVG fractal noise, fixed overlay, near-invisible. Zero network weight. */
body::before {
  content: "";
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  opacity: var(--grain-opacity); mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.035'/%3E%3C/svg%3E");
}
body > * { position: relative; z-index: 1; }

/* ── Brand ───────────────────────────────────────────────────────────── */
.brand { display: flex; align-items: center; gap: 9px; margin-bottom: 14px; color: var(--plum); }
.brand-note { width: 23px; height: 23px; flex: none; }
/* W19: the header SVGs carry colors as presentation ATTRIBUTES (stroke="#6d3b6e") — CSS wins
   over attributes, so designs retheme the logo through the variables, markup untouched. */
.brand-note path { stroke: var(--plum); }
.brand-note circle { stroke: var(--rose); }
.brand-flourish path { stroke: var(--gold); }
.brand-name { position: relative; display: inline-block; }
.brand-name b { font-family: var(--serif); font-size: 21px; font-weight: 600; letter-spacing: 0.2px; }
.brand-flourish { position: absolute; left: -2px; right: -2px; bottom: -6px; width: calc(100% + 4px); height: 8px; }
.brand-tag { color: var(--ink-soft); font-size: 12.5px; font-style: italic; font-family: var(--serif); margin-left: 3px; }

/* ── Shell + offer ───────────────────────────────────────────────────── */
.shell { width: 100%; max-width: 880px; }

.offer { text-align: center; max-width: 600px; margin: 0 auto 16px; }
.offer .promise {
  font-family: var(--serif); font-weight: 600; font-size: 27px; line-height: 1.14;
  margin: 0 0 6px; color: var(--ink); letter-spacing: -0.2px;
}
.offer .promise em { font-style: italic; color: var(--rose-deep); }
.offer .lede { font-size: 14px; line-height: 1.5; color: var(--ink-soft); margin: 0 auto 9px; max-width: 460px; }
.offer .reassure {
  display: inline-flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 7px;
  font-size: 12.5px; font-weight: 600; color: var(--plum); margin: 0;
}
.offer .reassure .dot { color: var(--gold); }

/* ── Funnel layout: quiz + companion rail ────────────────────────────── */
.funnel {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 250px;
  gap: 20px;
  align-items: start;
}

.card {
  width: 100%;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 22px 22px 20px;
  position: relative;
  overflow: hidden;
}
.card::before { /* gold hairline at the very top */
  content: "";
  position: absolute; inset: 0 0 auto 0; height: 3px;
  background: linear-gradient(90deg, var(--rose), var(--gold), var(--plum));
}

/* progress pips */
.pips { display: flex; gap: 7px; margin-bottom: 16px; }
.pip { height: 4px; flex: 1; border-radius: 4px; background: var(--line); transition: background .25s; }
.pip.done { background: var(--rose); }
.pip.active { background: var(--plum); }

.step { display: none; animation: rise .3s ease both; }
.step.active { display: block; }
@keyframes rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

.eyebrow { font-size: 11.5px; letter-spacing: 1.4px; text-transform: uppercase; color: var(--rose-deep); font-weight: 700; }
.step h2 { font-family: var(--serif); font-weight: 600; font-size: 20px; line-height: 1.2; margin: 5px 0 3px; }
.sub { color: var(--ink-soft); font-size: 13.5px; margin: 0 0 15px; }

/* choice chips */
.choices { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.choice {
  font: inherit; text-align: left; cursor: pointer;
  background: var(--paper); border: 1.5px solid var(--line); color: var(--ink);
  border-radius: 12px; padding: 11px 13px; min-height: 48px; display: flex; align-items: center;
  transition: transform .12s, border-color .15s, box-shadow .15s, background .15s;
}
.choice:hover { transform: translateY(-2px); border-color: var(--rose); box-shadow: 0 10px 22px -16px color-mix(in srgb, var(--rose) 60%, transparent); }
.choice.sel { border-color: var(--rose); background: var(--card); box-shadow: 0 0 0 3px color-mix(in srgb, var(--rose) 14%, transparent); }
.choice .ico-emoji { font-size: 17px; line-height: 1; margin-right: 9px; flex: none; }
.choice .lab { font-weight: 600; font-size: 14px; }
.choice .ex { display: inline-block; margin-left: 7px; font-size: 10px; letter-spacing: .4px; color: var(--gold); border: 1px solid var(--gold); border-radius: 6px; padding: 1px 5px; }
.choice.wide { grid-column: 1 / -1; }
.choice.surprise { border-style: dashed; color: var(--plum); }
/* W33.1 — recommended chip: the likely pick for the chosen occasion floats to the top of the
   step and wears this accent. A THIRD visual lane, kept distinct from .sel (rose ring) and the
   .ex badge (gold): --rec border + faint tint + soft ring. Orthogonal to .sel — a chip can be
   both, and when it is, .sel (rose) wins the ring so "chosen" stays unambiguous. */
.choice.rec { border-color: var(--rec);
  background: color-mix(in srgb, var(--rec) 8%, var(--card));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--rec) 16%, transparent); }
.choice.rec:hover { transform: translateY(-2px); border-color: var(--rec);
  box-shadow: 0 10px 22px -16px color-mix(in srgb, var(--rec) 55%, transparent),
              0 0 0 3px color-mix(in srgb, var(--rec) 16%, transparent); }
.choice.rec.sel { border-color: var(--rose);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--rose) 14%, transparent); }
/* W23 freeform occasion: the "Something else" card reveals this inline input + Continue row.
   [hidden] must beat the flex display rule — the display:flex would otherwise resurrect the row. */
.choices .other-row { grid-column: 1 / -1; display: flex; gap: 8px; }
.choices .other-row input { flex: 1; min-width: 0; }
.choices .other-row .btn { flex: none; padding: 10px 16px; white-space: nowrap; }
.choices .other-row[hidden] { display: none !important; }

label.field { display: block; margin: 0 0 11px; }
label.field .cap { display: block; font-size: 12.5px; font-weight: 600; color: var(--ink); margin-bottom: 5px; }
label.field .opt { color: var(--ink-soft); font-weight: 400; }
input[type="text"], input[type="email"], textarea {
  width: 100%; font: inherit; color: var(--ink); background: var(--paper);
  border: 1.5px solid var(--line); border-radius: 11px; padding: 11px 12px; resize: vertical;
}
input:focus, textarea:focus { outline: none; border-color: var(--rose); box-shadow: 0 0 0 3px color-mix(in srgb, var(--rose) 12%, transparent); background: var(--card); }
/* W31.8: server-side 422 on the email field — must also win over :focus (it follows it in source). */
input.field-invalid, input.field-invalid:focus { border-color: var(--rose-deep);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--rose-deep) 14%, transparent); }
/* W19: placeholders follow the theme's muted ink — the UA default grey vanishes on dark designs */
input::placeholder, textarea::placeholder { color: var(--ink-soft); opacity: .65; }
textarea { min-height: 92px; line-height: 1.5; }

/* W24 "I already wrote the lyrics" — a full-width tappable switch row (mobile-first: the whole
   card toggles, 52px+ target), not a bare checkbox. The input stays in the DOM (focus/forms),
   visually replaced by the .knob track. */
.own-toggle { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 2px 0 12px; padding: 12px 14px; min-height: 52px; cursor: pointer;
  background: var(--paper); border: 1.5px solid var(--line); border-radius: 12px;
  font-size: 14px; font-weight: 600; color: var(--ink);
  transition: border-color .18s ease, background .18s ease; }
.own-toggle:has(input:checked) { border-color: var(--rose);
  background: color-mix(in srgb, var(--rose) 5%, var(--paper)); }
.own-toggle input { position: absolute; opacity: 0; pointer-events: none; }
.own-toggle .knob { flex: none; width: 46px; height: 28px; border-radius: 999px;
  background: var(--line); position: relative; transition: background .18s ease; }
.own-toggle .knob::after { content: ""; position: absolute; top: 3px; left: 3px;
  width: 22px; height: 22px; border-radius: 50%; background: var(--card);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .28); transition: transform .18s ease; }
.own-toggle input:checked ~ .knob { background: var(--rose); }
.own-toggle input:checked ~ .knob::after { transform: translateX(18px); }
.own-toggle input:focus-visible ~ .knob {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--rose) 22%, transparent); }
/* The story/lyrics box autogrows with the text (JS, scrollHeight) — no inner scroll. */
.story-field textarea { overflow-y: hidden; resize: none; }
/* Own mode on: the lyrics box carries the same accent as the switched-on row — it IS the mode. */
.story-field.own-active textarea { border-color: var(--rose);
  background: color-mix(in srgb, var(--rose) 5%, var(--paper)); }
.story-field.own-active .cap { color: var(--rose-deep); }
/* Voice narrates a story — with finished lyrics it's off (class-gated: voice.js owns [hidden]) */
.voice.own-hidden { display: none !important; }
.char-note { display: block; text-align: right; font-size: 11.5px; color: var(--ink-soft); margin-top: 4px; }
.char-note[hidden] { display: none; }

/* W46 review-screen editing (shared by the wizard's inline review and the gift page): the lyrics
   ARE the editor — a textarea dressed as the reading panel it replaced, so nothing shifts when the
   caret lands in it. Autogrows with the text (JS sets height off scrollHeight) — no inner scroll,
   no resize handle to fight the script. The frame/padding come from the per-surface .lyrics rules. */
textarea.lyrics { display: block; width: 100%; min-height: 200px; overflow-y: hidden; resize: none; }
/* 16px is not a taste call: iOS Safari zooms the page whenever a focused field is smaller, and
   this field is meant to be tapped on every visit. Beats the 14.5px panel rule on specificity. */
.card.review textarea.lyrics,
.gstate[data-state="lyrics_review"] textarea.lyrics { font-size: 16px; }
/* The panel's own border out-specifies the shared input:focus rule — restate the ring here, or
   tapping the text gives no sign that it just became editable. */
.card.review textarea.lyrics:focus,
.gstate[data-state="lyrics_review"] textarea.lyrics:focus {
  border-color: var(--rose); box-shadow: 0 0 0 3px color-mix(in srgb, var(--rose) 12%, transparent);
}
/* The line under the box: the affordance hint, the save state (this IS the save confirmation —
   there is no Save button) and the restore link. Pulled up snug under the panel. */
.lyr-meta { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap;
  gap: 6px 12px; margin: -10px 0 16px; min-height: 22px; text-align: left; }
.lyr-hint, .lyr-save { font-size: 12.5px; line-height: 1.4; color: var(--ink-soft); }
.lyr-save { font-weight: 600; color: var(--plum); }
.lyr-save.warn { color: var(--rose-deep); }
/* Pinned right: the row's contents come and go (the hint retires after the first edit, the save
   state appears with it), and with space-between alone the link walked from one end to the other
   as they did — a moving target under the reader's thumb (Timur, 25.07). */
.lyr-restore { margin-left: auto; font: inherit; font-size: 12.5px; cursor: pointer; background: none; border: none;
  color: var(--ink-soft); text-decoration: underline; text-underline-offset: 2px;
  padding: 8px 2px; min-height: 36px; }
.lyr-restore:hover { color: var(--rose-deep); }
.lyr-restore:disabled { opacity: .5; cursor: default; }
.lyr-hint[hidden], .lyr-save[hidden], .lyr-restore[hidden] { display: none !important; }
/* W41.0 live draft. What replaced the invented progress curve: the H2 says what is happening and
   this line carries the detail — both are the panel's OWN type (.sub), not a new visual element.
   The slow breathe is the only motion left standing in for the bar; the arriving text takes over
   as the real progress signal the moment there is any. */
.stage-sub { margin: 2px 0 14px; min-height: 20px; animation: stage-breathe 2.6s ease-in-out infinite; }
.stage-sub[hidden] { display: none !important; }
@keyframes stage-breathe { 0%, 100% { opacity: .68; } 50% { opacity: 1; } }
/* While the song is being revealed the box is a display, not an input: no caret, no focus ring,
   nothing that invites typing into text that is about to be replaced by the finished draft. */
textarea.lyrics.streaming { caret-color: transparent; cursor: default; }
.card.review textarea.lyrics.streaming:focus,
.gstate[data-state="lyrics_review"] textarea.lyrics.streaming:focus {
  border-color: var(--line); box-shadow: none;
}
@media (prefers-reduced-motion: reduce) { .stage-line { animation: none; opacity: 1; } }
.lyr-meta .char-note { flex: 1 0 100%; margin-top: 0; }

.nav { display: flex; align-items: center; justify-content: space-between; margin-top: 16px; }
.back { background: none; border: none; color: var(--ink-soft); font: inherit; cursor: pointer;
  padding: 8px 6px; min-height: 48px; display: inline-flex; align-items: center; }
.back:hover { color: var(--ink); }
.back[hidden] { visibility: hidden; }

.btn {
  font: inherit; font-weight: 700; cursor: pointer; color: var(--on-accent);
  background: linear-gradient(135deg, var(--rose), var(--rose-deep));
  border: none; border-radius: 12px; padding: 12px 20px; min-height: 48px; letter-spacing: .2px;
  box-shadow: 0 12px 26px -14px color-mix(in srgb, var(--rose-deep) 80%, transparent); transition: transform .12s, opacity .15s;
}
.btn:hover { transform: translateY(-1px); }
.btn:disabled { opacity: .55; cursor: progress; transform: none; }
/* W6.2: .btn on a link (the expired screen's create-again CTA) — <a> is inline and underlined
   by default, which breaks the button metrics the class assumes on <button>. */
a.btn { display: inline-flex; align-items: center; justify-content: center; text-decoration: none; }
/* W32.5b: #pay-fallback upgrades to a .btn INSIDE p.note — without these overrides `.note a`
   wins the ink (rose-on-rose, unreadable) and .note's 13px shrinks the CTA. */
.note a.btn { color: var(--on-accent); font-size: 1rem; }
.note a.btn:hover { color: var(--on-accent); }

.err { color: var(--rose-deep); font-size: 13px; margin-top: 10px; min-height: 16px; }

/* ── Companion rail (dynamic helper, filled per step by app.js) ──────── */
.rail { display: flex; flex-direction: column; gap: 12px; }
.helper {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-soft); padding: 16px 16px 15px;
}
.helper h3 { font-family: var(--serif); font-weight: 600; font-size: 15.5px; margin: 0 0 7px; color: var(--ink); line-height: 1.25; }
.helper p { font-size: 12.5px; line-height: 1.5; color: var(--ink-soft); margin: 7px 0 0; }
.help-list { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 6px; }
.help-list li { position: relative; padding-left: 18px; font-size: 12.5px; line-height: 1.35; color: var(--ink); }
.help-list li::before {
  content: ""; position: absolute; left: 3px; top: 6px; width: 6px; height: 6px;
  border-radius: 50%; background: var(--rose);
}

/* "Your song so far" — picks summary inside the helper, clickable to jump back to a step */
.picks-cap { font-size: 10.5px; letter-spacing: 1px; text-transform: uppercase; color: var(--ink-soft); font-weight: 700; margin-bottom: 8px; }
.picks-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.pick {
  width: 100%; display: flex; align-items: center; gap: 8px; cursor: pointer; min-height: 48px;
  font: inherit; text-align: left; background: var(--paper); border: 1px solid var(--line);
  border-radius: 10px; padding: 7px 10px; transition: border-color .15s, background .15s;
}
.pick:hover { border-color: var(--rose); background: var(--card); }
.pk-emoji { font-size: 14px; line-height: 1; flex: none; }
.pk-lab { font-size: 12.5px; font-weight: 600; color: var(--ink); flex: 1; }
.pk-edit { font-size: 10px; letter-spacing: .4px; text-transform: uppercase; color: var(--rose-deep); font-weight: 700; opacity: .5; transition: opacity .15s; }
.pick:hover .pk-edit { opacity: 1; }
.picks + .help-ctx { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line); }

.foot { color: var(--ink-soft); font-size: 12px; margin-top: 18px; text-align: center; max-width: 560px; }
/* W4.7 follow-up: method hint under the buy CTA (gift page) — mirrors .foot's muted voice */
.pay-hint { color: var(--ink-soft); font-size: 12.5px; margin: 8px 0 0; text-align: center; }

/* W6.1 legal links — under the foot on both pages, muted so they never compete with the CTA */
.legal-links { color: var(--ink-soft); font-size: 11.5px; margin: 6px 0 0; text-align: center; opacity: .85; }
.legal-links a { color: var(--ink-soft); text-decoration: underline; text-underline-offset: 2px; }
#legal-refund[hidden] { display: none !important; }

/* ── Responsive ──────────────────────────────────────────────────────── */
@media (max-width: 780px) {
  .funnel { grid-template-columns: 1fr; gap: 14px; }
  /* helper stays BELOW the quiz on mobile (source order): the quiz is the conversion surface and
     must not be pushed down — the companion is a bonus for those who scroll (Timur, session 9). */
  .offer .promise { font-size: 25px; }
}
@media (max-width: 480px) {
  body { padding: 16px 13px 34px; }
  .offer { margin-bottom: 12px; }
  .offer .promise { font-size: 23px; }
  .offer .lede { font-size: 13.5px; }
  .choices { grid-template-columns: 1fr; }
  .card { padding: 18px 16px 16px; }
}

/* ════════════════════════════════════════════════════════════════════════
   GIFT PAGE (/m/{token}) — W3.2/W4. Restyled to warm DNA in W10 iteration 3.
   ════════════════════════════════════════════════════════════════════════ */
.gift { text-align: center; padding: 34px 26px 28px; max-width: 560px; margin: 0 auto; }
.gift .spin { width: 44px; height: 44px; margin: 4px auto 16px; border-radius: 50%;
  border: 3px solid var(--line); border-top-color: var(--rose); animation: turn 1s linear infinite; }
@keyframes turn { to { transform: rotate(360deg); } }

/* W11.E — perceived-progress bar on the real waits (quiz writing/updating + gift generating).
   Unscoped: it lives on both the gift page and the wizard's review card. Thinner than the
   player's 8px seek line and deliberately number-free; progress.js drives .pg-fill width. */
.pg { position: relative; height: 6px; max-width: 320px; margin: 10px auto 18px;
  border-radius: 6px; background: var(--paper-2); border: 1px solid var(--line); overflow: hidden; }
.pg-fill { position: absolute; left: 0; top: 0; bottom: 0; width: 0%; border-radius: 6px;
  background: linear-gradient(90deg, var(--rose), var(--gold)); transition: width .35s linear; }
.gift h1 { font-family: var(--font-display); font-weight: 600; font-size: 24px; line-height: 1.2; margin: 6px 0 4px; }
.gift code { background: var(--paper); border: 1px solid var(--line); border-radius: 7px; padding: 3px 8px; font-size: 12.5px; color: var(--ink-soft); word-break: break-all; }

/* `hidden` defense for the gift state toggles — scoped so a future display rule on these can't
   override the attribute (the wizard's .back[hidden]{visibility:hidden} layout is left intact). */
.gstate[hidden], .lyrics-wrap[hidden], .note[hidden], .player-err[hidden],
.dedication[hidden], .note-box[hidden], .foot[hidden], .viral-foot[hidden] { display: none !important; }

.linkline { display: flex; align-items: center; justify-content: center; gap: 8px; flex-wrap: wrap; }

/* W32.5 mandatory email gate (supersedes the round-3 nudge): a full-viewport overlay over
   generating/demo_ready while the order has no email. The backdrop is DELIBERATELY
   semi-transparent — the progress bar / lyrics / demo stay dimly visible behind it ("we're
   working, the song is real"), just not clickable (the overlay eats the pointer events). */
#email-gate {
  position: fixed; inset: 0; z-index: 70;
  display: flex; align-items: center; justify-content: center; padding: 24px;
  background: rgba(61, 43, 51, .38);
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
}
#email-gate[hidden] { display: none !important; }
.eg-card {
  position: relative; width: 100%; max-width: 400px; overflow: hidden;
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
  padding: 26px; text-align: center;
  box-shadow: 0 30px 60px -30px rgba(61, 43, 51, .35);
}
.eg-card::before { /* gold hairline, same signature as .card */
  content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: linear-gradient(90deg, transparent, var(--gold), transparent);
}
.eg-card h2 { font-family: var(--serif); font-size: 22px; line-height: 1.25; margin: 8px 0 6px; color: var(--ink); }
.eg-row { display: flex; gap: 8px; margin-top: 14px; }
.eg-row input { flex: 1; min-width: 0; }
.eg-row .btn { flex: none; padding: 10px 16px; white-space: nowrap; }
@media (max-width: 480px) {
  .eg-row { flex-direction: column; }
}
.linkcopy { font: inherit; font-size: 12.5px; cursor: pointer; color: var(--rose-deep);
  background: none; border: 1px solid var(--line); border-radius: 7px; padding: 3px 12px;
  min-height: 44px; }
.linkcopy:hover { border-color: var(--rose); }

/* Custom warm player (W11.C1 / feedback П4): a big tappable play button + a rose progress line.
   The <audio> has no `controls` and renders nothing; all UI state is driven by audio events in
   gift.js (M1 — the button/line always reflect true playback state, never an assumed one). */
.player { display: flex; align-items: center; gap: 14px; margin: 16px 0 8px; text-align: left; }
.p-play {
  width: 58px; height: 58px; border-radius: 50%; border: none; cursor: pointer; flex: none;
  background: linear-gradient(135deg, var(--rose), var(--rose-deep)); color: var(--on-accent);
  box-shadow: 0 12px 26px -14px color-mix(in srgb, var(--rose-deep) 80%, transparent);
  display: flex; align-items: center; justify-content: center;
  transition: transform .12s, opacity .15s;
}
.p-play:hover { transform: scale(1.05); }
.p-play:active { transform: scale(.97); }
.p-play svg { width: 24px; height: 24px; display: block; }
.p-play .ic-play { margin-left: 3px; }        /* optical centering of the triangle */
.p-play .ic-pause { display: none; }
.p-play.playing .ic-play { display: none; }
.p-play.playing .ic-pause { display: block; }
.p-track { flex: 1; min-width: 0; }
.p-seek { position: relative; height: 8px; border-radius: 6px; background: var(--paper-2);
  border: 1px solid var(--line); cursor: pointer; }
.p-seek::before { content: ""; position: absolute; inset: -20px 0; }  /* widen the tap zone to 48px */
.p-fill { position: absolute; left: 0; top: 0; bottom: 0; width: 0%; border-radius: 6px;
  background: linear-gradient(90deg, var(--rose), var(--gold)); pointer-events: none; }
.p-time { display: flex; justify-content: space-between; font-size: 12px; color: var(--ink-soft);
  margin-top: 7px; font-variant-numeric: tabular-nums; }
.player-err { color: var(--rose-deep); font-size: 13px; margin: 6px 0 0; }

.cta-row { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; margin-top: 16px; }
.ghost { font: inherit; font-weight: 600; cursor: pointer; color: var(--plum); min-height: 48px;
  background: var(--card); border: 1.5px solid var(--line); border-radius: 12px; padding: 12px 20px; transition: border-color .15s; }
.ghost:hover { border-color: var(--rose); }
/* W4.8 PayPal rail button — brand color on the word, [hidden] defense (shown via JS flag) */
.paypal-btn b { color: #003087; }
.paypal-btn[hidden] { display: none !important; }
.paypal-btn:disabled { opacity: .55; cursor: progress; }
.note { color: var(--ink-soft); font-size: 13px; margin: 12px 0 0; }
.note a { color: var(--rose-deep); text-underline-offset: 2px; }
.note a:hover { color: var(--rose); }
/* W6.2 consent notice — the quiet legal line under both email collection points (wizard field +
   generating nudge). Muted on purpose: it clarifies the consent act, it doesn't sell anything. */
.consent-note { display: block; color: var(--ink-soft); font-size: 12px; line-height: 1.45; margin: 6px 0 0; font-weight: 400; }
.consent-note a { color: var(--ink-soft); text-decoration: underline; text-underline-offset: 2px; }
.consent-note a:hover { color: var(--rose-deep); }

.lyrics-wrap { text-align: left; margin-top: 24px; padding-top: 16px; border-top: 1px solid var(--line); }
.lyrics { font-family: var(--font-display); white-space: pre-wrap; word-wrap: break-word; line-height: 1.6;
  font-size: 15px; color: var(--ink); margin: 8px 0 0; }

/* W14 deal timer — the launch price against the anchor, right above the CTA. Firm, not loud:
   struck anchor + serif price + a ticking clock line in the muted ink. */
.deal { display: flex; align-items: baseline; justify-content: center; gap: 10px; flex-wrap: wrap;
  margin: 16px 0 0; }
.deal[hidden] { display: none !important; }
.deal-old { color: var(--ink-soft); text-decoration: line-through; font-size: 15px; }
.deal-new { font-family: var(--font-display); font-weight: 700; font-size: 23px; color: var(--rose-deep); }
.deal-clock { font-size: 12.5px; color: var(--ink-soft); }
.deal-clock b { color: var(--rose-deep); font-variant-numeric: tabular-nums; }
/* W32.3(a): the saving as a soft pill — firm, not loud, matching the deal block's tone. */
.deal-save { font-size: 12.5px; font-weight: 600; color: var(--rose-deep); white-space: nowrap;
  background: color-mix(in srgb, var(--rose) 12%, transparent); border-radius: 999px; padding: 2px 9px; }
.deal-save[hidden] { display: none !important; }

/* W18: the preview→offer bridge — a warm serif call between the player and the value list */
.demo-hook { font-family: var(--font-display); font-size: 19px; line-height: 1.3; color: var(--rose-deep);
  text-align: center; margin: 16px 0 2px; }
/* П5 value block (demo_ready): what the $19 unlocks, ahead of the CTA */
.value-list { text-align: left; max-width: 400px; margin: 18px auto 4px; }
.vcap { font-size: 10.5px; letter-spacing: 1px; text-transform: uppercase; color: var(--ink-soft);
  font-weight: 700; margin-bottom: 9px; text-align: center; }
.value-list ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.value-list li { display: flex; gap: 10px; align-items: flex-start; font-size: 13.5px; line-height: 1.45; color: var(--ink); }
.value-list .vi { flex: none; line-height: 1.45; }
/* W18: the journey strip — buying is step 1 of 3, the page sells the wrap-and-send moment.
   Its own heading (.steps-cap) sets it apart from the value bullets above. */
.steps-cap { margin-top: 20px; }
.steps { text-align: left; max-width: 400px; margin: 0 auto; display: flex; flex-direction: column; gap: 7px; }
/* SCOPED to .steps — a bare `.step` collides with the wizard's own `.step { display:none }`
   (styles.css:112). Same specificity, later rule wins → the wizard showed every step at once.
   The journey strip's steps only ever live inside .steps, so scoping fixes it without touching
   the wizard. (Bug shipped in W18; found on prod 2026-07-04.) */
.steps .step { display: flex; gap: 10px; align-items: flex-start; font-size: 13px; line-height: 1.45; color: var(--ink-soft); }
.step-num { flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: 50%; margin-top: 1px;
  background: color-mix(in srgb, var(--rose) 12%, transparent); color: var(--rose-deep); font-size: 11px; font-weight: 700; }
.imagine { font-family: var(--serif); font-style: italic; font-size: 13.5px; color: var(--rose-deep); margin: 10px 0 0; }
/* W18: price certainty + price privacy under the CTA (replaced the free-preview guarantee line) */
.trust-line { color: var(--ink-soft); font-size: 12px; margin: 9px 0 0; }
/* W32.3(c): pre-purchase guarantee — a touch more present than the trust line (it answers the
   "what if the full song is wrong" fear), still muted. */
.guarantee { color: var(--ink-soft); font-size: 12.5px; line-height: 1.45; margin: 12px 0 0; }
/* W34: the promo-code field — a quiet collapsed link so the main buy path stays clean;
   states (form / applied badge / error) are toggled by gift.js. [hidden] guards mirror the
   preview-more pattern (a component display rule must never resurrect a hidden element). */
.promo-wrap { margin: 10px 0 0; text-align: center; }
.promo-toggle { background: none; border: none; padding: 0; cursor: pointer;
  color: var(--ink-soft); font-size: 12.5px; text-decoration: underline dotted; }
.promo-toggle:hover { color: var(--rose-deep); }
.promo-form { display: flex; gap: 8px; justify-content: center; margin: 8px 0 0; }
.promo-form[hidden] { display: none !important; }
.promo-input { width: 150px; padding: 8px 10px; border: 1px solid color-mix(in srgb, var(--rose) 35%, transparent);
  border-radius: 8px; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 13px;
  letter-spacing: 1px; text-transform: uppercase; }
.promo-apply { padding: 8px 14px; border: none; border-radius: 8px; cursor: pointer;
  background: color-mix(in srgb, var(--rose) 14%, transparent); color: var(--rose-deep);
  font-size: 13px; font-weight: 700; }
.promo-apply:disabled { opacity: .6; cursor: default; }
.promo-msg { font-size: 12.5px; margin: 7px 0 0; }
.promo-msg.ok { color: var(--rose-deep); font-weight: 700; }
.promo-msg.err { color: #a33; }
.promo-msg[hidden] { display: none !important; }
/* W18: the preview-ended nudge — revealed by gift.js on the audio `ended` event */
.preview-more { color: var(--rose-deep); font-size: 13px; margin: 9px 0 0; }
.preview-more[hidden] { display: none !important; }
.btn.pulse { animation: hitou-pulse 1.5s ease-in-out 3; }
@keyframes hitou-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--rose) 40%, transparent); }
  50% { box-shadow: 0 0 0 9px color-mix(in srgb, var(--rose) 0%, transparent); }
}
/* W18: Apple Pay express button host (Safari only; gift.js injects the Whop custom element) */
.express-wrap { margin: 14px 0 0; }
.express-wrap[hidden] { display: none !important; }

/* trust row (W11.C): official payment marks under the CTA — present, not shouting.
   Soft desaturation keeps them inside the warm palette; the PayPal 2024 wordmark is
   monochrome by design and inherits a muted ink via currentColor. */
.pay-row { display: flex; align-items: center; justify-content: center; flex-wrap: wrap;
  gap: 15px; margin: 13px 0 0; }
.pay-row .pi { height: 15px; width: auto; display: block; filter: grayscale(.35); opacity: .7; }
.pay-row .pi[hidden] { display: none !important; }   /* W49: display:block above would beat [hidden] */
.pay-row .pi-amex { height: 20px; }   /* square badge — optical compensation next to wordmarks */
.pay-row .pi-pp, .pay-row .pi-ap { color: var(--ink-soft); }   /* monochrome wordmarks (PayPal, Apple Pay) share the muted ink */

/* W11.B dedication — the giver's note. Recipient view: a warm hand-written card line.
   W15.5: pre-wrap lives ONLY on the user-text carriers (#note-view is a <p> with bare text;
   .d-text holds the note inside the sealed card). On the sealed-card CONTAINER (a div with
   nested <p>s) pre-wrap rendered the markup's own inter-tag newlines as phantom blank lines —
   the "huge padding" on the recipient's card. */
.dedication { font-family: var(--font-display); font-style: italic; font-size: 16px; line-height: 1.6;
  color: var(--ink); background: var(--paper); border: 1px solid var(--line); border-radius: 12px;
  padding: 14px 16px; margin: 12px 0 14px; text-align: left; word-wrap: break-word; }
#note-view, .d-text { white-space: pre-wrap; }
/* W13.2 — the sealed card splits into text + signature ("— Timur", right-aligned) */
.d-text { margin: 0; }
.d-from { margin: 8px 0 0; text-align: right; color: var(--ink-soft); }
.d-text[hidden], .d-from[hidden] { display: none !important; }
/* the "from" input sits ABOVE the note textarea (W13.3), saved by the same button */
#note-from { margin-bottom: 8px; }
/* Owner editor (shown only with the localStorage owner flag) */
.note-box { text-align: left; margin-top: 22px; padding-top: 16px; border-top: 1px solid var(--line); }
.note-box .sub { margin-bottom: 10px; }
/* W15.3 (П5): the textarea grows with the text — JS drives height off scrollHeight on input;
   overflow-y:hidden kills the inner scrollbar, resize:none the (now pointless) manual handle.
   min-height stays the floor. The gold .saved frame touches border/background only — no
   height interplay with the auto-grow. */
.note-box textarea { min-height: 88px; overflow-y: hidden; resize: none;
  transition: border-color .2s, box-shadow .2s, background .2s; }
/* round 3, П3: the saved note is visibly "fixed" — gold frame, warm fill. Editing lifts it. */
.note-box textarea.saved { border-color: var(--gold); background: color-mix(in srgb, var(--gold) 7%, var(--card));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--gold) 16%, transparent); }
/* W15.3 (П6): the meta line — counter left, whisper right (its margin-left:auto keeps a lone
   whisper pinned right while the counter hides under 400 chars); the AI button centers alone. */
.note-meta { display: flex; gap: 10px; align-items: baseline; margin-top: 6px; min-height: 18px; }
.note-counter { font-size: 12px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.note-counter.warn { color: var(--rose-deep); font-weight: 700; }
.note-counter[hidden] { display: none !important; }
.note-actions { display: flex; gap: 10px; margin-top: 8px; flex-wrap: wrap; justify-content: center; }
.note-actions .btn, .note-actions .ghost { padding: 10px 16px; }

/* gift page payment states (W4): inline Whop embed + paid download buttons */
#whop-embed { margin: 8px 0 2px; min-height: 300px; }
.downloads { display: flex; flex-direction: column; gap: 10px; margin: 12px 0 4px; }
/* .dl is an <a>, not a <button> — flex-center the label so min-height:48 keeps it vertically centered */
.downloads .dl { text-decoration: none; text-align: center; display: flex; align-items: center; justify-content: center; }
.gstate[data-state="paid"] .eyebrow { color: var(--gold); }

/* viral foot (W8.1 / W11.C): on demo_ready + paid the plain reassurance foot swaps for a warm
   invitation back to the wizard — the gift page is the product's own best ad. */
.viral-foot { margin-top: 16px; text-align: center; }
.vf-link { display: inline-flex; align-items: center; justify-content: center; flex-wrap: wrap;
  gap: 8px; min-height: 48px; padding: 4px 12px; color: var(--ink-soft);
  text-decoration: none; font-size: 13px; }
.vf-note { width: 18px; height: 18px; flex: none; color: var(--plum); }
.vf-text b { font-family: var(--serif); color: var(--plum); letter-spacing: .2px; }
.vf-cta { color: var(--rose-deep); font-weight: 700; white-space: nowrap;
  border-bottom: 1px solid color-mix(in srgb, var(--rose) 35%, transparent); transition: color .15s, border-color .15s; }
.vf-link:hover .vf-cta { color: var(--rose); border-color: var(--rose); }

/* ════════════════════════════════════════════════════════════════════════
   W13 — prepare (the giver's wrapping table) + sealed (the recipient's gift)
   ════════════════════════════════════════════════════════════════════════ */
/* hidden-defense for every W13 toggle (same discipline as the .gstate list above) */
#envelope[hidden], #prep-main[hidden], #seal-open[hidden],
.variant-picker[hidden], .seal-confirm[hidden], .seal-done[hidden],
.player-box[hidden], .lyrics-fold[hidden], .dl-link[hidden],
#wstep-design[hidden], #design-err[hidden],
#demo-variant-wrap[hidden] { display: none !important; }

/* two-demo picker (pre-pay): reuses .variant-picker/.vp-btn; the copy line sits tight under it */
.demo-vp-note { margin: 6px 0 10px; font-size: 12.5px; }

.gstate[data-state="prepare"] .eyebrow, .gstate[data-state="sealed"] .eyebrow { color: var(--gold); }

/* take picker — segmented pair in the .choice.sel voice; ≥48px tap targets (W11.D) */
.variant-picker { display: flex; gap: 8px; margin: 16px 0 4px; }
.vp-btn {
  flex: 1; min-height: 56px; cursor: pointer; font: inherit; font-weight: 600; font-size: 13.5px;
  color: var(--ink); background: var(--paper); border: 1.5px solid var(--line); border-radius: 12px;
  padding: 8px 10px; transition: border-color .15s, background .15s, box-shadow .15s;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
}
.vp-btn:hover { border-color: var(--rose); }
.vp-btn.sel { border-color: var(--rose); background: var(--card); box-shadow: 0 0 0 3px color-mix(in srgb, var(--rose) 14%, transparent); }
.vp-tag { font-size: 10px; letter-spacing: .4px; text-transform: uppercase; color: var(--ink-soft); font-weight: 700; }
.vp-btn.sel .vp-tag { color: var(--rose-deep); }

/* ghost download links (<a>, not <button>) — flex-center so min-height 48 stays centered */
.dl-link { text-decoration: none; display: flex; align-items: center; justify-content: center; text-align: center; }

/* seal: button → inline confirm → the sending pane */
.seal-block { margin-top: 20px; padding-top: 18px; border-top: 1px solid var(--line); }
.seal-confirm { margin-top: 2px; }
.sc-warn { color: var(--ink); font-size: 13.5px; line-height: 1.55; max-width: 400px; margin: 0 auto 12px; }
.seal-done .linkline { margin: 14px 0 0; }

/* collapsed lyrics on the sealed page — native <details>, styled to the warm shell */
.lyrics-fold { text-align: left; margin: 18px 0 2px; border: 1px solid var(--line);
  border-radius: 12px; background: var(--paper); }
.lyrics-fold summary { list-style: none; cursor: pointer; min-height: 48px; display: flex;
  align-items: center; justify-content: center; gap: 7px; padding: 10px 14px;
  font-weight: 600; color: var(--plum); font-size: 13.5px; }
.lyrics-fold summary::-webkit-details-marker { display: none; }
.lf-chev { width: 16px; height: 16px; flex: none; transition: transform .2s; }
.lyrics-fold[open] .lf-chev { transform: rotate(180deg); }
.lyrics-fold .lyrics { padding: 0 16px 14px; margin: 0; }

/* ── envelope overlay: the unwrapping moment ──────────────────────────────
   W13.2 slow choreography (~3.0s, one .opening class, cascaded transition-delays; the music
   starts ON the tap, so the whole reveal plays under the first bars):
     0.00-0.25  the Open button melts away
     0.10-0.70  the wax seal peels off (scale + slight rotate + fade)
     0.35-1.85  the flap folds up — rotateX 1.5s. NB: backface-visibility hides it past 90°,
                so the VISIBLE fold is the first half of progress; the slow-START curve
                (cubic-bezier(.6,.05,.4,.95)) spends ~55% of the duration there (plan-critic 🔧2).
                Keep backface-visibility: the flap's backside past 90° would poke out of the
                card's overflow:hidden crop.
     0.90-2.10  the envelope's face (eyebrow + name) lifts and dissolves
     1.50-3.00  the overlay dissolves; the card floats up — the gift page emerges beneath.
   The overlay keeps receiving pointer events while opening (NO pointer-events:none — an
   impatient double-tap must not fall through the still-opaque envelope into the player or a
   download link, plan-critic 🔧1); the JS guards re-entry instead. */
#envelope {
  position: fixed; inset: 0; z-index: 60;
  display: flex; align-items: center; justify-content: center; padding: 24px;
  /* W19: the design's pattern rides over the lamp here too — the unwrap moment IS the
     wrapping paper (classic's --bg-layer is none, so the default stays untouched) */
  background:
    var(--bg-layer),
    radial-gradient(900px 520px at 50% -6%, var(--glow) 0%, var(--paper) 48%, var(--paper-2) 100%);
  background-size: var(--bg-layer-size), auto;
  background-repeat: var(--bg-layer-repeat), no-repeat;
  transition: opacity 1.5s ease 1.5s;
}
#envelope.opening { opacity: 0; }
.env-card {
  position: relative; width: 100%; max-width: 400px; overflow: hidden;
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
  /* top padding clears the flap (84px) PLUS the seal's lower half (27px) — the wax must never
     sit on the eyebrow text */
  padding: 122px 26px 30px; text-align: center;
  box-shadow: 0 30px 60px -30px rgba(61, 43, 51, .35);
  perspective: 700px;
  transition: transform 1.5s ease 1.5s;
}
#envelope.opening .env-card { transform: translateY(-16px) scale(1.02); }
.env-card::before { /* gold hairline, same signature as .card */
  content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: linear-gradient(90deg, transparent, var(--gold), transparent);
}
.env-flap {
  position: absolute; top: 0; left: 0; right: 0; height: 84px; z-index: 1;
  background: linear-gradient(180deg, var(--paper-2), var(--paper));
  border-bottom: 1px solid var(--line);
  clip-path: polygon(0 0, 100% 0, 50% 100%);
  transform-origin: top; backface-visibility: hidden;
  transition: transform 1.5s cubic-bezier(.6, .05, .4, .95) .35s;
}
#envelope.opening .env-flap { transform: rotateX(-180deg); }
.env-seal {
  position: absolute; top: 84px; left: 50%; transform: translate(-50%, -50%);
  width: 54px; height: 54px; border-radius: 50%; z-index: 2;
  background: radial-gradient(circle at 35% 30%,
    color-mix(in srgb, var(--plum) 78%, #ffffff), var(--plum) 58%,
    color-mix(in srgb, var(--plum) 68%, #000000));
  color: var(--gold); font-size: 22px; line-height: 54px; font-family: var(--serif);
  box-shadow: 0 8px 18px -8px color-mix(in srgb, var(--plum) 70%, transparent),
    inset 0 0 0 2px color-mix(in srgb, var(--gold) 45%, transparent);
  transition: opacity .6s ease .1s, transform .6s ease .1s;
}
#envelope.opening .env-seal { opacity: 0; transform: translate(-50%, -50%) scale(1.18) rotate(-8deg); }
.env-body { transition: opacity 1.2s ease .9s, transform 1.2s ease .9s; }
#envelope.opening .env-body { opacity: 0; transform: translateY(-14px); }
.env-eyebrow { font-size: 11.5px; letter-spacing: 1.4px; text-transform: uppercase;
  color: var(--rose-deep); font-weight: 700; margin: 6px 0 0; }
.env-name { font-family: var(--font-display); font-weight: 600; font-size: 27px; line-height: 1.2;
  margin: 8px 0 0; color: var(--ink); }
.env-open { margin-top: 18px; transition: opacity .25s ease, transform .25s ease; }
#envelope.opening .env-open { opacity: 0; transform: translateY(6px); pointer-events: none; }
@media (max-width: 480px) {
  .env-name { font-size: 23px; }
  .env-card { padding: 116px 20px 26px; }
}
@media (prefers-reduced-motion: reduce) {
  #envelope, .env-flap, .env-seal, .env-body, .env-card, .env-open, .lf-chev {
    transition: none !important;
  }
  /* W19: the design swap snaps instead of cross-fading */
  html.design-anim, html.design-anim body { transition: none !important; }
}

/* voice brief (W4): record-the-story affordance — glued right under the story textarea (П3) */
.voice { margin: -4px 0 11px; }
.story-field { margin-bottom: 7px; }
.voice[hidden], .voice-live[hidden], .voice-note[hidden] { display: none !important; }
.voice-btn { font: inherit; font-size: 13px; font-weight: 600; cursor: pointer; color: var(--plum);
  background: var(--paper); border: 1.5px dashed var(--line); border-radius: 11px; padding: 10px 14px;
  width: 100%; transition: border-color .15s; }
.voice-btn:hover { border-color: var(--rose); }
.voice-btn:disabled { opacity: .6; cursor: progress; }
.voice-live { display: flex; align-items: center; gap: 10px; padding: 10px 14px;
  background: var(--paper); border: 1.5px solid var(--rose); border-radius: 11px; }
.voice-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--rose); animation: pulse 1s infinite; }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }
.voice-timer { font-variant-numeric: tabular-nums; color: var(--ink); font-weight: 600; }
.voice-stop { margin-left: auto; font: inherit; font-size: 13px; cursor: pointer; color: var(--on-accent);
  background: var(--rose); border: none; border-radius: 9px; padding: 7px 16px; min-height: 44px; }
.voice-note { font-size: 12.5px; margin: 8px 2px 0; color: var(--ink-soft); }
.voice-note.warn { color: var(--rose-deep); }
.voice-note.ok { color: var(--plum); }

/* ════════════════════════════════════════════════════════════════════════
   LYRICS REVIEW (W11) — approve / iterate the lyrics inside the wizard, before the paid render.
   Reuses .card / .spin / .eyebrow / .sub / .lyrics / .btn / .ghost / .note / label.field.
   ════════════════════════════════════════════════════════════════════════ */
.review-shell { width: 100%; max-width: 620px; }
.review-shell[hidden] { display: none !important; }
.rstate[hidden] { display: none !important; }
.card.review { text-align: center; }
.card.review .spin { margin: 4px auto 14px; }
.card.review h2 { font-family: var(--serif); font-weight: 600; font-size: 22px; line-height: 1.25; margin: 6px 0 4px; overflow-wrap: break-word; }
.card.review .preamble { font-family: var(--serif); font-style: italic; color: var(--rose-deep); font-size: 15px; line-height: 1.5; margin: 2px 0 10px; }
.card.review .r-title { color: var(--ink); }
/* lyrics get a framed, left-aligned panel here (the gift page shows them bare) */
.card.review .lyrics {
  text-align: left; margin: 14px 0 18px; padding: 16px 16px 14px;
  background: var(--paper); border: 1px solid var(--line); border-radius: 12px; font-size: 14.5px;
}
.btn.wide, .ghost.wide { width: 100%; display: block; }
/* Any wide CTA these screens hide must STAY hidden: display:block above would otherwise beat the
   UA's [hidden] rule and resurrect it (the known [hidden]-vs-display gotcha). */
.btn.wide[hidden], .ghost.wide[hidden] { display: none !important; }
.r-change { margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--line); text-align: left; }
.r-change label.field { margin-bottom: 8px; }
.r-change textarea { min-height: 70px; }

/* ════════════════════════════════════════════════════════════════════════
   W11.x — cross-device lyrics review ON THE GIFT PAGE (the wizard's twin above).
   Every selector is scoped under .gstate[data-state=…] (the W19 lesson: a bare
   shared class here collides with the wizard's review specificity and breaks layout).
   ════════════════════════════════════════════════════════════════════════ */
.gstate[data-state="lyrics_review"] .preamble {
  font-family: var(--serif); font-style: italic; color: var(--rose-deep);
  font-size: 15px; line-height: 1.5; margin: 2px 0 10px;
}
.gstate[data-state="lyrics_review"] .preamble[hidden] { display: none !important; }
/* lyrics get the framed, left-aligned panel (the generating wait shows them bare) */
.gstate[data-state="lyrics_review"] .lyrics {
  text-align: left; margin: 14px 0 18px; padding: 16px 16px 14px;
  background: var(--paper); border: 1px solid var(--line); border-radius: 12px; font-size: 14.5px;
}
.gstate[data-state="lyrics_review"] .lr-change {
  margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--line); text-align: left;
}
.gstate[data-state="lyrics_review"] .lr-change[hidden] { display: none !important; }
.gstate[data-state="lyrics_review"] .lr-change label.field { margin-bottom: 8px; }
.gstate[data-state="lyrics_review"] .lr-change textarea { min-height: 70px; }

/* ════════════════════════════════════════════════════════════════════════
   W15 — video greeting: the giver's round video note (Telegram-style).
   Prepare: recorder panes (idle / recording / review / saved). Sealed: the round
   player on the card. The circle is a WRAPPER div (border-radius + overflow:hidden) —
   Safari drops border-radius applied to <video> itself.
   ════════════════════════════════════════════════════════════════════════ */
.greet-box[hidden], .greet-pane[hidden], .greet-sealed[hidden], #greet-file-wrap[hidden],
.greet-progress[hidden] { display: none !important; }

.greet-box { text-align: center; margin-top: 22px; padding-top: 16px; border-top: 1px solid var(--line); }
.greet-box .sub { margin-bottom: 12px; }

.greet-circle {
  position: relative; width: 180px; height: 180px; margin: 6px auto 12px;
  border-radius: 50%; overflow: hidden; background: var(--paper-2);
  border: 2px solid var(--gold); box-shadow: 0 12px 28px -14px rgba(61, 43, 51, .4);
}
.greet-circle video { width: 100%; height: 100%; object-fit: cover; display: block; }
.greet-circle-lg { width: 200px; height: 200px; }

/* the live preview mirrors (people expect a mirror of themselves); the recording is NOT mirrored */
.greet-live { transform: scaleX(-1); }

.greet-count {
  position: absolute; left: 50%; bottom: 10px; transform: translateX(-50%); z-index: 1;
  font-variant-numeric: tabular-nums; font-weight: 700; font-size: 13px; color: #fff;
  background: rgba(42, 32, 36, .55); border-radius: 999px; padding: 3px 12px;
}
.greet-stop { background: var(--rose); }

/* upload progress under the review pane — same visual family as the W11.E bar */
.greet-progress { height: 6px; border-radius: 999px; background: var(--paper-2);
  overflow: hidden; margin: 12px auto 2px; max-width: 300px; }
.greet-progress-fill { height: 100%; width: 0;
  background: linear-gradient(90deg, var(--rose), var(--gold)); transition: width .3s ease; }

.greet-file-btn { display: inline-flex; align-items: center; justify-content: center;
  min-height: 48px; cursor: pointer; margin-top: 8px; }

/* W15.3 accent errors: the video-step status line escalates to a visible box on warn — the
   quiet grey .note voice read as decoration next to the camera (Timur, round 2). Scoped by ID:
   the element is <p class="note" id="greet-note"> (there is NO .greet-note class), and a bare
   .note.warn would leak into voice-note / vp-err. ok-статусы stay quiet on purpose. */
#greet-note.warn {
  background: color-mix(in srgb, var(--rose) 8%, transparent);
  border: 1.5px solid var(--rose);
  border-radius: 11px;
  padding: 12px 14px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--rose-deep);
}

/* sealed: the round note on the card, manual play — the tap badge renders off video events (M1) */
.greet-sealed { margin: 16px 0 6px; }
.greet-tap {
  position: absolute; inset: 0; z-index: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  background: rgba(42, 32, 36, .18); border: none; border-radius: 50%;
  transition: opacity .25s, background .2s;
}
.greet-tap svg { width: 46px; height: 46px; color: #fff;
  filter: drop-shadow(0 2px 8px rgba(42, 32, 36, .45)); }
.greet-tap:hover { background: rgba(42, 32, 36, .28); }
.greet-tap.playing { opacity: 0; pointer-events: none; }  /* the video itself toggles pause */

/* ════════════════════════════════════════════════════════════════════════
   Prepare = wrapping table (UX round 2026-07-03): three numbered steps, ONE
   primary CTA (Send), self-saving page. Number chips + caps headers give the
   360px scan; the mp3 keepsakes are quiet text links inside the song step.
   ════════════════════════════════════════════════════════════════════════ */
.wstep { margin-top: 22px; padding-top: 16px; border-top: 1px solid var(--line); text-align: left; }
.wstep-cap { display: flex; align-items: center; gap: 8px; margin-bottom: 10px;
  font-size: 11.5px; font-weight: 700; letter-spacing: 1.2px; text-transform: uppercase;
  color: var(--plum); }
/* W19: step numbers are CSS counters, not markup — the wrapping step hides while the feature
   flag is off (empty design_options), and a hardcoded "2. The song" without a step 1 would give
   the lie away. display:none ([hidden]) excludes a step from the count automatically. */
#prep-main { counter-reset: wstep; }
.wstep { counter-increment: wstep; }
.wstep-num { display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 22px; height: 22px; border-radius: 50%; background: var(--plum); color: var(--on-accent);
  font-size: 12px; font-weight: 700; letter-spacing: 0; }
.wstep-num::before { content: counter(wstep); }
.wstep-opt { margin-left: auto; font-size: 10.5px; letter-spacing: .8px; color: var(--ink-soft);
  font-weight: 600; }

/* ── W19: the wrapping step — a horizontal swatch carousel. Each dot carries its design's
   data-design attribute, so the THEME BLOCKS below ([data-design="…"]) paint the miniature
   with its own palette without touching the page. Scroll-snap keeps mobile swipes tidy. ── */
.design-carousel { display: flex; gap: 10px; overflow-x: auto; padding: 4px 2px 8px;
  scroll-snap-type: x proximity; -webkit-overflow-scrolling: touch;
  /* no scrollbar chrome: macOS "always show" renders a bar even when everything fits.
     Touch/trackpad scrolling still works — the standard carousel affordance. */
  scrollbar-width: none; }
.design-carousel::-webkit-scrollbar { display: none; }
.design-carousel .swatch { flex: none; scroll-snap-align: start; cursor: pointer; font: inherit;
  display: flex; flex-direction: column; align-items: center; gap: 6px; min-width: 64px;
  background: none; border: none; padding: 6px 4px; border-radius: 12px; }
.design-carousel .swatch-dot { width: 44px; height: 44px; border-radius: 50%; position: relative;
  background:
    linear-gradient(135deg, var(--paper) 0 38%, var(--rose) 38% 72%, var(--plum) 72% 100%);
  border: 2px solid var(--line);
  box-shadow: inset 0 0 0 2px var(--card), 0 4px 10px -6px color-mix(in srgb, var(--ink) 40%, transparent);
  transition: transform .12s, border-color .15s, box-shadow .15s; }
.design-carousel .swatch:hover .swatch-dot { transform: translateY(-2px); border-color: var(--rose); }
.design-carousel .swatch-label { font-size: 11px; font-weight: 600; color: var(--ink-soft); letter-spacing: .3px; }
.design-carousel .swatch.sel .swatch-dot { border-color: var(--rose);
  box-shadow: inset 0 0 0 2px var(--card), 0 0 0 3px color-mix(in srgb, var(--rose) 20%, transparent); }
.design-carousel .swatch.sel .swatch-label { color: var(--rose-deep); }
#wstep-design .wrap-sub { font-size: 12.5px; margin: 0 0 8px; }

/* ════════════════════════════════════════════════════════════════════════
   W19 — CARD DESIGNS. One block per design, selector [data-design="key"]: it themes BOTH the
   whole page (<html data-design=…>) and any swatch miniature (.swatch-dot[data-design=…]).
   Designs land here ONE AT A TIME (W19.5 review pipeline). Add designs, never rename keys —
   saved orders reference them forever. Every block must keep AA contrast on: buttons, inputs,
   player, note-box, deal timer, envelope, footer links.
   ════════════════════════════════════════════════════════════════════════ */
/* classic — the default: identical to :root. The block exists for the SWATCH miniature: on a
   repainted page the classic dot must show classic colors, not the inherited theme's. */
[data-design="classic"] {
  --paper: #fbf5ef;
  --paper-2: #f3e7dd;
  --card: #ffffff;
  --ink: #2a2024;
  --ink-soft: #6b5b62;
  --rose: #b23a64;
  --rose-deep: #8e2c4f;
  --plum: #6d3b6e;
  --gold: #c9a24b;
  --rec: #7a4a9c;        /* W33.1 recommended: violet, same as :root */
  --line: #ecdfd5;
  --glow: #ffffff;
  --on-accent: #ffffff;
}

/* midnight — deep navy night, champagne-gold accents, starlit paper, Cormorant display.
   Dark theme = the contrast stress-test: every value below was picked against --card #1c2542
   (ink 12:1, ink-soft 5.5:1, lavender plum 7:1, gold 8:1; amber CTAs carry DARK text). */
@font-face {
  font-family: "Cormorant Garamond";
  src: url("/static/fonts/cormorant-garamond-600-v1.woff2") format("woff2");
  font-weight: 600 700;
  font-style: normal;
  font-display: swap; /* loads lazily — only once a midnight selector actually uses it */
}
@font-face {
  font-family: "Cormorant Garamond";
  src: url("/static/fonts/cormorant-garamond-500-v1.woff2") format("woff2");
  font-weight: 400 500;
  font-style: normal;
  font-display: swap;
}
[data-design="midnight"] {
  --paper: #141b31;
  --paper-2: #0c1122;
  --card: #1c2542;
  --ink: #edeffa;
  --ink-soft: #9ca6c9;
  --rose: #d4a94f;       /* the accent family turns champagne-amber */
  --rose-deep: #c19a45;
  --plum: #aab6ea;       /* brand ink / step chips: moonlit lavender */
  --gold: #e0be6a;
  --rec: #8ea2ff;        /* W33.1 recommended: brighter periwinkle — pops on dark, clear of amber rose/gold */
  --line: #2c3760;
  --glow: #26335c;       /* the lamp glows deep blue, never white */
  --on-accent: #191307;  /* dark lettering on the amber CTA */
  --shadow: 0 16px 44px -24px rgba(2, 6, 18, 0.85);
  --shadow-soft: 0 8px 26px -20px rgba(2, 6, 18, 0.8);
  --grain-opacity: .22;  /* multiply-grain reads harsh on dark paper — dial it down */
  --font-display: "Cormorant Garamond", var(--serif);
  --font-body: "Cormorant Garamond", var(--serif);  /* the 500 face carries the dark body */
  /* a sparse hand-placed starfield (gold dots + two 4-point sparks), tiled 260px */
  --bg-layer: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='260'%3E%3Cg fill='%23e0be6a'%3E%3Ccircle cx='24' cy='38' r='1' opacity='.42'/%3E%3Ccircle cx='96' cy='18' r='.8' opacity='.3'/%3E%3Ccircle cx='168' cy='54' r='1.2' opacity='.36'/%3E%3Ccircle cx='226' cy='30' r='.7' opacity='.28'/%3E%3Ccircle cx='58' cy='104' r='.9' opacity='.3'/%3E%3Ccircle cx='140' cy='128' r='.7' opacity='.24'/%3E%3Ccircle cx='210' cy='112' r='1' opacity='.34'/%3E%3Ccircle cx='30' cy='176' r='.8' opacity='.26'/%3E%3Ccircle cx='108' cy='200' r='1.1' opacity='.34'/%3E%3Ccircle cx='186' cy='182' r='.8' opacity='.26'/%3E%3Ccircle cx='238' cy='222' r='1' opacity='.3'/%3E%3Ccircle cx='70' cy='242' r='.7' opacity='.24'/%3E%3Cpath d='M196 74l1.1 3.4 3.4 1.1-3.4 1.1-1.1 3.4-1.1-3.4-3.4-1.1 3.4-1.1z' opacity='.4'/%3E%3Cpath d='M44 138l.9 2.8 2.8.9-2.8.9-.9 2.8-.9-2.8-2.8-.9 2.8-.9z' opacity='.32'/%3E%3C/g%3E%3C/svg%3E");
  --bg-layer-size: 260px 260px;
}

/* confetti — a bright party: sunny cream paper, raspberry-and-violet accents, scattered
   paper confetti, round friendly Baloo 2 display. Light theme, so contrast rides on the
   ink family: ink 13:1 / ink-soft 5.4:1 on the white card; the raspberry CTA keeps WHITE
   lettering at 4.9:1 (unlike midnight's dark-on-amber). mood=festive — hidden on In Memoriam. */
@font-face {
  font-family: "Baloo 2";
  src: url("/static/fonts/baloo-2-600-v1.woff2") format("woff2");
  font-weight: 600 700;
  font-style: normal;
  font-display: swap; /* loads lazily — only once a confetti selector actually uses it */
}
@font-face {
  font-family: "Baloo 2";
  src: url("/static/fonts/baloo-2-400-v1.woff2") format("woff2");
  font-weight: 400 500;
  font-style: normal;
  font-display: swap;
}
[data-design="confetti"] {
  --paper: #fff7ee;
  --paper-2: #ffecdc;
  --card: #ffffff;
  --ink: #372a52;        /* deep violet ink — playful, still 13:1 on the card */
  --ink-soft: #71638b;
  --rose: #cf2f6b;       /* raspberry party accent (CTAs keep white lettering) */
  --rose-deep: #a92355;
  --plum: #6b3fa0;       /* vivid violet: brand ink / step chips */
  --gold: #e8a13c;
  --rec: #2bb3a3;        /* W33.1 recommended: party teal — clear of raspberry rose and amber gold */
  --line: #f5dfe2;
  --glow: #ffffff;
  --on-accent: #ffffff;
  --font-display: "Baloo 2", var(--sans);
  --font-body: "Baloo 2", var(--sans);
  /* hand-scattered paper confetti (rounded strips + dots in the accent family), tiled 240px */
  --bg-layer: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cg%3E%3Crect x='22' y='30' width='9' height='3.4' rx='1.7' fill='%23cf2f6b' opacity='.5' transform='rotate(24 26 32)'/%3E%3Crect x='104' y='14' width='8' height='3.2' rx='1.6' fill='%236b3fa0' opacity='.42' transform='rotate(-31 108 16)'/%3E%3Crect x='186' y='42' width='9' height='3.4' rx='1.7' fill='%23e8a13c' opacity='.5' transform='rotate(52 190 44)'/%3E%3Crect x='58' y='78' width='8' height='3.2' rx='1.6' fill='%232bb3a3' opacity='.44' transform='rotate(-14 62 80)'/%3E%3Crect x='150' y='96' width='9' height='3.4' rx='1.7' fill='%23cf2f6b' opacity='.4' transform='rotate(38 154 98)'/%3E%3Crect x='16' y='138' width='8' height='3.2' rx='1.6' fill='%23e8a13c' opacity='.46' transform='rotate(-42 20 140)'/%3E%3Crect x='96' y='162' width='9' height='3.4' rx='1.7' fill='%236b3fa0' opacity='.44' transform='rotate(18 100 164)'/%3E%3Crect x='188' y='150' width='8' height='3.2' rx='1.6' fill='%232bb3a3' opacity='.4' transform='rotate(-26 192 152)'/%3E%3Crect x='48' y='206' width='9' height='3.4' rx='1.7' fill='%23cf2f6b' opacity='.46' transform='rotate(44 52 208)'/%3E%3Crect x='142' y='222' width='8' height='3.2' rx='1.6' fill='%23e8a13c' opacity='.42' transform='rotate(-19 146 224)'/%3E%3Ccircle cx='76' cy='24' r='2' fill='%232bb3a3' opacity='.42'/%3E%3Ccircle cx='218' cy='88' r='1.8' fill='%236b3fa0' opacity='.4'/%3E%3Ccircle cx='36' cy='96' r='1.6' fill='%23e8a13c' opacity='.46'/%3E%3Ccircle cx='122' cy='128' r='2' fill='%23cf2f6b' opacity='.38'/%3E%3Ccircle cx='202' cy='196' r='1.8' fill='%23cf2f6b' opacity='.4'/%3E%3Ccircle cx='86' cy='236' r='1.6' fill='%236b3fa0' opacity='.44'/%3E%3Ccircle cx='12' cy='182' r='1.8' fill='%232bb3a3' opacity='.4'/%3E%3C/g%3E%3C/svg%3E");
  --bg-layer-size: 240px 240px;
}

/* garden — a calm botanical morning: watercolor eucalyptus-and-olive paper (the first
   imagegen texture, bg-garden-v1.webp — pre-blended 55% toward the paper tone so text always
   wins), moss ink, fern-green CTAs with white lettering (5:1), Lora serif display.
   --paper matches the texture's own paper tone, so the tile melts into the gradient. */
@font-face {
  font-family: "Lora";
  src: url("/static/fonts/lora-600-v1.woff2") format("woff2");
  font-weight: 600 700;
  font-style: normal;
  font-display: swap; /* loads lazily — only once a garden selector actually uses it */
}
@font-face {
  font-family: "Lora";
  src: url("/static/fonts/lora-400-v1.woff2") format("woff2");
  font-weight: 400 500;
  font-style: normal;
  font-display: swap;
}
[data-design="garden"] {
  --paper: #fbfdf1;
  --paper-2: #edf0dc;
  --card: #ffffff;
  --ink: #2e352a;        /* deep moss — 12:1 on the card */
  --ink-soft: #68715f;
  --rose: #4d7a52;       /* fern green CTAs, white lettering at ~5:1 */
  --rose-deep: #3b6141;
  --plum: #416b5c;       /* eucalyptus: brand ink / step chips */
  --gold: #b9924a;       /* dried-leaf ochre */
  --rec: #7a5aa0;        /* W33.1 recommended: soft wisteria — the one non-green/ochre accent that reads on the botanical palette */
  --line: #e5e9d4;
  --glow: #ffffff;
  --on-accent: #ffffff;
  --font-display: "Lora", var(--serif);
  --font-body: "Lora", var(--serif);
  --bg-layer: url("/static/bg-garden-v1.webp");
  --bg-layer-size: 560px 560px;
}

/* luxe — champagne and foil: warm marble-sheen paper (imagegen texture, mirror-tiled into a
   seamless 900px tile, pre-blended 20% toward the paper tone), espresso ink, bronze-gold
   CTAs with white lettering (~5:1), Marcellus display. Marcellus ships one 400 weight —
   the 400-700 range declaration makes headings render its TRUE light roman (no faux bold),
   which is the point: luxe whispers. */
@font-face {
  font-family: "Marcellus";
  src: url("/static/fonts/marcellus-400-v1.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap; /* loads lazily — only once a luxe selector actually uses it */
}
[data-design="luxe"] {
  --paper: #f7f0e4;
  --paper-2: #efe3cf;
  --card: #fffdf9;
  --ink: #33291c;        /* warm espresso — 12:1 on the card */
  --ink-soft: #77684f;
  --rose: #8f6a20;       /* bronze gold CTAs, white lettering at ~4.9:1 */
  --rose-deep: #74551a;
  --plum: #6a4f2a;       /* aged bronze: brand ink / step chips */
  --gold: #d4ab4f;
  --rec: #7a5a9c;        /* W33.1 recommended: muted amethyst — the cool note against the all-bronze palette */
  --line: #eadfc9;
  --glow: #fffdf7;
  --on-accent: #ffffff;
  --font-display: "Marcellus", var(--serif);
  --font-body: "Marcellus", var(--serif);  /* single face serves body and headings alike */
  --bg-layer: url("/static/bg-luxe-v1.webp");
  --bg-layer-size: 900px 900px;
}

/* kraft — a warm parcel: kraft-paper tan, paper-fiber flecks (pure SVG, no texture file),
   terracotta CTAs with white lettering (~4.6:1), ink-stamp brown chips, handwritten Caveat
   display. ink-soft is darkened past the usual — soft text sits on the TAN paper here
   (footer, whispers), and the kraft paper is far from white. */
@font-face {
  font-family: "Caveat";
  src: url("/static/fonts/caveat-600-v1.woff2") format("woff2");
  font-weight: 600 700;
  font-style: normal;
  font-display: swap; /* loads lazily — only once a kraft selector actually uses it */
}
@font-face {
  font-family: "Courier Prime";
  src: url("/static/fonts/courier-prime-400-v1.woff2") format("woff2");
  font-weight: 400 700; /* single face; bold synthesizes - fine for a typewriter */
  font-style: normal;
  font-display: swap;
}
[data-design="kraft"] {
  --paper: #e9d9ba;
  --paper-2: #dcc69d;
  --card: #fffdf4;
  --ink: #3d3121;        /* dark parcel-ink brown — 12:1 on the card */
  --ink-soft: #665640;   /* 5:1 even on the tan paper */
  --rose: #b65c38;       /* terracotta CTAs, white lettering at ~4.6:1 */
  --rose-deep: #96482a;
  --plum: #6f5433;       /* ink-stamp brown: brand ink / step chips */
  --gold: #b98a3e;
  --rec: #4f7d8c;        /* W33.1 recommended: faded denim stamp — the cool ink against the warm parcel palette */
  --line: #e0cda8;
  --glow: #f7ecd7;       /* kraft never glows white — a softer lamp */
  --on-accent: #ffffff;
  --font-display: "Caveat", cursive;
  --font-body: "Courier Prime", monospace;  /* typewritten parcel labels; handwriting stays for headings/lyrics/note */
  /* Caveat x-height is small: the lyrics block compensates (rule after the theme blocks) */
  /* paper fibers: short strokes darker and lighter than the sheet + tiny specks, tiled 220px */
  --bg-layer: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cg stroke-linecap='round'%3E%3Cpath d='M18 34l14 -2' stroke='%23b89566' stroke-width='1.1' opacity='.5'/%3E%3Cpath d='M92 18l11 1.5' stroke='%23fff7e3' stroke-width='1.2' opacity='.7'/%3E%3Cpath d='M170 46l13 -1' stroke='%23b89566' stroke-width='1' opacity='.42'/%3E%3Cpath d='M44 84l12 2' stroke='%23fff7e3' stroke-width='1.1' opacity='.6'/%3E%3Cpath d='M132 72l14 -2.5' stroke='%23b89566' stroke-width='1.1' opacity='.46'/%3E%3Cpath d='M196 104l12 1' stroke='%23fff7e3' stroke-width='1' opacity='.55'/%3E%3Cpath d='M26 132l13 1.5' stroke='%23b89566' stroke-width='1' opacity='.44'/%3E%3Cpath d='M104 150l12 -1.5' stroke='%23fff7e3' stroke-width='1.2' opacity='.62'/%3E%3Cpath d='M172 166l14 2' stroke='%23b89566' stroke-width='1.1' opacity='.48'/%3E%3Cpath d='M58 196l12 -2' stroke='%23b89566' stroke-width='1' opacity='.42'/%3E%3Cpath d='M142 208l13 1' stroke='%23fff7e3' stroke-width='1.1' opacity='.58'/%3E%3C/g%3E%3Cg fill='%23a08252'%3E%3Ccircle cx='70' cy='52' r='1' opacity='.4'/%3E%3Ccircle cx='156' cy='30' r='.9' opacity='.36'/%3E%3Ccircle cx='206' cy='70' r='1' opacity='.4'/%3E%3Ccircle cx='16' cy='96' r='.9' opacity='.34'/%3E%3Ccircle cx='86' cy='118' r='1' opacity='.38'/%3E%3Ccircle cx='186' cy='138' r='.9' opacity='.34'/%3E%3Ccircle cx='36' cy='168' r='1' opacity='.38'/%3E%3Ccircle cx='120' cy='186' r='.9' opacity='.34'/%3E%3Ccircle cx='204' cy='204' r='1' opacity='.36'/%3E%3C/g%3E%3C/svg%3E");
  --bg-layer-size: 220px 220px;
}

/* pastel — a soft dreamy sky: blush-lavender paper with drifting watercolor clouds of mint,
   baby blue, blush and lilac (pure SVG radial blobs, no texture file), dusty-violet ink,
   rose CTAs with white lettering (~5:1), round gentle Quicksand display. mood=calm. */
@font-face {
  font-family: "Quicksand";
  src: url("/static/fonts/quicksand-600-v1.woff2") format("woff2");
  font-weight: 600 700;
  font-style: normal;
  font-display: swap; /* loads lazily — only once a pastel selector actually uses it */
}
@font-face {
  font-family: "Quicksand";
  src: url("/static/fonts/quicksand-400-v1.woff2") format("woff2");
  font-weight: 400 500;
  font-style: normal;
  font-display: swap;
}
[data-design="pastel"] {
  --paper: #f9f3f7;
  --paper-2: #ece7f5;
  --card: #ffffff;
  --ink: #453a5e;        /* dusty violet — 9.5:1 on the card */
  --ink-soft: #746a8b;
  --rose: #b34a72;       /* soft rose CTAs, white lettering at ~5:1 */
  --rose-deep: #93395c;
  --plum: #655a9e;       /* periwinkle: brand ink / step chips */
  --gold: #d9a662;       /* peach gold */
  --rec: #4fa39a;        /* W33.1 recommended: soft mint-teal (echoes the cloud palette) — clear of rose and peach gold */
  --line: #eee3ee;
  --glow: #ffffff;
  --on-accent: #ffffff;
  --font-display: "Quicksand", var(--sans);
  --font-body: "Quicksand", var(--sans);
  /* drifting pastel clouds: large soft radial blobs in mint / baby blue / blush / lilac,
     very low opacity, tiled 480px — the gradient stays the base, the blobs just breathe */
  --bg-layer: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='480' height='480'%3E%3Cdefs%3E%3CradialGradient id='a'%3E%3Cstop offset='0%25' stop-color='%23f5bfd4' stop-opacity='.5'/%3E%3Cstop offset='100%25' stop-color='%23f5bfd4' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='b'%3E%3Cstop offset='0%25' stop-color='%23bcdcf2' stop-opacity='.5'/%3E%3Cstop offset='100%25' stop-color='%23bcdcf2' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='c'%3E%3Cstop offset='0%25' stop-color='%23c4e9d2' stop-opacity='.48'/%3E%3Cstop offset='100%25' stop-color='%23c4e9d2' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='d'%3E%3Cstop offset='0%25' stop-color='%23d9c8f2' stop-opacity='.5'/%3E%3Cstop offset='100%25' stop-color='%23d9c8f2' stop-opacity='0'/%3E%3C/radialGradient%3E%3C/defs%3E%3Ccircle cx='96' cy='84' r='86' fill='url(%23a)'/%3E%3Ccircle cx='368' cy='60' r='72' fill='url(%23b)'/%3E%3Ccircle cx='210' cy='240' r='92' fill='url(%23c)'/%3E%3Ccircle cx='420' cy='300' r='78' fill='url(%23d)'/%3E%3Ccircle cx='60' cy='380' r='80' fill='url(%23b)'/%3E%3Ccircle cx='300' cy='430' r='70' fill='url(%23a)'/%3E%3C/svg%3E");
  --bg-layer-size: 480px 480px;
}

/* kraft: handwritten lyrics/note render small — give the reading blocks more size */
[data-design="kraft"] .lyrics { font-size: 19px; }
[data-design="kraft"] .dedication { font-size: 19px; }

/* the reparented note editor loses its own frame inside step 2 — the step header speaks */
#prepare-note-slot .note-box { border-top: none; margin-top: 0; padding-top: 0; }
#prepare-note-slot .note-box .eyebrow { display: none; }

/* step 3 wraps the greet-box — its own border/eyebrow would double the step chrome */
#wstep-video .greet-box { border-top: none; margin-top: 0; padding-top: 0; text-align: center; }

/* mp3 keepsakes: a quiet line under the player, NOT buttons (they read as "download the
   video" anywhere near the video block) */
.dl-line[hidden] { display: none !important; }
.dl-line { font-size: 12.5px; color: var(--ink-soft); margin: 10px 2px 0; text-align: center; }
.dl-inline-link { color: var(--ink-soft); text-decoration: underline; text-underline-offset: 2px; }
.dl-inline-link:hover { color: var(--rose-deep); }

/* note autosave whisper — the only save voice on the page */
.note-whisper { margin-left: auto; font-size: 12.5px; color: var(--ink-soft); align-self: center; }
.note-whisper.ok { color: var(--plum); }
.note-whisper.warn { color: var(--rose-deep); }
.note-whisper[hidden] { display: none !important; }

/* seal block: reassurance line + the summary inside the confirm */
.seal-reassure { font-size: 12.5px; color: var(--ink-soft); margin: 0 0 10px; }
.sc-cap { font-family: var(--font-display); font-weight: 600; font-size: 17px; color: var(--ink);
  margin: 0 0 6px; }
.sc-summary { font-size: 13.5px; line-height: 1.55; color: var(--ink); max-width: 400px;
  margin: 0 auto 10px; }
.sc-summary.sc-nudge { color: var(--rose-deep); }
/* W15.4: the not-added-video warning inside the confirm — louder than the note nudge (the
   giver already made the recording effort; silent loss at the seal is the worst failure) */
.sc-video-warn { color: var(--rose-deep); font-weight: 600; font-size: 13.5px; line-height: 1.5;
  max-width: 400px; margin: 0 auto 10px; }
.sc-video-warn[hidden] { display: none !important; }

/* ===== W35 JS-dead fallbacks (gift.html + index.html). Two mutually exclusive layers:
   .nojs-note lives in <noscript> — renders ONLY when scripting is disabled (e.g. a tab that
   inherited an email-preview sandbox; incident 2026-07-15). .js-fallback is the CSS-only
   delayed banner for JS that loaded but never finished (crashed/missing file): pure animation
   reveals it at 10s, and a healthy gift.js/app.js adds .js-alive on <html> as its LAST line
   to keep it away — any top-level throw leaves the banner armed. The head's noscript-style
   kills .js-fallback when scripting is off, so the two texts never stack. Base is COLLAPSED
   (max-height:0 — no reserved box, no CLS while invisible); [hidden] is not used here. */
.nojs-note {
  font-size: 13.5px; line-height: 1.55; color: var(--ink); max-width: 400px;
  margin: 14px auto 0; padding: 10px 14px;
  background: var(--paper); border: 1.5px solid var(--line); border-radius: 12px;
}
.js-fallback {
  max-height: 0; opacity: 0; overflow: hidden;
  font-size: 13.5px; line-height: 1.55; color: var(--ink); max-width: 400px;
  margin: 0 auto; padding: 0 14px;
  background: var(--paper); border: 1.5px solid var(--line); border-radius: 12px;
  animation: js-fallback-reveal .3s ease 10s forwards;
}
.js-alive .js-fallback { display: none !important; animation: none; }
/* W35: the review-module guard hides #retry with the hidden attribute — pin the known
   [hidden]-vs-component-display gotcha for this one button explicitly. */
#retry[hidden] { display: none !important; }
@keyframes js-fallback-reveal {
  to { max-height: 200px; opacity: 1; margin-top: 14px; padding-top: 10px; padding-bottom: 10px; }
}

/* ════════════════════════════════════════════════════════════════════════
   W42 — KARAOKE (word-synced lyrics under the players; karaoke.js drives it).
   Colors ride the W19 theming vars only (--rose fill, --ink dim, --ink-soft past) — every
   design block already keeps these AA-safe, so karaoke inherits the theme for free.
   .karaoke has no display rule beyond block, but the [hidden] guard is explicit anyway
   (project gotcha: a later component display rule must never resurrect a hidden block). */
.karaoke { height: 10.5em; overflow: hidden; position: relative; margin: 14px 2px 4px;
  -webkit-mask-image: linear-gradient(transparent, #000 10%, #000 86%, transparent);
  mask-image: linear-gradient(transparent, #000 10%, #000 86%, transparent); }
.karaoke[hidden] { display: none !important; }
.kr-scroll { transition: transform .6s cubic-bezier(.22, 1, .36, 1); }
.kr-line { margin: 0 0 .3em; font-family: var(--font-body); font-size: 1.08rem;
  line-height: 1.5; font-weight: 600; cursor: pointer; transform-origin: left center;
  transition: opacity .4s, filter .4s, transform .4s;
  opacity: .8; filter: blur(calc(var(--d, 0) * .7px)); transform: scale(.97); }
.kr-line.cur { opacity: 1; filter: blur(0); transform: scale(1); }
.kr-line.past { opacity: .35; filter: blur(calc(var(--d, 0) * .5px + .8px)); }
/* Progressive word fill: the gradient mask slides left for exactly the word's sung duration
   (karaoke.js sets --dur per word). The first background is the no-color-mix fallback -
   without it an unsupported color-mix would drop the WHOLE declaration and color:transparent
   would erase the text. */
.kr-w {
  background: linear-gradient(to right, var(--rose) 50%, rgba(128, 120, 125, .5) 50%);
  background: linear-gradient(to right, var(--rose) 50%,
    color-mix(in srgb, var(--ink) 38%, transparent) 50%);
  background-size: 210% 100%; background-position-x: 100%;
  -webkit-background-clip: text; background-clip: text; color: transparent;
  transition: background-position-x var(--dur, .25s) linear; }
.kr-w.sung { background-position-x: 0%; }
.kr-line.past .kr-w { background: none; color: var(--ink-soft); transition: color .4s; }
/* Interlude dots: instrumental gaps ≥4s breathe, light up over the gap, die before the entry. */
.kr-dots { display: none; margin: 0 0 .3em; letter-spacing: .35em; font-size: 1.05rem;
  color: var(--ink-soft); }
.kr-dots.show { display: block; }
.kr-dots i { font-style: normal; opacity: .25; transition: opacity .3s;
  animation: kr-breath 1.5s ease-in-out infinite; display: inline-block; }
.kr-dots i.lit { opacity: 1; }
@keyframes kr-breath { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.15); } }
@media (prefers-reduced-motion: reduce) {
  .kr-line, .kr-w, .kr-scroll { transition: none; }
  .kr-dots i { animation: none; }
}

/* ── W38 sample feed — trust cards under the quiz (index.html) ─────────
   Third child of .funnel, between .card and .rail in source order: mobile's single-column
   collapse then reads quiz → samples → helper without pushing the quiz down. Desktop's grid
   declares no rows, so all three children get explicit placement — samples span full width
   in row 2. Everything is scoped under .samples: the bare .sc-* prefix already belongs to
   gift.html's seal-card block in this shared file. Palette variables only (W19 theming).
   The desktop @media block sits at the END of this section — equal specificity means the
   later rule wins, so it must follow the mobile-first base to override it. */
.samples { min-width: 0; }
.samples .samples-title { font-family: var(--font-display); font-weight: 600; font-size: 15px;
  color: var(--ink); margin: 0 0 9px; }
.samples .samples-sub { color: var(--ink-soft); font-size: 12px; font-weight: 400; font-style: italic; }
/* Mobile-first: a swipe strip — one card fully visible plus the edge of the next as the
   invitation to swipe (76% of a 390px shell leaves ~80px of neighbour showing). */
.samples .samples-feed { display: grid; grid-auto-flow: column; grid-auto-columns: min(76%, 300px);
  gap: 10px; overflow-x: auto; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch;
  scrollbar-width: none; padding: 2px 2px 6px; }
.samples .samples-feed::-webkit-scrollbar { display: none; }
.samples .sample-card { scroll-snap-align: start; position: relative; overflow: hidden;
  display: flex; align-items: center; gap: 11px; padding: 12px 13px 26px; min-width: 0;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-soft); }
.samples .smp-play { width: 44px; height: 44px; border-radius: 50%; border: none; cursor: pointer;
  flex: none; display: flex; align-items: center; justify-content: center;
  background: linear-gradient(135deg, var(--rose), var(--rose-deep)); color: var(--on-accent);
  transition: transform .15s; }
.samples .smp-play:hover { transform: scale(1.06); }
.samples .smp-play svg { width: 20px; height: 20px; }
.samples .smp-play .ic-play { margin-left: 2px; }
.samples .smp-play .ic-pause { display: none; }
.samples .sample-card.playing .ic-play { display: none; }
.samples .sample-card.playing .ic-pause { display: block; }
/* loading (slow network): the glyph gives way to a small spinner ring inside the same button */
.samples .sample-card.loading .ic-play,
.samples .sample-card.loading .ic-pause { display: none; }
.samples .smp-spin { display: none; width: 18px; height: 18px; border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, .4); border-top-color: var(--on-accent);
  animation: smp-spin .8s linear infinite; }
.samples .sample-card.loading .smp-spin { display: block; }
@keyframes smp-spin { to { transform: rotate(360deg); } }
.samples .smp-meta { min-width: 0; }
.samples .smp-name { font-family: var(--font-display); font-weight: 600; font-size: 14.5px;
  color: var(--ink); line-height: 1.25; display: -webkit-box; -webkit-line-clamp: 2;
  -webkit-box-orient: vertical; overflow: hidden; }
.samples .smp-for { font-size: 12px; color: var(--ink-soft); margin-top: 2px;
  line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2;
  -webkit-box-orient: vertical; overflow: hidden; }
/* Progress + seek: a warm ribbon along the card's bottom edge. The .smp-bar itself is a
   24px-tall invisible hit zone (mobile thumbs need it — same trick as .p-seek::before on the
   gift page); the visible lane is 5px at its bottom. touch-action:none keeps a horizontal
   scrub from being hijacked by the swipe strip's own scroll. The empty lane shows only on
   the ACTIVE card — that's the "you can seek here" affordance. */
.samples .smp-bar { position: absolute; left: 0; right: 0; bottom: 0; height: 26px;
  cursor: pointer; touch-action: none; }
.samples .smp-bar::before { content: ""; position: absolute; left: 10px; right: 10px; bottom: 7px;
  height: 5px; border-radius: 3px; background: var(--paper-2); opacity: 0; transition: opacity .2s; }
.samples .sample-card.active .smp-bar::before { opacity: 1; }
.samples .smp-fill { position: absolute; left: 10px; bottom: 7px; height: 5px;
  max-width: calc(100% - 20px); border-radius: 3px; width: 0%;
  background: linear-gradient(90deg, var(--rose), var(--gold)); }
/* The grab dot: rides the fill's right edge, appears with the lane on the active card.
   Pure CSS — the drag logic lives on the whole 26px .smp-bar zone, the dot just tracks it. */
.samples .sample-card.active .smp-fill::after { content: ""; position: absolute;
  right: -8px; top: 50%; transform: translateY(-50%); width: 16px; height: 16px;
  border-radius: 50%; background: var(--card); border: 3px solid var(--rose-deep);
  box-shadow: 0 1px 5px rgba(64, 24, 44, .35); box-sizing: border-box; }
.samples .sample-card.err .smp-fill::after { display: none; }
/* .err swaps the dedication line for the error note; the button goes inert. */
.samples .smp-err { display: none; font-size: 12px; color: var(--rose-deep); margin: 2px 0 0; }
.samples .sample-card.err .smp-play { opacity: .4; pointer-events: none; }
.samples .sample-card.err .smp-for { display: none; }
.samples .sample-card.err .smp-err { display: block; }
@media (prefers-reduced-motion: reduce) {
  .samples .smp-spin { animation: none; }
  .samples .smp-play { transition: none; }
}
@media (min-width: 781px) {
  .funnel > .card { grid-column: 1; grid-row: 1; }
  .funnel > .rail { grid-column: 2; grid-row: 1; }
  .funnel > .samples { grid-column: 1 / -1; grid-row: 2; }
  .samples .samples-feed { grid-auto-columns: minmax(0, 1fr); overflow-x: visible; }
  /* five columns leave ~110px for the meta — a slimmer button buys the title a longer line */
  .samples .sample-card { gap: 9px; padding: 11px 12px 24px; }
  .samples .smp-play { width: 40px; height: 40px; }
  .samples .smp-play svg { width: 18px; height: 18px; }
}

/* ── W44 post-demo offer popup: bottom sheet (mobile) / centered card (desktop) ──────────
   Opens once per pageload on the demo `ended` (gift.js). z-index 50 — above the page
   content (body > * = 1), below the envelope (60) and the email gate (70): the gate must
   always win should they ever coexist. The [hidden] guard is mandatory — there is no global
   [hidden] rule in this file, and the desktop @media sets display:flex on the container. */
#demo-done { position: fixed; inset: 0; z-index: 50; }
#demo-done[hidden] { display: none !important; }
.dd-backdrop { position: absolute; inset: 0; background: rgba(61, 43, 51, .38);
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
  opacity: 0; transition: opacity .25s ease; }
#demo-done.open .dd-backdrop { opacity: 1; }
.dd-card {
  position: absolute; left: 0; right: 0; bottom: 0;
  background: var(--card); border: 1px solid var(--line);
  border-radius: 18px 18px 0 0;
  padding: 26px 22px calc(24px + env(safe-area-inset-bottom));
  text-align: center; overflow: hidden; outline: none;
  box-shadow: 0 -18px 50px -24px rgba(61, 43, 51, .45);
  transform: translateY(100%); transition: transform .25s ease;
}
.dd-card::before { /* gold hairline, same signature as .card / .eg-card */
  content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: linear-gradient(90deg, transparent, var(--gold), transparent);
}
#demo-done.open .dd-card { transform: translateY(0); }
.dd-card h2 { font-family: var(--serif); font-size: 22px; line-height: 1.25; margin: 8px 0 6px; color: var(--ink); }
.dd-card .btn { width: 100%; margin-top: 16px; }
.dd-close { position: absolute; top: 8px; right: 8px; width: 38px; height: 38px;
  border: none; background: none; cursor: pointer; color: var(--muted);
  font-size: 24px; line-height: 1; padding: 0; }
.dd-close:hover { color: var(--ink); }
/* desktop: the sheet becomes a centered card. This @media block sits AFTER the mobile base
   on purpose — equal specificity, source order decides (the W38 lesson). */
@media (min-width: 720px) {
  #demo-done { display: flex; align-items: center; justify-content: center; padding: 24px; }
  .dd-card { position: relative; inset: auto; width: 100%; max-width: 420px;
    border-radius: 16px; padding: 26px;
    box-shadow: 0 30px 60px -30px rgba(61, 43, 51, .35);
    transform: translateY(10px); opacity: 0;
    transition: transform .25s ease, opacity .25s ease; }
  #demo-done.open .dd-card { transform: translateY(0); opacity: 1; }
  .dd-card .btn { width: auto; }
}
@media (prefers-reduced-motion: reduce) {
  .dd-backdrop, .dd-card { transition: none; }
}

/* ============================================================================
   Tool pages (W41.1) — /tools/{slug}. Everything here is scoped under .tool-*:
   this stylesheet is shared with the wizard and the gift page, and a bare class
   name in it has collided before. The chips, inputs, .btn and .err come from the
   components above untouched — the wizard's look is the point (the visitor lands
   in it a second after submitting, with this very file already cached).
   ============================================================================ */
/* .funnel reserves a 250px column for the wizard's "Your song so far" rail. This page has no
   rail (every question is on screen at once), so without this the card sits off-centre with an
   empty gutter beside it. Same measure as the reading sections below, so the page reads as one
   column top to bottom. */
.tool-funnel { grid-template-columns: minmax(0, 1fr); max-width: 640px; margin: 0 auto; }

.tool-form { text-align: left; }
.tool-group { border: 0; padding: 0; margin: 0 0 18px; }
.tool-legend { display: block; padding: 0; margin: 0 0 8px;
  font-family: var(--font-display); font-weight: 600; font-size: 17px; color: var(--ink); }
/* A field the visitor skipped: the group is named by its legend, so the mark goes on the
   whole group rather than on one control. */
.tool-group.invalid .tool-legend, .tool-group.invalid .cap { color: var(--rose-deep); }
.tool-group.invalid .tool-choices { border-radius: 12px;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--rose-deep) 12%, transparent); }
.tool-group.invalid textarea { border-color: var(--rose-deep); }

/* The wizard shows one question per screen; this page shows all of them, so the grid has to
   carry ~40 chips without turning into a column of buttons. Denser groups (style, tone, voice)
   drop to a compact chip; the relationship grid keeps the familiar two-up rhythm. */
.tool-choices-dense { grid-template-columns: repeat(2, 1fr); gap: 6px; }
.tool-choices-dense .choice { padding: 9px 11px; }
.tool-choices-dense .choice .lab { font-size: 13px; }
@media (min-width: 620px) {
  .tool-choices { grid-template-columns: repeat(3, 1fr); }
  .tool-choices-dense { grid-template-columns: repeat(4, 1fr); }
}
.tool-form .btn.wide { margin-top: 4px; }
.tool-foot { font-size: 12.5px; color: var(--ink-soft); text-align: center; margin: 10px 0 0; }

/* --- the reading half of the page ------------------------------------------------------- */
.tool-how, .tool-faq, .tool-links { max-width: 640px; margin: 26px auto 0; }
.tool-how h2, .tool-faq h2, .tool-links h2 {
  font-family: var(--font-display); font-size: 19px; font-weight: 600; margin: 0 0 12px; }
.tool-steps { margin: 0; padding-left: 20px; color: var(--ink-soft); font-size: 14px; line-height: 1.65; }
.tool-steps li { margin-bottom: 7px; }
.tool-steps b { color: var(--ink); font-weight: 600; }

/* Native <details> — no JS owns the FAQ, so it still opens on a page whose script died. */
.tool-faq-item { border-bottom: 1px solid var(--line); }
.tool-faq-q { position: relative; list-style: none; cursor: pointer; padding: 13px 30px 13px 0;
  font-weight: 600; font-size: 14.5px; color: var(--ink); }
.tool-faq-q::-webkit-details-marker { display: none; }
.tool-faq-q::after { content: "+"; position: absolute; right: 6px; top: 11px;
  font-size: 19px; font-weight: 400; color: var(--rose); transition: transform .18s ease; }
.tool-faq-item[open] .tool-faq-q::after { transform: rotate(45deg); }
.tool-faq-a { padding: 0 6px 14px 0; color: var(--ink-soft); font-size: 14px; line-height: 1.6; }

.tool-links-row { display: flex; flex-wrap: wrap; gap: 8px; }
.tool-links-row a { display: inline-block; padding: 7px 13px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--card); color: var(--plum);
  font-size: 13px; font-weight: 600; text-decoration: none; }
.tool-links-row a:hover { border-color: var(--rose); color: var(--rose-deep); }
