/* ═══════════════════════════════════════════════════════════════════
   SWEETNOTE — REVEAL THEMING (reveal-layer)
   ─────────────────────────────────────────────────────────────────
   Scan.html's reveal surface. Themes itself from the CSS custom
   properties the reveal-design picker writes as URL params
   (--c-ground, --c-ink, --c-accent, --c-custom-font-stack,
    --c-text-color).

   ARCHITECTURE:
     scan.html on load:
       1. Reads ?bg_color, ?accent_color, ?text_color, ?font_slug,
          ?decor_slug, ?logo_key, ?note from URL
       2. Sets --c-* variables on <body> + applies decor-{slug} class
       3. JS wraps .scan-video-wrap in .scan-video-stage for frame
       4. JS populates optional inscription from ?note= URL param
   ═══════════════════════════════════════════════════════════════════ */


/* ─── BASE: scan.html theming reads from CSS custom properties ─── */

html {
  /* iOS Safari inflates text on landscape rotation without this, blowing out
     the clamp()-tuned greeting and the 3-up reaction row. The marketing site
     gets it from site.css, which the reveal deliberately does not load. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  background: var(--c-ground, var(--color-white, #FFFFFF));
  color: var(--c-ink, var(--color-navy, #0C1B2E));
  transition: background 200ms ease;
}

/* Header carrying the brand's own logo or brand name (on transparent bg so it adapts) */
body .scan-header {
  background: var(--c-ground, var(--brand-paper));
  border-bottom-color: var(--c-hairline, transparent);
}
body .scan-header-brand {
  color: var(--c-ink, var(--brand-ink));
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--brand-font-display);
  font-style: normal;
  font-weight: 500;
  font-size: 16px;
  letter-spacing: 0.01em;
}
body img.scan-header-brand-mark {
  width: 32px;
  height: 32px;
  display: block;
  flex-shrink: 0;
  object-fit: contain;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.08));
}

/* Greeting — defaults match the Modern minimal preset (non-italic
   Inter). The editorial-warm preset restores the italic-Cormorant
   signature via the override block below. */
body .scan-greeting-eyebrow {
  color: var(--c-eyebrow, var(--brand-accent-deep));
}
body .scan-greeting-eyebrow::before,
body .scan-greeting-eyebrow::after {
  background: var(--c-accent, var(--brand-accent));
}
body .scan-greeting-h1 {
  color: var(--c-ink, var(--brand-ink));
  font-family: var(--c-display-font, var(--brand-font-display));
  font-style: normal;
  font-weight: 600;
}
/* ⚠ THE HEADLINE IS ONE COLOUR. DO NOT RE-TINT THE NAME (founder, 2026-08-23).
   This rule used to paint .name with a THREE-STOP GRADIENT
   (accent-legible -> accent-deep -> accent-legible) clipped to the text, so
   "For Sarah" shipped as ink + three derived shades of a colour the brand
   never chose. THE WIZARD OFFERS EXACTLY TWO COLOURS: one surface and one
   accent (5 bg swatches + 6 accent swatches + hex). There is no headline
   treatment control, so every one of those shades was invented by the renderer.

   It also broke preview == ship (the 2026-07-09 lock). The two greeting paths
   diverged: an authored line arrives as ?greeting= and renders VERBATIM as the
   H1 in one colour — which is what the wizard preview shows — while a bare
   first name arrives as ?to=, gets wrapped in this span, and shipped a gradient
   the brand had never seen. Same campaign, two different headlines, decided by
   whether the CSV had a first_name column.

   The name still reads as the emphasis: it keeps its heavier weight. Colour is
   not what was carrying that, and colour is what we do not offer. */
body .scan-greeting-h1 .name {
  color: inherit;
  -webkit-text-fill-color: currentColor;
  background: none;
}
body .scan-greeting-hair {
  background: var(--c-accent, var(--brand-accent));
}
body .scan-greeting-from {
  color: var(--c-ink-muted, var(--brand-ink-muted));
}
body .scan-greeting-from strong {
  color: var(--c-ink, var(--brand-ink));
}
/* Editorial-warm preset restores the italic-Cormorant signature on
   greeting + the inscription handwritten-quote look. */
body[data-theme-preset="editorial-warm"] .scan-greeting-h1 {
  font-style: italic;
  font-weight: 500;
}

/* Video stage — themed frame painted around the video */
body .scan-video-stage {
  background: var(--c-video-frame, transparent);
  padding: 22px;
  border-radius: 26px;
  box-shadow:
    0 24px 64px color-mix(in srgb, var(--c-accent, #000) 18%, transparent),
    0 6px 16px color-mix(in srgb, var(--c-accent, #000) 8%, transparent),
    0 0 0 1px var(--c-video-border, rgba(0,0,0,0.08));
  /* The stage is the frame's mat: it must be exactly the frame plus its own
     2×22px padding. It was pinned to the OLD fixed 380px frame width; since
     2026-08-22 the frame is height-led (see THE PROPORTIONAL SCALE in
     scan.html), so the mat reads the same --sn-frame-w the frame does and the
     two cannot drift apart. The 380px fallback restores the pre-2026-08-22 mat
     for any document that loads this sheet without scan.html's ladder — today
     that is scan-expired.html and scan-unavailable.html, neither of which
     renders a video stage at all, so it is belt-and-braces. */
  /* ⚠ SIZE THE MAT FROM ITS CONTENT, NOT BY RE-DERIVING THE FRAME'S MATH.
     `max-width: calc(var(--sn-frame-w, 380px) + 44px)` was measured on
     2026-08-22 computing to 424px — the 380px FALLBACK — while the frame
     itself resolved the very same --sn-frame-w to 192.9px. The mat was then
     ~187px wider than its frame, and because the frame sits at the mat's 22px
     padding the whole video landed 94px LEFT of the page centre. The fallback
     path is why it was silent: 424px is a plausible width, so nothing looked
     broken until the offset was measured.
     `fit-content` makes the mat exactly the frame plus its own padding BY
     CONSTRUCTION, which is what the rule always meant. It cannot drift from
     --sn-frame-w because it no longer reads it. Keep the max-width below as a
     ceiling only. */
  width: fit-content;
  max-width: 100%;
  margin: 0 auto;
  position: relative;
  isolation: isolate;
}
body .scan-video-stage > .scan-video-wrap {
  box-shadow: 0 0 0 1px var(--c-video-border, rgba(0,0,0,0.08));
  /* `margin: 0` — reinstating it LEFT-ALIGNS THE VIDEO (fixed 2026-08-22). It
     was harmless while the frame was `width:100%` inside a 380px-capped stage
     (nothing to centre). The frame is now height-led with a computed 9:16
     width, so it is narrower than its column on most viewports and the auto
     margins are what centre it. Keep `auto`. */
  margin: 0 auto;
}

/* VIDEO-POSTER placeholder — when the template is themed, paint the
   poster frame with the template's themed gradient instead of the
   default dark brown. This is what Sarah sees BEFORE pressing play —
   a colorful celebration frame, matching what the thumbnail promised.
   
   When the real video plays, the poster is hidden via .hidden class
   and the actual video content takes over. */
body .scan-video-poster {
  /* Default fallback is slate-navy for the Modern minimal aesthetic.
     The inline @supports color-mix() block in scan.html handles the
     accent-tinted version on modern browsers. */
  background: var(--c-video-frame, linear-gradient(180deg, #1F3045 0%, #0A2540 100%)) !important;
}
body[data-theme-preset="editorial-warm"] .scan-video-poster {
  background: var(--c-video-frame, linear-gradient(180deg, #3D2818 0%, #1A0F08 100%)) !important;
}
/* Subtle theme-matched glow at center for visual interest */
body .scan-video-poster::after {
  background: radial-gradient(ellipse,
    color-mix(in srgb, var(--c-ground, var(--brand-paper, #FFFFFF)) 60%, transparent) 0%,
    transparent 70%) !important;
}
/* Play button — circle on white, arrow in brand ink */
body .scan-video-play {
  background: var(--c-video-play-bg, rgba(255, 255, 255, 0.96)) !important;
  border-color: var(--c-ground, var(--brand-paper)) !important;
  box-shadow: 0 12px 32px color-mix(in srgb, var(--c-accent, var(--brand-accent)) 30%, transparent) !important;
}
body .scan-video-play svg {
  color: var(--c-video-play-arrow, var(--brand-ink)) !important;
  fill: var(--c-video-play-arrow, var(--brand-ink)) !important;
}
/* The "Tap to play" hint text should be readable on the themed gradient */
body .scan-video-hint {
  color: var(--c-ink, var(--brand-ink)) !important;
  opacity: 0.75;
}
@media (max-width: 540px) {
  body .scan-video-stage {
    padding: 14px;
    border-radius: 20px;
    max-width: calc(var(--sn-frame-w, 380px) + 28px);
  }
}

/* CC button + action chips */
body .scan-video-cc {
  background: var(--c-ink, var(--brand-ink));
  color: var(--c-ground, var(--brand-paper));
}
body .scan-action-chip {
  border-color: var(--c-hairline, var(--brand-line));
  color: var(--c-ink-muted, var(--brand-ink-muted));
}
body .scan-action-chip:hover,
body .scan-action-chip:focus-visible {
  border-color: var(--c-accent, var(--brand-accent));
  color: var(--c-ink, var(--brand-ink));
}

/* Learn more CTA */
body .scan-reveal-cta {
  background: var(--c-ink, var(--brand-ink));
  color: var(--c-ground, var(--brand-paper));
}
body .scan-reveal-cta:hover,
body .scan-reveal-cta:focus-visible {
  background: var(--c-accent-deep, var(--brand-accent-deep));
}


/* ═══════════════════════════════════════════════════════════════════
   PERSONAL INSCRIPTION
   ─────────────────────────────────────────────────────────────────
   Optional 280-char note the sender writes during Customize. Shown
   below the post-video actions, above the product description.
   Hidden when the inscription is empty (scan.html's JS adds a
   .has-inscription class to body when a note is present).
   ═══════════════════════════════════════════════════════════════════ */

.scan-inscription {
  display: none;  /* hidden unless body.has-inscription */
  max-width: 560px;
  margin: 0 auto;
  /* 40px top at the 844 reference — proportional 2026-08-22 so the below-fold
     rhythm shrinks with the pre-play stack (see THE PROPORTIONAL SCALE in
     scan.html). Fallbacks kept literal: scan-expired/scan-unavailable load this
     sheet without the ladder. */
  padding: clamp(24px, 4.74vh, 40px) 24px 8px;
  text-align: center;
  position: relative;
}
body.has-inscription .scan-inscription {
  display: block;
}
/* Cinematic-rebound beat — the sender's written note is the emotional
   echo of the video. After video-played, the inscription fades in
   with generous spacing so it feels like a breath, not the next
   cascaded block. Research: peak-end rule (Kahneman) — the seconds
   immediately after the peak shape the whole memory. */
/* Visible by default on first scan (ungated 2026-06-13) — the enter-animation
   below is a play/preview enhancement (its fill-mode handles the entrance when
   it runs), never the thing that makes the inscription visible. Do NOT
   reintroduce a base opacity:0 hide on .after-play-content/.scan-inscription. */
body.has-inscription .scan-inscription { opacity: 1; }
body.has-inscription.video-played .scan-inscription,
body.has-inscription.is-preview .scan-inscription {
  animation: scan-inscription-enter 1200ms cubic-bezier(.2, .7, .2, 1) 400ms both;
}
@keyframes scan-inscription-enter {
  0%   { opacity: 0; transform: translateY(20px); }
  100% { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  body.has-inscription.video-played .scan-inscription,
  body.has-inscription.is-preview .scan-inscription { animation: none; }
}
.scan-inscription-body {
  font-family: var(--c-display-font, var(--brand-font-body));
  font-style: normal;
  font-weight: 400;
  /* Height-capped as well as width-capped (2026-08-22, THE PROPORTIONAL SCALE
     in scan.html). 15px floor is the legibility floor for body prose — below
     that the model stops scaling, the same refusal the consent disclosure
     makes at 12px. At the 844 reference min(22px, 2.61vh) = 22px, so nothing
     changes on the modal phone. */
  font-size: clamp(max(15px, min(18px, 2.13vh)), 3vw, max(15px, min(22px, 2.61vh)));
  line-height: 1.5;
  color: var(--c-ink, var(--brand-ink));
  max-width: 440px;
  margin: 0 auto;
  letter-spacing: -0.01em;
  /* Keep the brand's own line breaks (founder, 2026-09-23). The message is set
     with textContent and stored with its newlines intact; without this they
     collapsed into one paragraph. pre-line still wraps and collapses runs of
     spaces. MERGE AFTER the Gift Lift window closes (Oct 2): it is the live
     instrument's stylesheet until then. */
  white-space: pre-line;
}
/* Editorial-warm restores the handwritten-italic-Cormorant treatment */
body[data-theme-preset="editorial-warm"] .scan-inscription-body {
  font-family: var(--c-display-font, var(--brand-font-display));
  font-style: italic;
  line-height: 1.45;
  letter-spacing: -0.005em;
}
/* Decorative quote marks removed June 2026 — the message reads as the brand's
   own line, not a pull-quote. */
@media (max-width: 540px) {
  .scan-inscription {
    padding: clamp(18px, 3.32vh, 28px) 20px 4px;
  }
  .scan-inscription-body {
    font-size: max(15px, min(17px, 2.01vh));
  }
}


@media (prefers-reduced-motion: reduce) {
  body {
    transition: none !important;
  }
}



/* Pre-play teaser + keep-this-note email modal CSS removed
   April 2026 along with the DOM they styled. The reveal now uses
   the inline #save-btn "Save this video" download as the single
   post-play save surface. */


/* ───────────────────────────────────────────────────────────────
   ACTION CTA — universal optional single-button "next step" slot.
   Renders only when ?cta_url= is set (see wireAction in scan.html).
   Brand-themable via --c-accent (the same custom property that
   themes the inscription and impact components), so the button
   reads as the brand's color, not gold-default. Same slot every
   reveal type — replaces the prior Invitation RSVP click-through,
   Announcement 2-CTA gallery, and Acknowledgment story link.
   ─────────────────────────────────────────────────────────────── */
.scan-action {
  max-width: 620px;
  /* A line of space on each side of the CTA — more air after the message above
     (top) and before the reaction block below (bottom) so the next-step button
     stands on its own. */
  margin: var(--sn-p-7, var(--space-7)) auto var(--sn-p-6, var(--space-6));
  padding: 0 var(--space-5);
  text-align: center;
}
.scan-action[hidden] { display: none; }
.scan-action-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  /* 52px at the reference, 44px floor — well over the 24px WCAG 2.5.8 AA
     minimum. This is the Action affordance; never floor it below 44px. */
  padding: clamp(10px, 1.66vh, 14px) 26px;
  min-height: clamp(44px, 6.16vh, 52px);
  /* Action CTA carries white text, so its background must clear WCAG AA (4.5:1),
     not the 3:1 UI floor the bright --brand-accent meets (#0891B2 on white = 3.68:1).
     Use the AA-safe --brand-accent-dark per preset (modern #075F73 = 7.26:1 /
     corporate #2F4E60 = 8.83:1 / editorial #7A5A20 = 6.34:1). A tenant's own
     --c-accent still wins for brand-deference; tenant-loader must validate a custom
     accent's white-text contrast. The bright --brand-accent stays on borders, the
     play pulse, and the gradient — those only need the 3:1 UI floor. */
  background: var(--c-accent, var(--brand-accent-dark, #075F73));
  /* Label ink is GUARANTEED-contrast, not the surface color: scan.html's
     Surface+Accent resolver sets --c-accent-ink to pure #000/#FFF by the
     accent's luminance (crossover 0.179 → always ≥4.5:1 on any accent hex).
     The old color:var(--c-ground) inherited the surface, which read 2.3:1 on
     a bright accent (e.g. white-on-#06B6D4) and 2.3:1 on a dark surface with
     no custom accent. Fallback #FFFFFF matches every --brand-accent-dark
     preset (7.26:1 modern / 8.83:1 corporate / 6.34:1 editorial). */
  color: var(--c-accent-ink, #FFFFFF);
  border: 1.5px solid transparent;
  border-radius: 999px;
  font-family: var(--brand-font-body);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.01em;
  /* A spaceless label (wizard allows 40 chars verbatim) must wrap inside the
     pill at 320px, not overflow it. */
  max-width: 100%;
  overflow-wrap: anywhere;
  text-decoration: none;
  transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease, gap 0.2s ease;
  cursor: pointer;
}
.scan-action-cta:hover,
.scan-action-cta:focus-visible {
  transform: translateY(-1px);
  gap: 12px;
  box-shadow: 0 8px 24px -10px color-mix(in srgb, var(--c-accent, var(--brand-accent)) 60%, transparent);
}
.scan-action-cta:focus-visible {
  outline: 2px solid var(--c-accent, var(--brand-accent));
  outline-offset: 3px;
}
.scan-action-cta-arrow {
  display: inline-block;
  transition: transform 0.2s ease;
}
.scan-action-cta:hover .scan-action-cta-arrow,
.scan-action-cta:focus-visible .scan-action-cta-arrow {
  transform: translateX(2px);
}
.scan-action-note {
  margin: var(--space-2) 0 0;
  font-family: var(--brand-font-body);
  font-size: 11px;
  color: var(--c-ink-soft, var(--brand-ink-soft));
  letter-spacing: 0.02em;
}
@media (prefers-reduced-motion: reduce) {
  .scan-action-cta,
  .scan-action-cta-arrow { transition: none; }
  .scan-action-cta:hover { transform: none; gap: 8px; }
}
@media (max-width: 420px) {
  /* Keep the full space-7 top margin from the base rule here too — the reveal is
     always viewed at phone width, so a smaller mobile margin is what actually
     shows; the message-to-CTA gap must stay open on the phone, not just desktop. */
  .scan-action-cta { padding: 12px 20px; font-size: 13px; }
}
