/* ============================================================
   EverBanyan Studio — premium botanical editorial
   Ivory paper, ink type, moss actions, restrained memory gold.
   No glassmorphism, no neon, no dashboard ornament.
   ============================================================ */

/* ============================================================
   Typography delivery (F-02) — the audit's open fallback system,
   self-hosted and deterministic. Tiempos/Söhne licensing is not
   available to this prototype, so per the Typography Verdict:

     Display + reading  →  Newsreader (variable; REAL 400–500 wght
                           band only — the file's axis is clamped, so
                           no other weight exists to synthesize — with
                           the full 6–72 optical axis giving the
                           display cut at name sizes and the text cut
                           at reading sizes), roman + true italic.
     Interface          →  Source Sans 3, real static 400 / 500 / 600.

   All faces ship as local WOFF2 (fonts/); the first-view faces are
   preloaded from index.html so the webfont is in hand at first paint —
   no swap-induced reflow. Nothing depends on locally installed fonts.
   ============================================================ */

/* Marcellus (Phase 1) — the memorial display face: an open, inscriptional
   roman that carries the person's name and the principal keepsake headings.
   Single true 400; nothing else exists to synthesize. Self-hosted; licence
   + provenance in fonts/PROVENANCE.md. */
@font-face {
  font-family: "Marcellus";
  src: url("fonts/marcellus-latin-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: "Marcellus";
  src: url("fonts/marcellus-latin-ext-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}

@font-face {
  font-family: "Newsreader";
  src: url("fonts/newsreader-latin-var.woff2") format("woff2");
  font-weight: 400 500;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: "Newsreader";
  src: url("fonts/newsreader-latin-ext-var.woff2") format("woff2");
  font-weight: 400 500;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: "Newsreader";
  src: url("fonts/newsreader-latin-var-italic.woff2") format("woff2");
  font-weight: 400 500;
  font-style: italic;
  font-display: swap;
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: "Newsreader";
  src: url("fonts/newsreader-latin-ext-var-italic.woff2") format("woff2");
  font-weight: 400 500;
  font-style: italic;
  font-display: swap;
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}

@font-face {
  font-family: "Source Sans 3";
  src: url("fonts/source-sans-3-latin-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: "Source Sans 3";
  src: url("fonts/source-sans-3-latin-ext-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: "Source Sans 3";
  src: url("fonts/source-sans-3-latin-500.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: "Source Sans 3";
  src: url("fonts/source-sans-3-latin-ext-500.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: "Source Sans 3";
  src: url("fonts/source-sans-3-latin-600.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: "Source Sans 3";
  src: url("fonts/source-sans-3-latin-ext-600.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}

:root {
  --ivory:        #F3F0E8;
  --ivory-raise:  #FAF8F2;   /* cards, sheets */
  --ivory-dip:    #ECE8DC;   /* wells, chips   */
  --ink:          #292B27;
  --ink-soft:     #5A5D53;
  --ink-faint:    #8A8C80;
  --moss:         #58704D;
  --moss-deep:    #465C3D;
  --moss-wash:    #EEF1E8;
  --walnut:       #6B4C32;
  --gold:         #B08D45;
  --gold-soft:    #C8A96A;
  --hairline:     rgba(41, 43, 39, 0.16);
  --hairline-soft:rgba(41, 43, 39, 0.09);

  /* One typography system, mapped by function (F-02 → Phase 1):
     memorial — Marcellus: the person's name, principal keepsake headings,
                the EverBanyan wordmark (the engraved voice);
     display  — Newsreader display cuts: studio headings, the price;
     reading  — Newsreader text cuts: dates, dedication, memory prose;
     interface — Source Sans 3: navigation, fields, controls, status. */
  --font-memorial: "Marcellus", "Newsreader", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  --font-display: "Newsreader", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  --font-reading: "Newsreader", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  --font-interface: "Source Sans 3", "Segoe UI", Arial, sans-serif;
  --serif: var(--font-reading);
  --sans:  var(--font-interface);
  --track-kicker: .08em;   /* ceiling for short categorical uppercase kickers */

  /* Phase 1-2 Finding 5 · ONE interface-control type token.
     Every general interface control — buttons, rail navigation, chips, stage
     tools, link-like controls, the pickers' card titles — reads from this, so
     the 16 px floor is a single fact rather than fourteen literals that can
     drift apart again. The body floor above is already 16 px; this brings the
     controls up to meet it.

     Scope, stated so it can be audited rather than argued: a CONTROL is an
     element that takes pointer/keyboard interaction, and the element that
     renders that control's visible label. Supporting prose inside a control —
     a card's description, a "Premium" badge, field help — is editorial text
     at the Phase 1 reading scale and is deliberately not raised; raising a
     three-line card description to 16 px would push the Gold gallery past
     twice its height for no legibility gain on a label nobody reads to act. */
  --fs-control: 16px;
  --control-h: 44px;        /* also the F-04 touch-target floor */

  --r-sm: 10px;
  --r-md: 14px;
  --r-lg: 18px;

  --shadow-card:  0 1px 2px rgba(41,43,39,.05), 0 10px 30px -18px rgba(41,43,39,.35);
  --shadow-float: 0 2px 6px rgba(41,43,39,.08), 0 24px 60px -30px rgba(41,43,39,.45);

  --topbar-h: 64px;
  --rail-w: 360px;
}

* { box-sizing: border-box; }

[hidden] { display: none !important; }

html, body {
  margin: 0;
  height: 100%;
}

html {
  /* Real faces only: never let the browser fake a weight or an italic. */
  font-synthesis: none;
  /* The variable optical axis picks the display cut at name sizes and
     the text cut at reading sizes — the Newsreader Display/Text pair. */
  font-optical-sizing: auto;
}

body {
  /* Interface body floor is 16 px (Phase 1 typography contract). */
  font: 400 16px/1.55 var(--font-interface);
  color: var(--ink);
  background: var(--ivory);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow: hidden;
}

.svg-defs { position: absolute; width: 0; height: 0; overflow: hidden; }

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

.skip-link {
  position: fixed; z-index: 200; top: -48px; left: 16px;
  padding: 10px 16px; border-radius: 0 0 var(--r-sm) var(--r-sm);
  background: var(--ink); color: var(--ivory); text-decoration: none;
  font-size: var(--fs-control); transition: top .15s ease;
}
.skip-link:focus-visible { top: 0; }

/* Checkboxes and radios carry no text of their own, but they ARE controls and
   the F5 audit measures every control it can reach — so they inherit the
   interface font instead of the UA's 13.33 px, and their box is stated
   explicitly so inheriting a larger font cannot change their size. */
input[type="checkbox"], input[type="radio"] {
  font: inherit;
  width: 18px; height: 18px;
  accent-color: var(--moss);
}

/* ---------- shared focus ---------- */
:focus-visible {
  outline: 2px solid var(--moss);
  outline-offset: 2px;
  border-radius: 4px;
}

/* ============================================================
   Top bar
   ============================================================ */

.topbar {
  position: relative; z-index: 30;
  height: var(--topbar-h);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  padding: 0 calc(20px + env(safe-area-inset-right, 0px)) 0 calc(24px + env(safe-area-inset-left, 0px));
  background: var(--ivory);
  border-bottom: 1px solid var(--hairline-soft);
}

.brand {
  display: flex; align-items: center; gap: 9px;
  color: var(--walnut);
}
.brand-mark { width: 20px; height: 20px; color: var(--moss); }
.brand-name {
  /* The wordmark carries the memorial face (Phase 1). */
  font-family: var(--font-memorial);
  font-size: 18px; letter-spacing: .035em;
}

.topbar-title {
  text-align: center;
  display: flex; align-items: baseline; gap: 10px;
  justify-content: center;
  min-width: 0;
}
.memorial-name {
  margin: 0;
  font: 400 21px/1.1 var(--font-memorial);
  color: var(--ink);
  letter-spacing: .02em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 40vw;
}
.memorial-dates {
  font-family: var(--font-reading);
  font-size: 15px;
  color: var(--ink-soft);
  white-space: nowrap;
}

.topbar-actions {
  display: flex; align-items: center; gap: 14px;
  justify-content: flex-end;
}

/* The one status channel. It is not a control in the resting state, but the
   failed / offline / conflict states ARE clickable (retryNow), so it is held
   to the control floor too. */
.save-state {
  font-size: var(--fs-control);
  color: var(--ink-faint);
  letter-spacing: .01em;
  display: inline-flex; align-items: center; gap: 7px;
  white-space: nowrap;
  max-width: 30vw; overflow: hidden; text-overflow: ellipsis;
}
.save-state::before {
  content: "";
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--moss);
  opacity: .85;
}
.save-state.saving::before { background: var(--gold); }

/* ---------- buttons ---------- */

.btn {
  font: 500 var(--fs-control)/1 var(--font-interface);
  letter-spacing: .015em;
  border-radius: 12px;
  padding: 0 18px;
  height: var(--control-h);
  min-height: var(--control-h);
  border: 1px solid transparent;
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}
.btn-primary {
  background: var(--moss); color: #FBFAF5;
  border-color: var(--moss);
}
.btn-primary:hover { background: var(--moss-deep); border-color: var(--moss-deep); }
.btn-secondary {
  background: transparent; color: var(--moss-deep);
  border-color: var(--moss);
}
.btn-secondary:hover { background: var(--moss-wash); }
.btn-ghost {
  background: transparent; color: var(--ink-soft);
  border-color: var(--hairline);
}
.btn-ghost:hover { color: var(--ink); border-color: var(--ink-soft); }
.btn-block { width: 100%; }
.btn-icon { width: 15px; height: 15px; fill: currentColor; }
.btn[disabled] { opacity: .45; cursor: default; pointer-events: none; }

.linklike {
  background: none; border: 0; padding: 8px 2px;
  font: 500 var(--fs-control)/1.2 var(--font-interface);
  color: var(--moss-deep);
  cursor: pointer;
  border-bottom: 1px solid transparent;
}
.linklike:hover { border-bottom-color: currentColor; }
.linklike-quiet { color: var(--ink-faint); }
.linklike-quiet:hover { color: var(--walnut); }
/* Prompt 32 · the edit sheet's re-place affordance, above the button row */
.mem-replace { display: block; margin: 2px 0 6px; }

/* ============================================================
   Layout
   ============================================================ */

.studio {
  position: relative;
  display: grid;
  grid-template-columns: var(--rail-w) minmax(0, 1fr);
  height: calc(100dvh - var(--topbar-h));
}

/* ============================================================
   Editorial rail
   ============================================================ */

.rail {
  position: relative; z-index: 20;
  display: flex; flex-direction: column;
  background: var(--ivory);
  border-right: 1px solid var(--hairline-soft);
  min-height: 0;
}

.sheet-grip { display: none; }

.rail-nav {
  display: flex; flex-direction: column;
  padding: 18px 28px 6px;
}
.rail-nav-item {
  appearance: none; background: none; border: 0;
  border-top: 1px solid var(--hairline-soft);
  text-align: left;
  padding: 12px 2px;
  min-height: var(--control-h);
  /* Sentence case, quiet weight — navigation is wayfinding, not signage. */
  font: 500 var(--fs-control)/1.2 var(--font-interface);
  letter-spacing: .01em;
  color: var(--ink-soft);
  cursor: pointer;
  display: flex; align-items: center; gap: 8px;
  transition: color .15s ease;
}
.rail-nav-item:first-child { border-top: 0; }
.rail-nav-item:hover { color: var(--walnut); }
.rail-nav-item[aria-current="true"] { color: var(--moss-deep); font-weight: 600; }
.rail-nav-item[aria-current="true"]::before {
  content: "";
  width: 14px; height: 1.5px;
  background: var(--moss);
  flex: none;
}
.nav-count {
  margin-left: auto;
  font: 500 var(--fs-control)/1 var(--font-interface);
  letter-spacing: .02em;
  color: var(--gold);
}

.rail-body {
  flex: 1; min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 10px 28px 28px;
  scrollbar-width: thin;
  scrollbar-color: var(--hairline) transparent;
}

.rail-foot {
  padding: 12px 28px 14px;
  border-top: 1px solid var(--hairline-soft);
  font-size: 12px;
  color: var(--ink-faint);
  display: flex; align-items: center; gap: 8px;
}
.rail-foot-dot {
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--gold); opacity: .8;
}

/* ---------- panels ---------- */

.panel-head { margin: 14px 0 20px; }
.panel-title {
  margin: 0 0 6px;
  font: 500 24px/1.15 var(--font-display);
  color: var(--ink);
}
.panel-sub {
  margin: 0;
  font-size: 14px; line-height: 1.55;
  color: var(--ink-soft);
}

.panel-footnote {
  margin: 22px 0 4px;
  padding-top: 14px;
  border-top: 1px solid var(--hairline-soft);
  font-size: 13px; line-height: 1.6;
  color: var(--ink-faint);
}

/* ---------- fields ---------- */

.field { margin: 0 0 18px; }
.field-label, .group-label, .subgroup-label {
  display: block;
  /* Short categorical kickers: uppercase is allowed here, but at a
     legible 12 px and restrained tracking (≤ .08em). */
  font: 600 12px/1.2 var(--font-interface);
  letter-spacing: var(--track-kicker);
  text-transform: uppercase;
  color: var(--walnut);
  margin: 0 0 8px;
}
.field-optional {
  font-weight: 500; letter-spacing: .02em; text-transform: none;
  color: var(--ink-faint);
}
.field-help {
  margin: 6px 0 0;
  font-size: 13px; line-height: 1.5; color: var(--ink-faint);
}

/* Prompt 18 A4 · the nameplate portrait is included; leaf photos remain the
   paid pack. Both stay honest when the server is unavailable. */
.photo-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.photo-add {
  flex: 1 1 auto; min-width: 0;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  min-height: var(--control-h);
  padding: 12px 16px;
  border: 1px dashed var(--hairline);
  border-radius: var(--r-md);
  background: transparent;
  font: 500 var(--fs-control)/1.35 var(--font-interface);
  color: var(--moss-deep);
  cursor: pointer;
  transition: border-color .15s ease, background-color .15s ease, color .15s ease;
}
.photo-add:hover:not(:disabled) { border-color: var(--moss); background: var(--moss-wash); }
.photo-add:disabled { color: var(--ink-faint); cursor: not-allowed; opacity: .7; }
.photo-thumb {
  flex: none;
  width: 56px; height: 56px; border-radius: var(--r-sm);
  overflow: hidden; background: var(--ivory-dip);
  box-shadow: inset 0 0 0 1px var(--hairline);
  display: block;
}
.photo-thumb[hidden] { display: none; }
.photo-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.photo-thumb.portrait-guide {
  position: relative;
  width: 132px;
  height: 92px;
  background: #e8e2d6;
}
.photo-thumb.portrait-guide img {
  object-fit: contain;
}
.photo-thumb.portrait-guide::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 42%;
  height: 76%;
  transform: translate(-50%, -50%);
  border: 1.5px dashed rgba(251, 250, 245, .95);
  border-radius: 42% 42% 32% 32% / 34% 34% 20% 20%;
  box-shadow: 0 0 0 999px rgba(31, 28, 23, .12);
  pointer-events: none;
}

/* §7E §4 · the in-scene nameplate portrait photo, above the name. [hidden]
   keeps it out of layout entirely (no faked frame when empty); populated with an
   APPROVED portrait it is a small, quiet framed image — restrained,
   never louder than the name it sits above. */
.plate-photo[hidden] { display: none; }
.plate-photo {
  display: block;
  margin: 0 auto .55rem;
  /* The size is a VARIABLE so fitPlate() can step the circle down on a short
     stage — the portrait is subordinate to the name and yields room first. */
  width: var(--plate-photo-size, clamp(62px, 13vmin, 104px));
  height: var(--plate-photo-size, clamp(62px, 13vmin, 104px));
  border-radius: 50%;
  overflow: hidden;
  background: var(--ivory-dip);
  box-shadow: var(--shadow-card), 0 0 0 3px var(--ivory-raise);
}
.plate-photo img { display: block; width: 100%; height: 100%; object-fit: cover; }

.input {
  width: 100%;
  font: 400 16px/1.4 var(--font-interface);
  color: var(--ink);
  background: var(--ivory-raise);
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
  padding: 10px 12px;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.input:hover { border-color: rgba(41,43,39,.3); }
.input:focus { outline: none; border-color: var(--moss); box-shadow: 0 0 0 1px var(--moss); }
.input::placeholder { color: var(--ink-faint); }
textarea.input { resize: vertical; min-height: 44px; }

.input-serif {
  /* User-authored memorial text is set in the reading face as typed. */
  font-family: var(--font-reading);
  font-size: 17px;
}

.dates-row { display: flex; align-items: center; gap: 10px; }
.input-year { width: 96px; text-align: center; }
.dates-dash { color: var(--ink-faint); font-family: var(--serif); }

/* ---------- groups ---------- */

.group {
  margin: 0 0 8px; padding: 18px 0 14px;
  border: 0; border-top: 1px solid var(--hairline-soft);
}
.group:first-of-type { border-top: 0; padding-top: 6px; }
.group-lede {
  margin: -2px 0 12px;
  font-size: 13.5px; line-height: 1.55; color: var(--ink-soft);
}
.group-note {
  margin: 12px 0 0;
  font-size: 13px; line-height: 1.6;
  color: var(--ink-faint);
}
.subgroup { margin-top: 16px; }

/* §7C.4/§7C.5 · the nameplate controls (show/omit + shape) sit above the
   memorial-object cards. A checkbox row and a clear separation keep the shape
   chips from crowding the cards below. */
.toggle-row {
  display: flex; align-items: center; gap: 10px;
  min-height: var(--control-h); cursor: pointer;
  font: 500 var(--fs-control)/1.3 var(--font-interface); color: var(--ink);
}
.toggle-row input {
  width: 18px; height: 18px; flex: none; cursor: pointer;
  accent-color: var(--walnut);
}
#nameplate-wrap {
  padding-bottom: 20px;
  margin-bottom: 16px;
  border-bottom: 1px solid var(--hairline-soft);
}
#nameplate-shape-wrap[hidden] { display: none; }
#nameplate-shape-note[hidden],
#object-variant-wrap[hidden],
#legacy-object-note[hidden] { display: none; }

/* Development-only compatibility controls for Prompt 12's immutable gate.
   They are installed only under ?qa=1 and the production build strips that
   branch. The pet-first product interface never exposes this picker. */
.qa-legacy-picker {
  position: fixed !important;
  inset: 84px auto auto 16px;
  width: 320px;
  height: 180px;
  margin: 0 !important;
  opacity: .001;
  z-index: 1000;
  overflow: visible;
}

/* ---------- choice cards ---------- */

.choice-stack { display: flex; flex-direction: column; gap: 10px; }

.choice-card {
  position: relative;
  display: block;
  cursor: pointer;
  background: var(--ivory-raise);
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  padding: 13px 44px 13px 16px;
  transition: border-color .15s ease, background-color .15s ease;
}
.choice-card:hover { border-color: rgba(41,43,39,.34); }
.choice-card input {
  position: absolute; opacity: 0; pointer-events: none;
}
.choice-card.selected {
  background: var(--moss-wash);
  border-color: var(--moss);
  box-shadow: 0 0 0 1px var(--moss);
}
.choice-card:has(input:focus-visible) {
  outline: 2px solid var(--moss); outline-offset: 2px;
}
/* The card's visible LABEL — held to the control floor (F5). The description
   below it is editorial supporting text, not the control's name. */
.choice-title {
  font: 500 var(--fs-control)/1.3 var(--font-interface);
  color: var(--ink);
  display: flex; align-items: baseline; gap: 8px;
}
.choice-title .t-serif { font-family: var(--font-display); font-size: 17.5px; }
.choice-form {
  font: 600 12px/1.2 var(--font-interface);
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--walnut);
}
.choice-desc {
  margin-top: 3px;
  font-size: 13px; line-height: 1.5;
  color: var(--ink-soft);
}
.choice-tag {
  display: inline-block; margin-left: 8px;
  font: 600 12px/1 var(--font-interface); letter-spacing: .06em; text-transform: uppercase;
  color: var(--gold);
  border: 1px solid rgba(176,141,69,.4);
  border-radius: 999px; padding: 3px 8px;
  vertical-align: middle;
}
.choice-check {
  position: absolute; top: 50%; right: 14px;
  transform: translateY(-50%);
  width: 18px; height: 18px;
  color: var(--moss);
  opacity: 0;
  transition: opacity .15s ease;
}
.choice-card.selected .choice-check { opacity: 1; }

/* ---------- chips ---------- */

.chip-row { display: flex; flex-wrap: wrap; gap: 8px; }

/* §7C · "a path to the tree" chips carry a subtle angled accent on the side the
   path arrives from — a quiet visual cue, no extra markup. */
.approach-chip[data-dir="left"] { padding-left: 28px; }
.approach-chip[data-dir="right"] { padding-right: 28px; }
.approach-chip[data-dir="left"]::before,
.approach-chip[data-dir="right"]::after {
  content: ""; position: absolute; top: 50%;
  width: 13px; height: 2px; border-radius: 2px;
  background: currentColor; opacity: .5;
}
.approach-chip[data-dir="left"]::before { left: 10px; transform: translateY(-50%) rotate(28deg); }
.approach-chip[data-dir="right"]::after { right: 10px; transform: translateY(-50%) rotate(-28deg); }
.approach-chip.selected[data-dir]::before,
.approach-chip.selected[data-dir]::after { opacity: .85; }
.chip {
  position: relative;
  display: inline-flex; align-items: center;
  min-height: 44px;
  padding: 0 16px;
  border-radius: 999px;
  border: 1px solid var(--hairline);
  background: var(--ivory-raise);
  font: 500 var(--fs-control)/1.2 var(--font-interface);
  color: var(--ink-soft);
  cursor: pointer;
  transition: all .15s ease;
}
.chip:hover { border-color: rgba(41,43,39,.34); color: var(--ink); }
.chip input { position: absolute; opacity: 0; pointer-events: none; }
.chip.selected {
  background: var(--moss); border-color: var(--moss);
  color: #FBFAF5;
}
.chip:has(input:focus-visible) { outline: 2px solid var(--moss); outline-offset: 2px; }

/* ---------- emblem / socket grids ---------- */

.emblem-grid {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px;
}
.emblem-card {
  position: relative;
  min-height: 48px;
  display: flex; align-items: center; justify-content: center;
  text-align: center;
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
  background: var(--ivory-raise);
  font: 500 var(--fs-control)/1.25 var(--font-interface);
  color: var(--ink-soft);
  cursor: pointer;
  padding: 6px 8px;
  transition: all .15s ease;
}
.emblem-card:hover { border-color: rgba(41,43,39,.34); color: var(--ink); }
.emblem-card input { position: absolute; opacity: 0; pointer-events: none; }
.emblem-card.selected {
  border-color: var(--moss); background: var(--moss-wash);
  color: var(--moss-deep); box-shadow: 0 0 0 1px var(--moss);
}
.emblem-card:has(input:focus-visible) { outline: 2px solid var(--moss); outline-offset: 2px; }

.socket-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
}
.socket-card {
  position: relative;
  display: flex; align-items: center; gap: 10px;
  min-height: 48px;
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
  background: var(--ivory-raise);
  font: 500 var(--fs-control)/1.3 var(--font-interface);
  color: var(--ink-soft);
  cursor: pointer;
  padding: 8px 12px;
  transition: all .15s ease;
}
.socket-card:hover { border-color: rgba(41,43,39,.34); color: var(--ink); }
.socket-card input { position: absolute; opacity: 0; pointer-events: none; }
.socket-card.selected {
  border-color: var(--moss); background: var(--moss-wash);
  color: var(--moss-deep); box-shadow: 0 0 0 1px var(--moss);
}
.socket-card:has(input:focus-visible) { outline: 2px solid var(--moss); outline-offset: 2px; }
.socket-dot {
  flex: none;
  width: 22px; height: 22px;
  border: 1px solid currentColor; border-radius: 50%;
  position: relative; opacity: .8;
}
.socket-dot::after {
  content: ""; position: absolute;
  width: 6px; height: 6px; border-radius: 50%;
  background: currentColor;
  top: var(--dy, 50%); left: var(--dx, 50%);
  transform: translate(-50%, -50%);
}

/* ============================================================
   Memories rail
   ============================================================ */

.mem-list { list-style: none; margin: 18px 0 0; padding: 0; }
.mem-item { border-top: 1px solid var(--hairline-soft); }
.mem-item:first-child { border-top: 0; }
.mem-item-btn {
  display: block; width: 100%;
  background: none; border: 0; text-align: left;
  padding: 13px 10px 13px 14px;
  cursor: pointer;
  border-left: 2px solid transparent;
  transition: background-color .15s ease, border-color .15s ease;
}
.mem-item-btn:hover { background: var(--ivory-raise); }
.mem-item.selected .mem-item-btn {
  background: var(--ivory-raise);
  border-left-color: var(--gold);
}
.mem-quote {
  margin: 0;
  /* Memory prose is roman by default (F-08): lived recollection reads as
     prose, not as a decorative quotation. */
  font: 400 16px/1.5 var(--font-reading);
  color: var(--ink);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.mem-by {
  margin: 6px 0 0;
  font: 500 13px/1.4 var(--font-interface);
  letter-spacing: .01em;
  color: var(--walnut);
}
.mem-by .mem-date { color: var(--ink-faint); }

/* Prompt 31 A3 · Option B — the list's promise that it points AT the tree.
   Quiet at rest so twelve of them do not shout, and it firms up under the
   pointer, the keyboard and on the open memory. Always present rather than
   hover-only: on a phone there is no hover, and an affordance that only
   appears to a mouse is not an affordance. No motion of its own — the camera
   flight it announces already respects reduced-motion (Prompt 18 A5). */
/* Prompt 12's type floor caught the first draft of this and was right twice:
   11px is below the 12px minimum, and UPPERCASE is reserved for categorical
   kickers — "find it on the tree" is a sentence, and setting a sentence in
   caps is shouting an invitation. Sentence case at 12px, quiet by opacity
   rather than by shrinking it under the floor. */
.mem-find {
  display: block;
  margin: 5px 0 0;
  font: 500 12px/1.35 var(--font-interface);
  color: var(--ink-faint);
  opacity: .62;
  transition: opacity .15s ease, color .15s ease;
}
.mem-item-btn:hover .mem-find,
.mem-item-btn:focus-visible .mem-find,
.mem-item.selected .mem-find { opacity: 1; color: var(--walnut); }

/* The same promise on the visitor's list, stated once above the entries. */
.visitor-list-note {
  margin: 0;
  padding: 8px 16px 6px;
  font: 400 12px/1.45 var(--font-interface);
  color: var(--ink-faint);
  border-bottom: 1px solid var(--hairline-soft);
}

.mem-editor-title {
  margin: 12px 0 16px;
  font: 500 20px/1.2 var(--font-display);
}
.mem-place-lede {
  margin: 0 0 14px;
  font-size: 13.5px; line-height: 1.55; color: var(--ink-soft);
}
.btn-row {
  display: flex; justify-content: space-between; gap: 10px;
  margin-top: 20px;
}

.place-card .choice-title { gap: 10px; }
.place-badge {
  flex: none;
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--gold);
  color: #FFFDF6;
  font: 600 12px/22px var(--font-interface);
  text-align: center;
}

/* ============================================================
   Stage
   ============================================================ */

.stage {
  position: relative;
  min-width: 0; min-height: 0;
  background:
    radial-gradient(120% 90% at 50% 30%, #EFE9DB 0%, #E6DFCE 100%);
  overflow: hidden;
  /* Phase 7C F4 · the stage's bottom furniture crowds by STAGE width, not by
     viewport width — at 801×800 the viewport is "desktop" while the stage is
     only 441 px. Making the stage a size container lets the compact rules key
     off the number that actually decides whether the hint, the tools and the
     metadata fit on one line. */
  container-type: inline-size;
  container-name: stage;
}

.stage-mount, .stage-mount iframe.stage-renderer {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  border: 0;
}
.stage-renderer { display: block; }

.stage-gesture {
  position: absolute; inset: 0; z-index: 2;
  cursor: grab;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}
.stage-gesture.dragging { cursor: grabbing; }
.stage-gesture:focus-visible {
  outline: 2px solid var(--moss);
  outline-offset: -3px;
  border-radius: 0;
}

/* ---------- nameplate (top of stage, as in the concept) ---------- */

/* Phase 9 · the identity block carries TWO deterministic treatments, switched
   on the stage container (never per element) from two signals the Studio
   already has: the scene light, and prompt 11's `turtleview {under}` crossing.

     on-light  golden-hour · noon, pulled back → today's dark keepsake ink
     on-dark   dusk, OR under the canopy in any light → ivory over a soft
               scrim, because dark ink dies against the Blue Dusk sky and
               against foliage

   Both are colour + one gradient: no per-frame work, and no blur filter
   anywhere (backdrop-filter is expensive on phones — Phase 9 A2-speed). */
.stage-plate {
  position: absolute; z-index: 3;
  top: var(--plate-top, 26px); left: 50%;
  /* Two shifts ride the same transform so the under-canopy relocation is a
     transition rather than a jump: --plate-shift-y drops the block into the
     lower band, --plate-scale makes it compact once it is there. */
  transform: translateX(-50%) translateY(var(--plate-shift-y, 0px)) scale(var(--plate-scale, 1));
  transform-origin: top center;
  transition: transform .42s cubic-bezier(.3,.9,.3,1), color .42s ease;
  width: min(72%, 640px);
  text-align: center;
  pointer-events: none;
  /* the ramp (fitPlate) writes --plate-name-size; the treatments write the
     ink and halo triples every line below reads from */
  --plate-name-size: clamp(56px, 4.5vw, 64px);
  --plate-ink: 41 43 39;
  --plate-halo: 250 248 242;
  --plate-glow: 0 0 10px rgb(var(--plate-halo) / .55), 0 0 3px rgb(var(--plate-halo) / .8);
  color: rgb(var(--plate-ink));
}
.stage[data-plate-treatment="on-dark"] .stage-plate {
  --plate-ink: 246 243 234;
  --plate-halo: 12 16 22;
  --plate-glow: 0 1px 3px rgb(var(--plate-halo) / .85), 0 0 14px rgb(var(--plate-halo) / .6);
}

/* One soft gradient behind the words. Painted only in the on-dark treatment,
   below the text (z-index -1 inside the plate's own stacking context), inert
   to the pointer, and never a blur. */
.plate-scrim {
  position: absolute; inset: -18px -26px -22px;
  z-index: -1;
  pointer-events: none;
  border-radius: 32px;
  opacity: 0;
  transition: opacity .42s ease;
  background: radial-gradient(118% 128% at 50% 46%,
    rgb(var(--plate-halo) / .90) 0%,
    rgb(var(--plate-halo) / .80) 40%,
    rgb(var(--plate-halo) / .42) 72%,
    rgb(var(--plate-halo) / 0) 100%);
}
.stage[data-plate-treatment="on-dark"] .plate-scrim { opacity: 1; }

/* Under the canopy the name may not sit buried in the leaves at the top
   (Phase 9's suspended I4). The owner-approved choice here is RELOCATE, not
   fade: the same gate also requires the name and dates to hold AA against
   what is actually behind them, and a block faded to ~0.15 opacity cannot
   meet any contrast bar. So the identity travels — calmly, on the transform
   above — to a compact scrimmed band low on the stage, clear of the crown,
   and returns on `under=false` and on Reset. --plate-shift-y is measured by
   the Studio from the real stage and plate rectangles. */
.stage[data-plate-view="under"] .stage-plate { --plate-scale: .62; }
.stage[data-plate-view="under"] .plate-over { display: none; }
/* The keepsake hierarchy (Phase 1):
     LIVING TREE TRIBUTE      · kicker, 13–14px tracked caps
     MILO                     · Marcellus, 56–64px desktop / 38–44px mobile
     2012 — 2026              · reading serif, 18px
     BANYAN · SEED 863        · species/seed line, 13–14px tracked caps
   Uppercase is visual (text-transform); the DOM keeps entered case. */
/* Phase 1-2 Finding 6 · the kicker measured 3.69:1 over the golden sky and
   4.05:1 over the noon sky, both under AA's 4.5:1 for 13 px type. The cause
   was the 72 % ink wash, which spends contrast on a refinement nobody can
   see at this size. It now renders at full treatment ink with the same soft
   halo the rest of the block carries — so it clears AA over both light skies
   AND over dark foliage, instead of only over one of them. */
.plate-kicker, .plate-form {
  display: block;
  font: 600 13px/1.4 var(--font-interface);
  letter-spacing: var(--track-kicker); text-transform: uppercase;
  color: rgb(var(--plate-ink));
  text-shadow: var(--plate-glow);
  margin: 0 0 6px;
}
.plate-form {
  margin: 8px 0 0;
  color: rgb(var(--plate-ink) / .88);
}
.plate-over {
  display: none;
  margin: 12px 0 0;
  /* The dedication is the one deliberate italic accent (F-08); 18–20px
     reading serif per the Phase 1 scale. */
  font: italic 400 18.5px/1.55 var(--font-reading);
  color: rgb(var(--plate-ink) / .92);
  text-shadow: var(--plate-glow);
  overflow-wrap: break-word;
}
.plate-name {
  margin: 0;
  /* Phase 5-6 F3 · the size is a VARIABLE, written by fitPlate() from the
     measured block height. A short name keeps the Phase 1 scale exactly; a
     60-character one steps down until the identity block fits its share of
     the stage instead of swallowing it. */
  font: 400 var(--plate-name-size)/1.06 var(--font-memorial);
  letter-spacing: .045em;
  text-transform: uppercase;
  overflow-wrap: break-word;
  text-wrap: balance;
  text-shadow: var(--plate-glow);
}
.plate-dates {
  margin: 5px 0 0;
  font: 400 var(--plate-dates-size, 18px)/1.4 var(--font-reading);
  letter-spacing: .055em;
  /* solid treatment ink: the dates are held to the same AA bar as the name */
  color: rgb(var(--plate-ink));
  text-shadow: var(--plate-glow);
}
.plate-meta {
  position: absolute; z-index: 3;
  left: 50%; transform: translateX(-50%);
  bottom: 20px;
  pointer-events: none;
  margin: 0;
  display: flex; align-items: center; gap: 16px;
  font: 600 12px/1 var(--font-interface);
  letter-spacing: var(--track-kicker); text-transform: uppercase;
  color: rgba(243, 240, 232, .85);          /* the low stage sits over dark soil */
  text-shadow: 0 1px 8px rgba(30, 22, 12, .35);
  white-space: nowrap;
}
.plate-meta-item { display: inline-flex; align-items: center; gap: 7px; }
.plate-meta-item svg { width: 13px; height: 13px; color: rgba(243,240,232,.72); }
.plate-meta-sep { width: 1px; height: 12px; background: rgba(243,240,232,.4); }

/* ---------- markers (retired in Phase 2) ----------
   The DOM marker layer is no longer used for memories — the V5 renderer
   draws every glowing leaf at its stable branch anchor, depth-tested
   against the real canopy. #stage-markers stays in the markup and these
   rules stay harmless for it; the Studio keeps the layer permanently
   hidden. */

.stage-markers {
  position: absolute; inset: 0; z-index: 4;
  pointer-events: none;
}
.stage-markers[hidden] { display: none; }

.marker {
  position: absolute;
  width: 44px; height: 44px;
  transform: translate(-50%, -50%);
  background: none; border: 0; padding: 0;
  cursor: pointer;
  pointer-events: auto;
  display: grid; place-items: center;
  opacity: var(--leaf-fade, 1);
}
.marker[hidden] { display: none; }
.marker::before {
  content: ""; position: absolute;
  width: calc(var(--leaf-px, 15px) * 1.8);
  height: calc(var(--leaf-px, 15px) * 1.8);
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(200,169,106,.55) 0%, rgba(200,169,106,.22) 45%, rgba(200,169,106,0) 72%);
}
.marker svg {
  position: relative;
  width: var(--leaf-px, 15px); height: var(--leaf-px, 15px);
  fill: var(--gold-soft);
  stroke: rgba(107,76,50,.55); stroke-width: .75;
  filter: drop-shadow(0 1px 2px rgba(60,45,20,.35));
  transition: fill .15s ease;
}
.marker:hover svg, .marker.selected svg { fill: #D8BC7E; }
.marker.selected::before {
  animation: halo 3.2s ease-in-out infinite;
}
.marker:focus-visible { outline: 2px solid var(--gold); outline-offset: -6px; border-radius: 50%; }

.marker.candidate::before {
  width: calc(var(--leaf-px, 15px) * 2.1);
  height: calc(var(--leaf-px, 15px) * 2.1);
}
.marker.candidate .marker-badge {
  position: absolute; top: 0; right: 0;
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--ivory-raise);
  border: 1px solid var(--gold);
  color: var(--walnut);
  font: 600 12px/19px var(--font-interface);
  text-align: center;
}
.marker.candidate.chosen::before { animation: halo 2.2s ease-in-out infinite; }

@keyframes halo {
  0%, 100% { transform: scale(1);    opacity: .9; }
  50%      { transform: scale(1.18); opacity: 1;  }
}

/* ---------- the memory folio (Phase 2) ----------
   A keepsake page beside the tree, not a popup: warm vellum, a fine
   gold-thread border, restrained paper grain (pure CSS — no assets),
   the small gold leaf and its MEMORY LEAF label, 18–20 px reading serif.
   No modal scrim — orbiting continues while it is open. The renderer's
   glowing leaf marks the place; this card only presents the words. */

.memory-card {
  position: absolute; z-index: 6;
  left: 26px; bottom: 84px;
  width: min(360px, calc(100% - 52px));
  max-height: calc(100% - 168px);
  display: flex; flex-direction: column;
  background:
    radial-gradient(150% 130% at 16% 6%, rgba(255,252,242,.85) 0%, rgba(249,241,222,0) 55%),
    repeating-linear-gradient(94deg, rgba(160,128,72,.03) 0 3px, rgba(160,128,72,0) 3px 7px),
    repeating-linear-gradient(4deg, rgba(120,94,52,.022) 0 2px, rgba(120,94,52,0) 2px 6px),
    linear-gradient(168deg, #FAF4E4 0%, #F6EDD8 58%, #F1E6CC 100%);
  border: 1px solid rgba(168,132,66,.42);
  outline: 1px solid rgba(255,250,236,.6);
  outline-offset: -3px;
  border-radius: 14px;
  box-shadow: 0 2px 6px rgba(84,62,26,.10), 0 26px 56px -30px rgba(84,62,26,.5);
  padding: 12px 18px 14px;
}
.memory-card[hidden] { display: none; }
.memory-card-rule { display: none; }
.memory-card-head {
  display: flex; align-items: center; gap: 7px;
  flex: none;                                    /* the header never scrolls away */
  font: 600 12px/1.2 var(--font-interface);
  letter-spacing: var(--track-kicker); text-transform: uppercase;
  color: var(--walnut);
  padding-bottom: 6px;
  border-bottom: 1px solid rgba(168,132,66,.28);
}
.memory-card-leaf { width: 12px; height: 12px; fill: var(--gold); flex: none; }
.memory-card-close {
  /* A true 44 × 44 close target (F-04), visually quiet, never clipped. */
  margin-left: auto; margin-right: -10px;
  flex: none;
  width: 44px; height: 44px;
  display: grid; place-items: center;
  background: none; border: 0; cursor: pointer;
  border-radius: 50%;
  font-size: 20px; line-height: 1;
  color: var(--ink-faint);
  padding: 0;
}
.memory-card-close:hover { color: var(--ink); }
/* B-C2 · the leaf photo, inside the folio. Restrained: a small framed plate
   above the words, never louder than the memory it belongs to. */
.memory-card-photo {
  flex: none;
  margin: 12px 0 0;
  border-radius: 10px;
  overflow: hidden;
  background: rgba(120,94,52,.06);
  box-shadow: inset 0 0 0 1px rgba(168,132,66,.34);
  max-height: 46%;
  display: flex;
}
.memory-card-photo[hidden] { display: none; }
.memory-card-photo img {
  display: block; width: 100%; height: auto; max-height: 100%;
  object-fit: cover;
}

.memory-card-text {
  margin: 12px 0 0;
  /* Memory prose at the Phase 1/2 reading scale: 18–20 px serif, roman. */
  font: 400 18.5px/1.6 var(--font-reading);
  color: var(--ink);
  overflow-wrap: break-word;
  overflow-y: auto;          /* long memories scroll inside the folio;  */
  min-height: 0;             /* the head and close control never leave  */
}
.memory-card-text:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 3px;
  border-radius: 4px;
}
.memory-card-by {
  margin: 8px 0 0;
  flex: none;
  font: 500 13px/1.45 var(--font-interface);
  letter-spacing: .01em;
  color: var(--walnut);
}
.memory-card-actions {
  display: flex; gap: 14px;
  flex: none;
  margin-top: 8px; padding-top: 6px;
  border-top: 1px solid var(--hairline-soft);
}

/* ---------- stage tools & hint ---------- */

.stage-tools {
  position: absolute; z-index: 5;
  right: 18px; bottom: 16px;
  display: flex; flex-direction: column; align-items: flex-end; gap: 8px;
}
.stage-tool {
  display: inline-flex; align-items: center; gap: 8px;
  min-height: var(--control-h);
  padding: 0 14px;
  border-radius: 999px;
  border: 1px solid var(--hairline);
  background: color-mix(in srgb, var(--ivory-raise) 88%, transparent);
  font: 500 var(--fs-control)/1.2 var(--font-interface);
  color: var(--ink-soft);
  cursor: pointer;
  transition: all .15s ease;
}
.stage-tool:hover { color: var(--ink); border-color: rgba(41,43,39,.35); }
.stage-tool svg { width: 15px; height: 15px; }

.stage-hint {
  position: absolute; z-index: 3;
  left: 20px; bottom: 16px;
  margin: 0;
  font: 400 13px/1.45 var(--font-interface);
  color: rgba(243, 240, 232, .82);
  text-shadow: 0 1px 6px rgba(30, 22, 12, .4);
  pointer-events: none;
  transition: opacity .6s ease;
}
.stage-hint.faded { opacity: 0; }

/* ---------- visitor (preview) pieces ---------- */

.visitor-actions {
  position: absolute; z-index: 5;
  left: 20px; bottom: 16px;
}
.btn-stage {
  background: color-mix(in srgb, var(--ivory-raise) 90%, transparent);
  border: 1px solid var(--hairline);
  color: var(--ink);
  border-radius: 999px;
}
.btn-stage:hover { border-color: var(--gold); }
.btn-stage .btn-icon { fill: var(--gold); }

.visitor-list {
  position: absolute; z-index: 6;
  right: 18px; top: 18px;
  width: 300px;
  max-height: min(64vh, 560px);
  display: flex; flex-direction: column;
  background: var(--ivory-raise);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-float);
  overflow: hidden;
}
.visitor-list-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 6px 16px 4px;
  font: 600 12px/1.2 var(--font-interface);
  letter-spacing: var(--track-kicker); text-transform: uppercase;
  color: var(--walnut);
  border-bottom: 1px solid var(--hairline-soft);
}
.visitor-list-head .memory-card-close { margin-right: -12px; }
.mem-list-visitor { overflow-y: auto; margin: 0; padding: 2px 6px 8px; }

/* ---------- the preview control set (Phase 1-2 F7 · Phase 7C F5) ----------
   ONE layout, two shapes. On a wide stage it is the familiar pill: a label and
   the actions on one line. On a narrow one it wraps to two rows inside the
   safe area — because the previous fixed no-wrap row put "Publish for
   visitors" 49 px past the right edge of a 390 px viewport, where no scroll
   could reach it. Wrapping (rather than shrinking type or truncating labels)
   is what keeps BOTH findings fixed by the same rule: any control that joins
   .preview-bar-actions later inherits the same behaviour. */
.preview-bar {
  position: fixed; z-index: 60;
  top: 14px; left: 16px;
  max-width: calc(100vw - 32px);
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px 14px;
  background: var(--ivory-raise);
  border: 1px solid var(--hairline);
  border-radius: 999px;
  box-shadow: var(--shadow-card);
  padding: 6px 8px 6px 18px;
}
.preview-bar-label {
  /* A whole sentence is not a kicker: sentence case, readable size. */
  font: 500 15px/1.25 var(--font-interface);
  letter-spacing: .01em;
  color: var(--ink-soft);
  min-width: 0;
}
.preview-bar-actions {
  display: flex; align-items: center; gap: 10px;
  min-width: 0; flex-wrap: wrap;
}
.preview-bar .btn { padding: 0 14px; }

/* Prompt 52 · V-1 (DESIGN.md §5.4) — the neutral boot state. ONE document is served to the keeper and
   to a stranger opening a memorial link, and the shell does not know which it is until studio.js names
   the mode as its first act. Until then it paints its ground and neither chrome; the one thing it may
   show is the card (§4.3), over that neutral state. No transition may run in it: a control that carries
   `transition: all` (the stage tools do) would fade into hiding and stay painted for its 150 ms —
   measured by Prompt 50 (review/fix/50/2026-09-20, A4b), two buttons in the first frame. */
body[data-mode="boot"] > :not(#tab-card) { visibility: hidden; }
body[data-mode="boot"] *, body[data-mode="boot"] *::before, body[data-mode="boot"] *::after { transition: none !important; }

/* preview mode: the studio recedes, the memorial remains */
body[data-mode="preview"] .topbar,
body[data-mode="preview"] .rail,
body[data-mode="preview"] #btn-save-view { display: none; }
body[data-mode="preview"] .studio {
  grid-template-columns: 1fr;
  height: 100dvh;
}
body[data-mode="preview"] .plate-over { display: block; }
/* fix-23 (P14-B3-3) · on a short landscape stage the full identity cannot
   physically fit: at 844×390 the bar-pushed plate (top 82) with a 60-char
   name and 220-char dedication holds 239 px at its ramp floors, the bottom
   chrome takes 81, and the camera band left for the tree is negative — the
   adapter's last-resort seat then lifts the crown behind the words (the
   measured 3.3 px clearance). The dedication YIELDS here exactly as it does
   under the canopy (`[data-plate-view="under"] .plate-over`): below 560 px
   of viewport height — preview's stage is the viewport — the plate carries
   kicker · name · dates, the ramp fits it inside its 40 % share, and the
   solve re-seats unrelaxed with the contract's ≥24 px gap. The dedication
   remains in About and in every taller state. 560 sits between the failing
   390 stage and the shortest passing desktop cell (614 at 1093×614). */
@media (max-height: 560px) {
  body[data-mode="preview"] .plate-over { display: none; }
}
body[data-mode="preview"] .stage-hint {
  left: 50%; transform: translateX(-50%);
  bottom: 46px;
}

/* ---------- failure state (F-06) ----------
   The failure card lays out inside the measured safe area: on the phone
   the editor sheet's band is subtracted (same --safe-bottom the rest of
   the chrome uses), the card is capped to the unobscured height, and
   only its body scrolls — the heading and the 44 px Try again action
   are always visible, at every sheet snap. */

.stage-fallback {
  position: absolute; inset: 0; z-index: 10;
  display: grid; place-items: center;
  background:
    radial-gradient(90% 70% at 50% 34%, #F5F2EA 0%, #ECE7D9 100%);
  padding: 24px 20px calc(var(--safe-bottom, 0px) + 20px);
}
.fallback-inner {
  max-width: 420px;
  max-height: 100%;
  min-height: 0;
  display: flex; flex-direction: column; align-items: center;
  text-align: center;
  background: var(--ivory-raise);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-card);
  padding: 28px 30px 24px;
}
.fallback-leaf { flex: none; width: 30px; height: 30px; color: var(--moss); opacity: .8; }
.fallback-title {
  flex: none;
  margin: 12px 0 10px;
  font: 400 23px/1.25 var(--font-memorial);
}
.fallback-scroll {
  min-height: 48px;              /* always a readable slice of the words */
  overflow-y: auto;              /* only the explanation ever scrolls */
  margin: 0 0 18px;
}
.fallback-body {
  margin: 0;
  /* Long-form memorial copy: the reading face at reading size. */
  font: 400 16px/1.6 var(--font-reading);
  color: var(--ink-soft);
}
.btn-retry { flex: none; min-height: 44px; min-width: 132px; }
.stage-fallback[data-state="loading"] .fallback-leaf {
  animation: fallback-breathe 1.6s ease-in-out infinite;
}
@keyframes fallback-breathe {
  0%, 100% { opacity: .45; }
  50%      { opacity: .95; }
}

/* ============================================================
   Keep this memorial
   ============================================================ */

.keep-scrim {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(41,43,39,.4);
  display: grid; place-items: center;
  padding: 24px;
}
/* Prompt 29 · the free-first-100 entry note: quiet, factual, no urgency.
   Visible only when the Studio learns from the server that the window has
   closed — surfaced before investment, never sprung at checkout. */
.free-window-note {
  margin: 0; padding: 10px 20px;
  font: 400 14px/1.6 var(--font-interface);
  color: var(--ink-soft, #6c6353);
  background: var(--ivory, #fffdf8);
  border-bottom: 1px solid var(--hairline, #e4ddcd);
  text-align: center;
}

.keep-sheet {
  width: min(620px, 100%);
  max-height: min(92dvh, 860px);
  display: flex; flex-direction: column;
  background: var(--ivory);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-float);
  overflow: hidden;
}
.keep-head {
  padding: 30px 38px 20px;
  border-bottom: 1px solid var(--hairline-soft);
  text-align: center;
}
.keep-kicker {
  margin: 0 0 8px;
  font: 600 12px/1.2 var(--font-interface);
  letter-spacing: var(--track-kicker); text-transform: uppercase;
  color: var(--gold);
}
.keep-title {
  margin: 0;
  font: 400 30px/1.15 var(--font-memorial);
  letter-spacing: .01em;
}
.keep-summary {
  margin: 8px 0 0;
  font: 400 16px/1.5 var(--font-reading);
  color: var(--ink-soft);
  overflow-wrap: break-word;
}
.keep-body {
  overflow-y: auto;
  padding: 8px 38px 10px;
}
.keep-block {
  padding: 18px 0;
  border-bottom: 1px solid var(--hairline-soft);
}
.keep-block p {
  margin: 0;
  /* Long-form keep copy is reading text, not interface fine print. */
  font: 400 16px/1.6 var(--font-reading);
  color: var(--ink-soft);
  overflow-wrap: break-word;
}
.keep-block-title {
  margin: 0 0 6px;
  font: 500 16.5px/1.3 var(--font-display);
  color: var(--ink);
}
#keep-email-block .field-help { margin-top: 7px; }
#keep-email { margin-top: 2px; }
.input-invalid { border-color: #A4552F !important; box-shadow: 0 0 0 1px #A4552F !important; }
.keep-email-error {
  margin: 7px 0 0; font-size: 13px; line-height: 1.5; color: #8F4526;
}

.keep-price { padding: 20px 0 8px; }
.keep-price-line {
  display: flex; align-items: baseline; justify-content: space-between;
  position: sticky; top: -4px; z-index: 2;
  gap: 12px;
  margin: 0 0 4px;
  padding: 8px 0;
  background: var(--ivory);
}
.keep-price-label {
  font: 500 16.5px/1.2 var(--font-display);
}
.keep-price-amount {
  /* The price numeral carries display weight — quiet, not promotional. */
  font: 500 28px/1 var(--font-display);
  font-variation-settings: 'opsz' 40;
  color: var(--ink);
  white-space: nowrap;
}
.keep-price-once {
  font: 600 12px/1 var(--font-interface);
  font-variation-settings: normal;
  letter-spacing: var(--track-kicker); text-transform: uppercase;
  color: var(--ink-faint);
  margin-left: 2px;
}
.keep-included {
  margin: 0; padding: 14px 0 6px;
  border-top: 1px solid var(--hairline-soft);
  list-style: none;
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 7px 22px;
}
.keep-included li {
  position: relative;
  padding-left: 16px;
  font-size: 14px; line-height: 1.5;
  color: var(--ink-soft);
}
.keep-included li::before {
  content: "";
  position: absolute; left: 0; top: .52em;
  width: 6px; height: 6px;
  border-radius: 50% 50% 50% 0;
  background: var(--gold-soft);
  transform: rotate(-45deg);
}

/* §9 · the permanence reassurance under the included list. */
.keep-permanence {
  margin: 12px 0 0;
  padding-top: 12px;
  border-top: 1px solid var(--hairline, rgba(0,0,0,.08));
  font: 400 13.5px/1.55 var(--font-reading);
  color: var(--ink-soft);
}

/* Phase 7 · live checkout / order state inside the sheet. Honest words
   from the server, one calm surface — no spinners, no fake progress. */
.keep-status {
  margin: 16px 0 6px;
  border: 1px solid rgba(88,112,77,.35);
  background: var(--moss-wash);
  border-radius: var(--r-md);
  padding: 14px 18px;
  font: 400 14px/1.6 var(--font-reading);
  color: var(--ink);
}
.keep-status-error {
  border-color: rgba(164,85,47,.4);
  background: rgba(164,85,47,.07);
  color: #6E3620;
}

/* Prompt 30 · the keep-success moment: certain, not loud. Strong
   confirmation color, the sentence at full prominence — no animation. */
.keep-status-success {
  border-color: var(--moss-deep, #3f4a35);
  background: var(--moss-deep, #3f4a35);
  color: #f6f3ea;
  font: 600 17px/1.5 var(--font-reading);
  padding: 18px 20px;
}
.keep-status-sub {
  display: block;
  margin-top: 8px;
  font: 400 14px/1.55 var(--font-reading);
  color: rgba(246,243,234,.88);
}
/* Prompt 39 · the keeper's dashboard link inside the keep sheet — visible on
   both grounds the sheet can show it against (the moss success block and the
   plain status well), and an honest ≥40px touch target. */
.keep-status-link {
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  margin-top: 6px;
  font: 600 14.5px/1.4 var(--font-interface);
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.keep-status-link:focus-visible { outline: 2px solid var(--gold, #B08D45); outline-offset: 2px; }
.keep-sheet--kept { border-color: var(--moss-deep, #3f4a35); }
#btn-keep.kept {
  background: var(--moss-deep, #3f4a35);
  color: #f6f3ea;
  border-color: var(--moss-deep, #3f4a35);
}

/* §7C.1 · technical fine print — the raw species+seed's one visible home,
   deliberately quiet: metadata, not a memorial label. */
.keep-fineprint {
  margin: 14px 0 0;
  font: 400 12px/1.5 var(--font-ui);
  letter-spacing: .01em;
  color: var(--ink-soft);
  opacity: .8;
}

/* Phase 7 · post-payment progress strip.

   It used to sit at the viewport's bottom-left, which on the desktop layout is
   INSIDE the 360 px editor rail — measured with elementFromPoint, the strip
   was the hit target over "Save this memory", so after any purchase the
   memory editor's primary action could not be clicked. (Found while walking
   the real buy → upload → publish path for B-C2.) It now lives in the stage's
   own bottom-left, one control-height above the stage tools and the gesture
   hint, and steps aside for the sheet and the preview bar on the phone. The
   overlap sweep in 7CF4-narrow-stage measures it alongside the rest.

   layoutOrderStrip() owns left / bottom / max-width from measured rectangles;
   the values here are only the pre-measurement fallback. */
.order-status {
  position: fixed;
  left: calc(var(--rail-w) + 18px);
  bottom: calc(18px + var(--control-h) + 12px);
  z-index: 60;
  max-width: min(430px, calc(100vw - var(--rail-w) - 36px));
  display: flex; align-items: baseline; gap: 14px;
  background: var(--ivory);
  border: 1px solid var(--hairline);
  border-left: 3px solid #58704D;
  border-radius: var(--r-md);
  box-shadow: 0 10px 30px rgba(44,42,38,.14);
  padding: 12px 16px;
}
/* in preview the rail is gone, so the stage starts at the viewport edge */
body[data-mode="preview"] .order-status {
  left: 18px;
  max-width: min(430px, calc(100vw - 36px));
}
.order-status-final { border-left-color: var(--gold-soft); }
.order-status-msg {
  margin: 0;
  font: 400 13.5px/1.55 var(--font-reading);
  color: var(--ink);
}
.order-status-hide {
  font: 600 var(--fs-control)/1.2 var(--font-interface);
  letter-spacing: .02em;
  background: none; border: 0; padding: 8px 4px;
  color: var(--ink-faint); cursor: pointer;
}
.order-status-hide:hover { color: var(--ink); }
/* Prompt 39 · the strip's dashboard link */
.order-status-link {
  flex: none;
  font: 600 13.5px/1.2 var(--font-interface);
  color: var(--moss-deep, #465C3D);
  text-decoration: underline;
  text-underline-offset: 3px;
  padding: 8px 0;
}
.order-status-link:focus-visible { outline: 2px solid var(--gold, #B08D45); outline-offset: 2px; }

/* Phase 7 · the ?example=1 demo is always labelled — never mistakable
   for the owner's own memorial. */
.example-badge {
  position: absolute;
  top: 14px; right: 14px;
  z-index: 40;
  margin: 0;
  padding: 6px 12px;
  font: 600 11px/1 var(--font-interface);
  letter-spacing: var(--track-kicker); text-transform: uppercase;
  color: #6E3620;
  background: rgba(255,253,248,.92);
  border: 1px solid rgba(164,85,47,.4);
  border-radius: 999px;
  pointer-events: none;
}

.keep-actions {
  display: flex; justify-content: space-between; gap: 12px;
  padding: 16px 38px 24px;
  border-top: 1px solid var(--hairline-soft);
  background: var(--ivory);
}

/* ============================================================
   Toasts (F-09) — actionable exceptions only. Routine saved /
   restored feedback lives in the one status channel (top bar /
   sheet). The region's offsets are measured by the Studio so a
   toast never intersects stage controls, cards, failure copy or
   the top and bottom bars.
   ============================================================ */

.toast-region {
  position: fixed; z-index: 150;
  left: 50%; bottom: 26px;
  transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  pointer-events: none;
  max-width: calc(100vw - 24px);
}
.toast {
  display: flex; align-items: center; gap: 14px;
  pointer-events: auto;
  background: var(--ink);
  color: var(--ivory);
  border-radius: 999px;
  box-shadow: var(--shadow-float);
  padding: 12px 20px;
  font: 500 14px/1.3 var(--font-interface);
  max-width: 100%;
  animation: toast-in .25s ease;
}
.toast .linklike { color: var(--gold-soft); padding: 2px; }
.toast .linklike:hover { border-bottom-color: var(--gold-soft); }
@keyframes toast-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* ============================================================
   Phase 7C F4 · narrow STAGE, desktop layout

   The desktop grid keeps a fixed 360 px rail, so a 801 px window leaves a
   441 px stage while every `@media (max-width:800px)` compact rule stays
   asleep. That is the reported case, but it is not the only one: the same
   single-row arrangement fails wherever the stage is too narrow for it, and
   the geometry says where. Measured (review/fix/12/2026-07-27/
   7CF4-narrow-stage/7CF4-result.json):

     hint    321 px wide, pinned 20 px from the stage's left edge
     meta    282 px wide, centred
     tools   216 px wide, pinned 18 px from the right

   so the hint clears the centred metadata only once
   `20 + 321 + 12 <= W/2 - 141`, i.e. W >= 988 px of STAGE. (The tools clear
   it from W >= 774.) The threshold below is that number with a margin for
   longer phase and light labels — at 1280×800 the stage is 920 px and the
   hint ran 22 px into the metadata before this rule existed.

   Wrapped in a min-width media query because the phone has its own bottom
   geometry (above the sheet) and must keep it.
   ============================================================ */

@media (min-width: 801px) {
  @container stage (max-width: 1040px) {
    /* row 0 · the tools, as two compact pills side by side */
    .stage-tools {
      right: 12px; bottom: 12px;
      flex-direction: row-reverse; gap: 8px;
    }
    #btn-save-view .stage-tool-label { display: none; }
    #btn-save-view { padding: 0 12px; }

    /* row 0 (left) · the visitor's memory count, opposite the tools */
    .visitor-actions { left: 12px; bottom: 12px; }

    /* row 1 · the phase·light line, clear of the row below it */
    .plate-meta { bottom: calc(12px + var(--control-h) + 12px); }

    /* row 2 · the gesture hint, centred and bounded by the stage */
    .stage-hint,
    body[data-mode="preview"] .stage-hint {
      left: 50%; right: auto; transform: translateX(-50%);
      bottom: calc(12px + var(--control-h) + 46px);
      max-width: calc(100% - 28px);
      text-align: center;
    }
  }
}

/* ============================================================
   Mobile — 390 × 844 : the tree keeps the room
   ============================================================ */

@media (max-width: 800px) {
  :root { --topbar-h: 54px; }

  .topbar { padding: 0 12px 0 14px; grid-template-columns: auto 1fr auto; }
  .save-state, #btn-preview, .topbar-title { display: none; }
  .brand { grid-column: 1; }
  #btn-keep { grid-column: 3; padding: 0 14px; }

  .studio { display: block; height: calc(100dvh - var(--topbar-h)); }

  .stage { position: absolute; inset: 0; }

  .stage-plate {
    top: calc(var(--plate-top, 14px) + env(safe-area-inset-top, 0px));
    width: 88%;
    --plate-name-size: clamp(38px, 10.5vw, 44px);
    --plate-dates-size: 16.5px;
  }
  .plate-kicker, .plate-form { font-size: 12.5px; }
  .plate-over { font-size: 17px; }
  .plate-meta { display: none; }

  .stage-tools {
    right: 12px;
    bottom: calc(var(--sheet-visible, 40dvh) + 12px);
    flex-direction: row-reverse;              /* two compact pills, side by side */
  }
  #btn-save-view .stage-tool-label { display: none; }
  #btn-save-view { padding: 0 12px; }
  .stage-hint {
    left: 14px; max-width: 46vw;
    bottom: calc(var(--sheet-visible, 40dvh) + 16px);
  }

  /* ------- the rail becomes a bottom sheet ------- */
  .rail {
    position: fixed; z-index: 40;
    left: 0; right: 0; bottom: 0;
    height: 78dvh;                       /* content height; sheet slides */
    transform: translateY(calc(78dvh - var(--sheet-visible, 40dvh)));
    transition: transform .28s cubic-bezier(.3,.9,.3,1);
    border-right: 0;
    border-top: 1px solid var(--hairline);
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    box-shadow: 0 -8px 40px -20px rgba(41,43,39,.5);
    background: var(--ivory);
    touch-action: none;
  }
  /* peek shows the grip and the WHOLE section nav — two wrapped rows of pills
     at the 16 px control floor — so no section is hidden at the lowest snap.
     sheetVisiblePx() in studio.js carries the same number. */
  body[data-sheet="peek"] { --sheet-visible: 152px; }
  body[data-sheet="mid"]  { --sheet-visible: 40dvh; }
  body[data-sheet="tall"] { --sheet-visible: 50dvh; }
  .rail.sheet-dragging { transition: none; }

  .sheet-grip {
    display: block;
    position: relative;
    padding: 4px 0 0;
  }
  .sheet-save {
    position: absolute;
    right: 18px; top: 5px;
    font-size: var(--fs-control);
    line-height: 1.2;
    color: var(--ink-faint);
    max-width: 60vw;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .sheet-grip button {
    margin: 0 auto;
    width: 72px; height: 26px;
    background: none; border: 0; cursor: grab;
    font-size: var(--fs-control);
    display: grid; place-items: center;
  }
  .grip-bar {
    width: 40px; height: 4px; border-radius: 999px;
    background: var(--hairline);
  }

  /* At the 16 px control floor four section pills no longer fit one 390 px
     row, and the old `overflow-x:auto; scrollbar-width:none` row would have
     hidden "Preview" past the edge with no visible affordance — the only way
     into the visitor view on a phone. They WRAP instead: every section stays
     on screen and reachable without a scroll gesture nobody can see. */
  .rail-nav {
    flex-direction: row;
    flex-wrap: wrap;
    gap: 6px 4px;
    padding: 0 14px 10px;
  }
  .rail-nav-item {
    border-top: 0;
    flex: none;
    padding: 10px 12px;
    min-height: var(--control-h);
    border-radius: 999px;
    letter-spacing: .01em;
  }
  .rail-nav-item[aria-current="true"] {
    background: var(--moss-wash);
  }
  .rail-nav-item[aria-current="true"]::before { display: none; }

  .rail-body {
    /* Prompt 32 (found during item 5's mobile pass, pre-existing on the ADD
       flow too) · the sheet is a 78dvh box slid down so only --sheet-visible
       shows; rail-body's bottom (78dvh - visible) could hold content that NO
       scroll position could ever bring into the visible band — at 390×844
       'tall', the memory form's tail (place/save buttons, the re-place link)
       sat ~200px below the fold at max scroll. The hidden band joins the
       bottom padding, so the scroller's range covers everything it holds. */
    padding: 4px calc(20px + env(safe-area-inset-right, 0px))
      calc(40px + env(safe-area-inset-bottom, 0px) + (78dvh - var(--sheet-visible, 40dvh)))
      calc(20px + env(safe-area-inset-left, 0px));
    touch-action: pan-y;
  }
  .rail-foot { display: none; }

  .panel-head { margin-top: 6px; }
  .panel-title { font-size: 21px; }

  /* The selected memory is bounded by the measured safe area (F-04):
     never under the top bar or the identity, never behind the sheet,
     12 px clear on both sides. --safe-top / --safe-bottom are written
     by the Studio from real rectangles; the CSS fallbacks keep the
     card sane before the first measurement. */
  .memory-card {
    left: 12px; right: 12px; top: auto;
    bottom: calc(var(--safe-bottom, var(--sheet-visible, 40dvh)) + 12px);
    width: auto;
    /* Bounded to the unobscured band; the 168px floor keeps the header,
       a readable slice of text and the actions usable even under an
       extreme identity + sheet combination (the body scrolls).
       Phase 1-2 F3 · and capped at about a third of the stage, because the
       folio may not swallow the canopy it is describing — a long memory
       scrolls inside the card rather than growing it over the gold glow. */
    max-height: min(34dvh,
      max(168px, calc(100% - var(--safe-top, 96px) - var(--safe-bottom, var(--sheet-visible, 40dvh)) - 24px)));
  }

  .visitor-list {
    left: 12px; right: 12px; top: auto; bottom: 12px;
    width: auto; max-height: 46dvh;
  }

  .keep-head { padding: 24px 22px 16px; }
  .keep-body { padding: 4px 22px 8px; }
  .keep-actions { padding: 14px 22px 20px; }
  .keep-included { grid-template-columns: 1fr; }

  /* the post-payment strip clears the sheet, and the preview bar in preview */
  .order-status {
    left: 12px; right: 12px;
    max-width: none;
    bottom: calc(var(--sheet-visible, 40dvh) + 12px);
  }
  body[data-mode="preview"] .order-status {
    left: 12px;
    max-width: none;
    bottom: calc(var(--preview-bar-h, 96px) + 20px + env(safe-area-inset-bottom, 0px));
  }

  /* JS positions the toast region from measured rectangles (F-09);
     this is only the pre-measurement fallback. */
  .toast-region { bottom: calc(var(--sheet-visible, 40dvh) + 64px); }
  body[data-mode="preview"] .toast-region { bottom: 160px; }

  /* Failure card on the phone (F-06): reserve a strip under the top bar
     tall enough for a docked two-line actionable toast, so it can never
     touch the card; tighten the card so the whole message fits the
     default (peek) state. */
  .stage-fallback { padding-top: 84px; padding-left: 14px; padding-right: 14px; }
  .fallback-inner { padding: 20px 20px 18px; }
  .fallback-leaf { width: 26px; height: 26px; }
  .fallback-title { font-size: 21px; margin: 10px 0 8px; }
  .fallback-body { line-height: 1.55; }
  .fallback-scroll { margin-bottom: 14px; }

  /* the preview bar is measured (--preview-bar-h, written by
     measureSafeAreas) rather than assumed at 122 px, because the unified
     two-row bar is taller than the old one-row pill and everything stacked
     above it has to move with it. */
  body[data-mode="preview"] .plate-meta {
    display: flex;
    top: auto;
    bottom: calc(var(--preview-bar-h, 96px) + 74px + env(safe-area-inset-bottom, 0px));
  }
  body[data-mode="preview"] .stage-hint { display: none; }
  /* F7 + 7C F5 · the phone shape: the sentence takes its own row, the actions
     share the next one, and both stay inside the safe-area width. A pill
     radius is wrong for a two-row block, so it relaxes to a card. */
  .preview-bar {
    top: auto; left: 10px; right: 10px;
    bottom: calc(10px + env(safe-area-inset-bottom, 0px));
    max-width: none;
    border-radius: var(--r-lg);
    padding: 10px 12px;
    gap: 8px;
  }
  .preview-bar-label {
    flex: 1 1 100%;
    text-align: center;
    font-size: 14px;
  }
  .preview-bar-actions {
    flex: 1 1 100%;
    gap: 8px;
  }
  /* Every action keeps its whole label — `min-width: max-content` stops flex
     from shrinking a button into an ellipsis, so when the pair no longer fits
     one row (the Publish label grows to "Publish the latest changes" once the
     memorial is published) the row WRAPS and each button takes a full line.
     Truncating "Publish for v…" is the original defect; it is not allowed to
     come back in a narrower form. */
  .preview-bar-actions > .btn,
  .preview-bar-actions > .stage-tool {
    flex: 1 1 auto;
    min-width: max-content; max-width: 100%;
    padding: 0 12px;
    white-space: nowrap;
    display: block; line-height: var(--control-h);
    text-align: center;
  }
  body[data-mode="preview"] .visitor-actions {
    left: 12px;
    bottom: calc(var(--preview-bar-h, 96px) + 20px + env(safe-area-inset-bottom, 0px));
  }
  body[data-mode="preview"] .stage { inset: 0; }
  body[data-mode="preview"] .stage-tools {
    bottom: calc(var(--preview-bar-h, 96px) + 20px + env(safe-area-inset-bottom, 0px));
  }
  /* preview has no editor sheet: the selected memory sits low, above the
     preview bar and the memory-count action, leaving the tree band —
     where its leaf glows — clear. Same measured safe-area variables. */
  body[data-mode="preview"] .memory-card {
    bottom: calc(var(--safe-bottom, 122px) + 12px);
    max-height: min(34dvh,
      max(168px, calc(100% - var(--safe-top, 190px) - var(--safe-bottom, 122px) - 24px)));
  }
}

/* Short desktop stages (1366×768 · 1024×768): the keepsake block compresses
   to its contract floors (name 56px) and a tighter top margin, buying the
   root flare its ground without touching the 82–94% width window. */
@media (min-width: 801px) and (max-height: 800px) {
  .stage-plate {
    top: var(--plate-top, 16px);
    --plate-name-size: 56px;
    --plate-dates-size: 17px;
  }
  .plate-dates { margin-top: 3px; }
  .plate-kicker, .plate-form { font-size: 13px; margin-bottom: 4px; }
  .plate-form { margin: 6px 0 0; }
}

/* very small heights (landscape phones) — keep the sheet honest */
@media (max-width: 800px) and (max-height: 500px) {
  body[data-sheet="mid"] .rail,
  body[data-sheet="tall"] .rail { --sheet-visible: 55dvh; }
}

/* ============================================================
   Reduced motion — stillness is a feature
   ============================================================ */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    /* fix-23 (P14-B3-2) · was `transition-duration: .01ms`. Elements with no
       declared transition keep the initial `transition-property: all`, so that
       duration override gave EVERY property on EVERY unstyled element a
       one-frame transition — same-task layout reads then see the pre-change
       geometry. fitPlate() removes its ramp override and measures the plate in
       that state: it read the still-ramped height, concluded the ideal-size
       block fits, and left it unfitted while the camera band kept the stale
       measurement — the −198.5 px reduced-motion identity clearance. Reduced
       motion means instant, not one-frame-late: no transitions at all. */
    transition: none !important;
  }
}

/* ============ Phase 4 · Gold gallery ============ */
.gold-card { display: grid; grid-template-columns: 74px 1fr; gap: 12px; align-items: start; }
.gold-card .gold-thumb {
  width: 74px; height: 92px; object-fit: cover; border-radius: 8px;
  background: #e8e2d4; box-shadow: inset 0 0 0 1px rgba(58, 50, 38, .12);
}
.gold-card .gold-body { display: block; min-width: 0; }
.gold-card .gold-seed {
  display: block; margin-top: 4px;
  font: 500 11px/1.4 'Source Sans 3', sans-serif;
  letter-spacing: .04em; color: var(--ink-3, #8a8274);
}
.gold-card .choice-check { align-self: center; }
.gold-actions { display: flex; gap: 18px; margin: 2px 2px 6px; }
.gold-confirm {
  margin: 8px 0 10px; padding: 12px 14px;
  border: 1px solid rgba(88, 112, 77, .35); border-radius: 10px;
  background: rgba(88, 112, 77, .07);
}
.gold-confirm-msg { margin: 0 0 10px; font: italic 400 14.5px/1.5 'Newsreader', serif; }
.gold-confirm .btn-row { justify-content: flex-end; gap: 10px; }

/* I-P34-007 · the growing state, on the card that was just chosen. It is
   painted a full frame before setTreeSeed takes the main thread, which is the
   whole point of the finding: the old code set the status and rebuilt in the
   same task, so nothing was ever presented. */
.gold-card.growing { border-color: var(--gold); box-shadow: 0 0 0 1px var(--gold); }
.gold-growing {
  display: inline-flex; align-items: center; gap: 7px;
  margin-top: 7px;
  font: 600 14px/1 var(--font-interface);
  letter-spacing: .05em; text-transform: uppercase;
  color: var(--walnut);
}
.gold-growing::before {
  content: ""; width: 8px; height: 8px; border-radius: 50%;
  background: var(--gold);
  animation: halo 1.4s ease-in-out infinite;
}

/* ============ Phase 6 · truthful save states, conflict review, publish ============ */
.save-state.save-attention, .sheet-save.save-attention {
  color: #8a4a2a; font-weight: 600; cursor: pointer;
}
#conflict-review {
  position: fixed; inset: 0; z-index: 240;
  display: flex; align-items: center; justify-content: center;
  background: rgba(38, 32, 24, 0.42);
}
#conflict-review[hidden] { display: none; }
.conflict-card {
  background: #faf6ec; border-radius: 14px; max-width: 30rem;
  margin: 1rem; padding: 1.5rem 1.6rem 1.3rem;
  box-shadow: 0 18px 48px rgba(30, 24, 14, 0.28);
}
.conflict-card h2 { font-family: 'Newsreader', serif; font-size: 1.35rem; margin: 0 0 .55rem; }
.conflict-card p { font-size: .95rem; line-height: 1.5; margin: 0 0 .5rem; }
.conflict-meta { color: #6c6353; font-size: .85rem; }
.conflict-actions { display: flex; gap: .6rem; margin-top: 1rem; flex-wrap: wrap; }
.conflict-actions button {
  font: 600 var(--fs-control)/1.2 var(--font-interface); padding: .7rem 1rem;
  min-height: var(--control-h);
  border-radius: 999px; border: 1px solid #b9b09c; background: #fff; cursor: pointer;
}
.conflict-actions #conflict-keep-server { background: #405c48; border-color: #405c48; color: #f6f2e8; }
#publish-row {
  position: fixed; z-index: 59;
  top: 64px; left: 16px;
  display: none;
  max-width: min(340px, calc(100vw - 32px));
  flex-direction: column; gap: .4rem;
  background: var(--ivory-raise, #faf6ec);
  border: 1px solid var(--hairline, #d9d2c0);
  border-radius: 14px;
  box-shadow: var(--shadow-card, 0 10px 30px rgba(30,24,14,.12));
  padding: .6rem .8rem .65rem;
}
body[data-mode="preview"]:not(.visitor-page) #publish-row { display: flex; }
/* the pill never eats a stage tap: only its real controls take the pointer */
#publish-row { pointer-events: none; background: transparent; border: 0; box-shadow: none; padding: 0; }
.publish-btn { white-space: nowrap; align-self: flex-start; pointer-events: auto; }
.publish-status {
  font-size: 14px; line-height: 1.45; color: #58503f; overflow-wrap: anywhere;
  background: var(--ivory-raise, #faf6ec);
  border: 1px solid var(--hairline, #d9d2c0);
  border-radius: 10px; padding: .5rem .65rem;
  pointer-events: auto;
}
.publish-status:empty { display: none; }
/* a real link the owner is expected to open and share → control floor */
.publish-link { color: #405c48; font-weight: 600; font-size: var(--fs-control); }
/* Prompt 32 (A1 item 3) · the refresh sentence after a republish past
   delivered files — a new line inside the same status pill */
.publish-refresh-note { display: block; margin-top: .35rem; color: #405c48; font-weight: 600; }
/* On the phone the preview bar is docked at the BOTTOM and the top bar is
   hidden in preview, so a link line pinned under a 64 px top bar would land on
   the memorial's own name. It rides just above the bar instead — the same
   measured band the bar occupies, never over the identity. */
@media (max-width: 800px) {
  #publish-row {
    top: auto; left: 10px; right: 10px;
    bottom: calc(var(--preview-bar-h, 96px) + 18px + env(safe-area-inset-bottom, 0px));
    max-width: none;
  }
}
#btn-publish { white-space: nowrap; }
.visitor-page .rail, .visitor-page .top-actions { display: none; }
.visitor-missing {
  min-height: 60vh; display: flex; flex-direction: column; align-items: center;
  justify-content: center; text-align: center; padding: 2rem; gap: .4rem;
  font-family: 'Source Sans 3', sans-serif;
}
.visitor-missing h1 { font-family: 'Newsreader', serif; font-weight: 500; font-size: 1.6rem; }
.visitor-missing p { color: #6c6353; max-width: 34rem; }

/* ============================================================
   §7E · premium enhancements — pickers, the checkout selection, vanity panel.
   Quiet by design: a premium badge is a small tag, never a louder card; the
   base look is always free; no dark patterns anywhere.
   ============================================================ */

/* the quiet "Premium" tag (forms + enhancement pickers) */
.choice-tag--premium {
  margin-left: .5ch;
  background: var(--gold-soft, #efe3c4);
  color: var(--moss-deep, #3f4a35);
  border-color: transparent;
}
.enhance-card.locked, .gold-card.locked { cursor: pointer; }
.enhance-card.locked .enhance-body, .gold-card.locked .gold-body { opacity: .82; }
.enhance-card.locked .choice-check, .gold-card.locked .choice-check { display: none; }

/* Prompt 30 · checkout disabled → an unowned premium option is ROADMAP:
   a quiet "coming soon" tag, no pointer affordance, nothing to click. */
.choice-tag--soon {
  margin-left: .5ch;
  background: var(--paper-deep, #eceadf);
  color: var(--ink-soft, #6c6353);
  border-color: transparent;
}
.enhance-card.soon, .gold-card.soon { cursor: default; }
.enhance-card.soon .enhance-body, .gold-card.soon .gold-body { opacity: .68; }

/* the enhancement pickers (skins as cards, weather as chips share choice-card) */
.enhance-card .enhance-body { display: flex; flex-direction: column; gap: .1rem; }
.enhance-locked { font: 400 13px/1.4 var(--font-interface); color: var(--ink-soft, #6c6353); margin: .3rem 0 0; }

/* ---- checkout: two tiers + itemised total ---- */
.keep-tiers { display: flex; flex-direction: column; gap: .5rem; margin: .2rem 0 .7rem; }
.keep-tier {
  display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto;
  gap: 0 .6rem; align-items: start; padding: .6rem .75rem;
  border: 1px solid var(--hairline, #e4ddcd); border-radius: var(--r-md, 14px);
  background: var(--ivory-raise, #fbf8f1); cursor: pointer;
}
.keep-tier.selected { border-color: var(--moss, #7c8a63); box-shadow: 0 0 0 1px var(--moss, #7c8a63); }
.keep-tier input { grid-row: 1 / span 2; margin-top: .2rem; }
.keep-tier-title { font: 600 var(--fs-control)/1.3 var(--font-interface); color: var(--ink, #2c2a26); }
.keep-tier-desc { grid-column: 2; font: 400 13px/1.45 var(--font-interface); color: var(--ink-soft, #6c6353); }
.keep-addon {
  display: flex; align-items: baseline; gap: .55rem; padding: .5rem 0;
  min-height: var(--control-h);
  font: 400 var(--fs-control)/1.45 var(--font-interface); color: var(--ink, #2c2a26); cursor: pointer;
}
.keep-addon-note { color: var(--ink-soft, #6c6353); font-size: 12.5px; }
.keep-total { margin: .5rem 0 .2rem; border-top: 1px solid var(--hairline-soft, #efe9db); padding-top: .5rem; }
.keep-total-line { display: flex; justify-content: space-between; gap: 1rem; font: 400 14px/1.7 var(--font-interface); color: var(--ink-soft, #6c6353); }
.keep-total-subtotal { border-top: 1px dashed var(--hairline, #e4ddcd); margin-top: .3rem; padding-top: .3rem; }
.keep-total-tax { color: var(--ink-soft, #6c6353); }
.keep-total-sum { color: var(--ink, #2c2a26); font-weight: 600; border-top: 1px solid var(--hairline, #e4ddcd); margin-top: .3rem; padding-top: .3rem; }

/* ---- the vanity URL panel — a section in the About panel ---- */
.vanity-panel {
  margin: 1rem 0 .25rem; padding: 1rem 1.1rem;
  background: var(--ivory-raise, #fbf8f1); border: 1px solid var(--hairline, #e4ddcd);
  border-radius: var(--r-md, 14px);
  display: flex; flex-direction: column; gap: .5rem;
}
.vanity-title { font: 600 15px/1.3 var(--font-interface); color: var(--ink, #2c2a26); margin: 0; }
.vanity-note { font: 400 13px/1.5 var(--font-interface); color: var(--ink-soft, #6c6353); margin: 0; }
.vanity-warn { font: 400 12.5px/1.5 var(--font-interface); color: var(--moss-deep, #3f4a35); margin: 0; }
.vanity-status { font: 400 13px/1.4 var(--font-interface); margin: 0; min-height: 1.1em; }
.vanity-status.ok { color: #4b6b3c; }
.vanity-status.err { color: #9a5a44; }
.vanity-listing { display: flex; align-items: baseline; gap: .5rem; min-height: var(--control-h); font: 400 var(--fs-control)/1.45 var(--font-interface); color: var(--ink, #2c2a26); cursor: pointer; }
.vanity-panel .input { margin: .1rem 0; }

/* ---- Prompt 36 A4 · the cart: selection accumulates, one review step ---- */
.choice-tag--chosen {
  background: var(--moss, #7c8a63); color: var(--ivory, #fbf8f1);
  border-color: transparent;
}
.enhance-card.chosen, .gold-card.chosen { border-color: var(--moss, #7c8a63); box-shadow: 0 0 0 1px var(--moss, #7c8a63); }
.enhance-card.chosen .enhance-body { opacity: 1; }
.keep-lines { list-style: none; margin: .2rem 0 .4rem; padding: 0; }
.keep-line {
  display: grid; grid-template-columns: 1fr auto auto; gap: .6rem; align-items: baseline;
  padding: .45rem 0; border-bottom: 1px solid var(--hairline-soft, #efe9db);
  font: 400 var(--fs-control)/1.45 var(--font-interface); color: var(--ink, #2c2a26);
}
.keep-line-label { min-width: 0; }
.keep-line-note { display: block; color: var(--ink-soft, #6c6353); font-size: 12.5px; }
.keep-line-amount { white-space: nowrap; color: var(--ink-soft, #6c6353); }
.keep-line-remove {
  font: 500 12.5px/1 var(--font-interface); color: var(--ink-soft, #6c6353);
  background: none; border: 0; padding: .25rem .35rem; cursor: pointer; border-radius: 6px;
  text-decoration: underline; text-underline-offset: 2px;
}
.keep-line-remove:hover, .keep-line-remove:focus-visible { color: var(--ink, #2c2a26); outline: none; background: var(--ivory-raise, #fbf8f1); }
.keep-bundle-note {
  margin: .3rem 0 .6rem; padding: .55rem .75rem; border-radius: var(--r-md, 14px);
  background: var(--ivory-raise, #fbf8f1); border: 1px solid var(--hairline, #e4ddcd);
  font: 400 13px/1.45 var(--font-interface); color: var(--ink-soft, #6c6353);
}
.keep-market { margin: .7rem 0 .2rem; padding-top: .6rem; border-top: 1px solid var(--hairline-soft, #efe9db); }
.keep-market-title { font: 600 var(--fs-control)/1.3 var(--font-interface); color: var(--ink, #2c2a26); margin: 0 0 .35rem; }
.keep-market-choices { display: flex; flex-wrap: wrap; gap: .5rem; }
.keep-market-choice {
  display: inline-flex; align-items: center; gap: .45rem; padding: .45rem .75rem;
  border: 1px solid var(--hairline, #e4ddcd); border-radius: 999px; background: var(--ivory-raise, #fbf8f1);
  font: 500 var(--fs-control)/1.2 var(--font-interface); color: var(--ink, #2c2a26); cursor: pointer;
}
.keep-market-choice.selected { border-color: var(--moss, #7c8a63); box-shadow: 0 0 0 1px var(--moss, #7c8a63); }
.keep-market-choice input { margin: 0; }
.keep-market-note { margin: .45rem 0 0; font: 400 12.5px/1.45 var(--font-interface); color: var(--ink-soft, #6c6353); }
.keep-empty-note { margin: .4rem 0; font: 400 13px/1.45 var(--font-interface); color: var(--ink-soft, #6c6353); }
@media (max-width: 480px) {
  .keep-line { grid-template-columns: 1fr auto; }
  .keep-line-remove { grid-column: 2; justify-self: end; }
}
