/** Shopify CDN: Minification failed

Line 68:0 All "@import" rules must come first

**/
/* ================================================================
   Stamped Pages Customiser — V2 (UI-only rewrite)
   Namespaced to `.sp-app` to avoid collision with the rest of the
   theme. 95% of visual styling is set inline from embosser-v2.js;
   this file only carries:
     1) Design-token CSS variables
     2) Font loading (B791-Roman + Google Fonts)
     3) Resets scoped to .sp-app
     4) @keyframes (cannot be inlined)
     5) :hover / :focus / :active / ::placeholder / ::-webkit-scrollbar
        (pseudo-classes cannot be inlined)
     6) Mobile media-query tweaks
   ================================================================ */

/* ---- Auto-open loading skeleton --------------------------------
   Server-rendered inside #custom-embosser-root. Visible the moment
   the wrapper is display:flex (auto-open's head-script activator).
   embosser-v2.js's render() clears the root before mounting the
   live customiser, which removes this skeleton automatically.
   Kept above @import / @font-face so it paints even on the very
   first frame, before fonts have loaded.
   ---------------------------------------------------------------- */
.sp-pre-skeleton {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  width: 100%;
  height: 100%;
  min-height: 100vh;
  min-height: 100dvh;
  background: #F5F0EB;
}
.sp-pre-pulse {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #CAA75E;
  animation: sp-pre-pulse 1.2s ease-in-out infinite alternate;
}
.sp-pre-text {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-size: 14px;
  color: #8a7a64;
  letter-spacing: 1.2px;
  animation: sp-pre-fade 1.2s ease-in-out infinite alternate;
}
@keyframes sp-pre-pulse {
  from { transform: scale(0.6); opacity: 0.4; }
  to   { transform: scale(1.15); opacity: 1; }
}
@keyframes sp-pre-fade {
  from { opacity: 0.4; }
  to   { opacity: 0.95; }
}

/* ---- Web fonts (Google) ----------------------------------------
   Cormorant Garamond (display serif), Lato (UI sans), Fraunces
   (optional display serif alt). `display=swap` keeps paint fast.
   The browser will dedupe if the theme is also loading these.
   ---------------------------------------------------------------- */
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400;1,500;1,600&family=Lato:wght@300;400;500;700;900&family=Fraunces:ital,opsz,wght@0,9..144,300;0,9..144,400;0,9..144,500;0,9..144,600;0,9..144,700;1,9..144,400&display=swap');

/* ---- Proprietary embosser face --------------------------------
   Kept for stamp-text parity with the existing customiser. Files
   live in the theme's /assets/ dir and ship via the Shopify CDN.
   ---------------------------------------------------------------- */
@font-face {
  font-family: 'B791-Roman';
  src: url('b791-roman.woff2') format('woff2'),
       url('b791-roman.ttf')  format('truetype');
  font-display: swap;
  font-weight: 400;
  font-style: normal;
}

/* =====================================================================
   PDP "Personalise and add to basket" button
   Lifted verbatim from the pre-port embosser.css:818-902. Styles the
   theme-level button that OPENS this customiser; must live in this file
   because embosser.css is no longer loaded by the Liquid include.
   ===================================================================== */
.btn-generate-preview.push-btn::after { display: none !important; }
.btn-generate-preview.push-btn {
  background: transparent !important;
  border: none !important;
  box-shadow: none !important;
  padding: 0 !important;
  border-radius: 8px !important;
  transform: none !important;
  top: 0 !important;
}
@media (hover: hover) and (pointer: fine) {
  .btn-generate-preview.push-btn:hover { transform: none !important; }
  .btn-generate-preview.push-btn:hover .sp-personalise-surface {
    transform: none !important;
    background: #265761 !important;
    box-shadow: none !important;
  }
}
.btn-generate-preview .sp-personalise-surface::before { display: none !important; }
.btn-generate-preview .sp-personalise-surface {
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  width: 100% !important;
  padding: 20px 32px !important;
  border-radius: 8px !important;
  background: #326B75 !important;
  box-shadow: none !important;
  color: #fff !important;
  text-transform: none !important;
  transition: background 0.2s ease !important;
  pointer-events: none;
}
.btn-generate-preview .sp-personalise-inner {
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  gap: 10px !important;
}
.btn-generate-preview .sp-personalise-label {
  font-family: 'Lato', sans-serif !important;
  /* Bumped from 14 → 17 so the primary PDP commit CTA carries proper
     visual weight against the price + variant picker above it. */
  font-size: 17px !important;
  font-weight: 700 !important;
  letter-spacing: 0.04em !important;
  line-height: 1.25 !important;
  color: #fff !important;
  text-transform: none !important;
}
.btn-generate-preview .sp-personalise-icon {
  /* Sized up 15 → 19 to stay proportional to the bumped label. */
  width: 19px !important;
  height: 19px !important;
  flex-shrink: 0 !important;
  opacity: 0.9 !important;
  margin-top: 0 !important;
  color: #fff !important;
}
.btn-generate-preview.push-btn:active .sp-personalise-surface {
  background: #274F58 !important;
}
@media (max-width: 1024px) {
  /* Tablet/mobile: same generous label (17px) but a slightly smaller
     icon (17 vs 19) so a long button label wraps cleanly without
     breaking line on narrow phones. */
  .btn-generate-preview .sp-personalise-inner { gap: 9px !important; }
  .btn-generate-preview .sp-personalise-icon  { width: 17px !important; height: 17px !important; }
}

/* =====================================================================
   EMBOSSER WRAPPER GATE
   The theme's snippets/embosser_app.liquid injects:
     <div id="embosser-wrapper" style="display:none;">
       <div id="custom-embosser-root"></div>
     </div>
   ...and prepends it into `.product-details-main`. The wrapper must stay
   hidden until the PDP's "Personalize and add to basket" button adds the
   `.open` class to `.product-details-main`. This mirrors the pre-port
   behaviour (embosser.css:62-74) so the customiser doesn't render
   eagerly on product-page load.
   ===================================================================== */
#embosser-wrapper {
  display: none !important;
  position: fixed;
  top: 0 !important;
  left: 0; right: 0;
  /* Wrapper covers the full layout viewport at all times.
       - 100vh: fallback for pre-iOS-15.4 / pre-Chrome-108 browsers
                that don't support dvh. Uses the LARGEST viewport
                (URL bar minimised).
       - 100dvh: tracks the DYNAMIC viewport so as iOS Safari's
                bottom URL bar expands/contracts on scroll, the
                wrapper's bottom follows it. Without dvh, the sticky
                Add-to-cart bar would sit under the expanded URL bar
                and be untappable.
       - bottom: 0: belt-and-braces anchor to layout bottom for
                browsers where fixed + height layout is flaky.
     Zoom is blocked via viewport-meta while the customiser is open
     (see main-product.liquid), so visual viewport == layout viewport
     and no gap ever exposes the PDP. Keyboard doesn't change dvh —
     it only shrinks the VISUAL viewport — so the wrapper stays full
     and iOS auto-scrolls focused inputs inside the sheet. */
  height: 100vh;
  height: 100dvh;
  bottom: 0;
  overflow: hidden;
  background: #F5F0EB;
  z-index: 9999;
  -webkit-transform: translateZ(0);
  transform: translateZ(0);
}
.product-details-main.open #embosser-wrapper {
  display: flex !important;
  flex-direction: column;
  z-index: 2147483647 !important;
  /* Wrapper itself (background colour) shows instantly — anchors
     the viewport to the cream palette so the user registers "I'm
     in the customiser now" on frame 1. Content fades in on top
     over it via the rule below. */
}

/* Hide theme chrome (footer, floating buttons, sticky nav, live chat
   widgets) while the customiser is open. Even though the customiser
   wrapper uses position:fixed + z-index: max-int, Shopify themes
   often wrap their footer in a transform / sticky parent which
   spawns its own stacking context — breaking z-index ordering.
   Nuclear option: just hide the elements by name while
   .product-details-main.open is set on the page. */
html:has(.product-details-main.open) footer,
html:has(.product-details-main.open) [data-section-type="footer"],
html:has(.product-details-main.open) .footer,
html:has(.product-details-main.open) .site-footer,
html:has(.product-details-main.open) .pagefy-section-footer,
html:has(.product-details-main.open) .shopify-section-footer,
html:has(.product-details-main.open) [id*="footer" i]:not(#embosser-wrapper):not(#embosser-wrapper *),
html:has(.product-details-main.open) [class*="footer" i]:not(#embosser-wrapper):not(#embosser-wrapper *) {
  visibility: hidden !important;
  pointer-events: none !important;
}
/* Fallback for browsers without :has() support (Firefox < 121).
   JS sets body.sp-customiser-active when the wrapper opens so we
   can gate the same rules off a body class. */
body.sp-customiser-active footer,
body.sp-customiser-active .footer,
body.sp-customiser-active .site-footer,
body.sp-customiser-active [data-section-type="footer"],
body.sp-customiser-active [id*="footer" i]:not(#embosser-wrapper):not(#embosser-wrapper *),
body.sp-customiser-active [class*="footer" i]:not(#embosser-wrapper):not(#embosser-wrapper *) {
  visibility: hidden !important;
  pointer-events: none !important;
}
/* Two-stage fade: solid backdrop first, content second. Animating
   the wrapper's direct child (#custom-embosser-root) instead of the
   wrapper means the wrapper's paint is immediate and only the
   customiser UI does the opacity transition — reads cleaner than
   the whole rectangle fading in. `backwards` fill-mode applies the
   opacity-0 start from frame 1 so there's no one-frame full-opacity
   flash before the animation kicks in. */
.product-details-main.open #embosser-wrapper > * {
  animation: sp-customiser-in 0.35s ease-out backwards;
}
@keyframes sp-customiser-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Missing-pick toast slide-in — used by renderMissingPickToast when
   the customer taps the greyed Add-to-cart button. */
@keyframes sp-toast-in {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}
/* Desktop variant: toast is horizontally-centred via transform, so
   the slide-in needs to preserve the translateX. */
@media (min-width: 1025px) {
  @keyframes sp-toast-in {
    from { opacity: 0; transform: translate(-50%, 12px); }
    to   { opacity: 1; transform: translate(-50%, 0); }
  }
}

/* Stamp enters the preview card with a gentle opacity + scale fade
   whenever STATE.design changes (new design picked or AI generation
   completed). Opacity 0 -> 1 + a subtle 0.94 -> 1 scale gives an
   "eased in" rather than "popped in" feel. backwards fill-mode
   prevents a one-frame full-opacity flash before the animation
   begins. Border / handle / text edits don't re-trigger because
   the stamp host only animates when the design ID actually changes
   (see STATE._lastStampDesign in renderDesktop / renderMobileHero). */
@keyframes sp-stamp-in {
  from { opacity: 0; transform: scale(0.94); }
  to   { opacity: 1; transform: scale(1); }
}
/* Make the v2 root + app fill the wrapper when it's open. */
.product-details-main.open #embosser-wrapper #custom-embosser-root,
.product-details-main.open #embosser-wrapper .sp-app {
  flex: 1;
  min-height: 0;
  width: 100%;
}

/* ---- Design tokens --------------------------------------------- */
.sp-app {
  /* Color scale */
  --sp-ink:        #2a2520;
  --sp-ink-soft:   #5c544b;
  --sp-muted:      #8a7f72;
  --sp-bg:         #f5ebe0;
  --sp-panel:      #e9e2d4;
  --sp-panel-2:    #ddd2bf;
  --sp-card:       #ffffff;
  /* Accent — user direction: warm olive (not amber/tan). mockup3.jsx uses
     #c08b5c as its internal fallback but user overrode to olive at CP-A
     foundation review. Flagged in handback for confirmation. */
  --sp-accent:     #8a7a3e;      /* warm olive */
  --sp-accent-ink: #60562c;
  --sp-accent-55:  #8a7a3e55;
  --sp-accent-00:  #8a7a3e00;
  --sp-accent-18:  #8a7a3e2e;    /* 18% alpha for progress pill bg */
  --sp-accent-40:  #8a7a3e66;    /* 40% alpha for open-card border */
  --sp-accent-80:  #8a7a3ecc;    /* 80% alpha for VERIFIED BUYER */
  --sp-accent-cc:  #8a7a3ecc;
  --sp-divider:    #d4c4a8;
  --sp-divider-soft: #e5dac5;
  --sp-danger:     #b85c3a;
  --sp-good:       #5e8a5c;

  /* Typography */
  --sp-font-ui:      'Lato', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --sp-font-display: 'Cormorant Garamond', 'Fraunces', 'Georgia', serif;
  --sp-font-stamp:   'B791-Roman', 'Cormorant Garamond', 'Georgia', serif;

  /* Layout */
  --sp-radius-sm: 6px;
  --sp-radius-md: 10px;
  --sp-radius-lg: 14px;
  --sp-radius-xl: 20px;
  --sp-radius-pill: 100px;

  /* Shadows */
  --sp-shadow-card:
    0 40px 60px -20px rgba(80,55,30,0.35),
    0 12px 20px  -8px rgba(80,55,30,0.18),
    0 1px 2px rgba(0,0,0,0.08),
    inset 0 1px 0 rgba(255,255,255,0.9);
  --sp-shadow-soft:
    0 4px 14px rgba(80,60,40,0.12);
  --sp-shadow-chip:
    0 2px 8px rgba(60,40,20,0.10);

  /* Breakpoint (matches theme's existing 768 cutoff) */
  /* Mobile breakpoint covers all iPads in portrait (up to iPad
     Pro 12.9" at 1024px) plus phones. The desktop layout is a
     1.3fr/1fr two-column grid that needs ~1100px+ to give the
     right column enough horizontal space; below that, the right
     column compresses to phone-narrow widths and the DYO banner
     starts wrapping every word. Mobile layout (single-column
     scroll with sticky preview) handles any width gracefully so
     iPads use it in portrait and only switch to desktop in
     landscape (iPad Air landscape = 1180, iPad Pro 11" = 1194). */
  --sp-bp: 1024px;

  /* Paper texture (used by JS as a background-image value) */
  --sp-paper-tex:     url('paper-texture.avif');
  --sp-paper-tex-wh:  url('paper-texture-white.avif');

  /* Z-index scale — keeps under theme header/cart-drawer */
  --sp-z-sticky:    40;
  --sp-z-overlay:   900;
  --sp-z-sheet:     910;
  --sp-z-modal:     920;
  --sp-z-toast:     930;

  /* Base */
  color: var(--sp-ink);
  font-family: var(--sp-font-ui);
  font-size: 14px;
  line-height: 1.35;
  background: var(--sp-bg);
}

/* ---- Resets, scoped to .sp-app --------------------------------- */
.sp-app *,
.sp-app *::before,
.sp-app *::after { box-sizing: border-box; }

.sp-app button {
  font-family: inherit;
  cursor: pointer;
  border: none;
  background: none;
  padding: 0;
  color: inherit;
}

.sp-app input,
.sp-app textarea,
.sp-app select {
  font-family: inherit;
  color: inherit;
}

.sp-app a { color: inherit; text-decoration: none; }

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

/* ---- Paper texture helper -------------------------------------- */
.sp-paper-tex {
  background-color: #f5f0e6;
}
.sp-paper-tex--white {
  background-color: #ffffff;
  background-image: var(--sp-paper-tex-wh);
  background-size: cover;
}

/* ================================================================
   Keyframes — lifted verbatim from the JSX `<style>` blocks
   ================================================================ */

/* Desktop — progress-pill attention pulse (mockup3.jsx:176) */
@keyframes sp-pulse-dot {
  0%, 100% { box-shadow: 0 0 0 0 var(--sp-accent-55); }
  50%      { box-shadow: 0 0 0 4px var(--sp-accent-00); }
}

/* Mobile — progress-pill pulse (mockup3-mobile.jsx:166) */
@keyframes sp-m-pulse-dot {
  0%, 100% { box-shadow: 0 0 0 0 var(--sp-accent-55); }
  50%      { box-shadow: 0 0 0 4px var(--sp-accent-00); }
}

/* Neutral pulse — progress pill uses stone, not accent, for the "not yet
   done" state (per user direction — the pill should read neutral, the
   accent is reserved for completion). */
@keyframes sp-pulse-dot-neutral {
  0%, 100% { box-shadow: 0 0 0 0 rgba(120,100,80,0.35); }
  50%      { box-shadow: 0 0 0 4px rgba(120,100,80,0.00); }
}

/* Mobile bottom-sheet slide-in (mockup3-mobile.jsx:170) */
@keyframes sp-sheet-in {
  from { transform: translateY(100%); }
  to   { transform: translateY(0); }
}

/* Backdrop fade-in (mockup3-mobile.jsx:174) */
@keyframes sp-backdrop-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Design picker slide-down (mockup3.jsx:1433) */
@keyframes sp-slide-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Generic shimmer (mockup3.jsx:1430) */
@keyframes sp-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* Spinner (mockup3.jsx:1429) */
@keyframes sp-spin { to { transform: rotate(360deg); } }

/* Describe-your-own panel (section 10b in embosser-v2.js):
   - dyo-fade: ambient fade-in for the panel + sentence card
   - dyo-tray-in: tray slides down when a blank is opened
   - dyo-spin: CTA spinner during generation
   - dyo-shake: shake the pill of a missing-required blank on invalid submit */
@keyframes dyo-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes dyo-tray-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes dyo-spin { to { transform: rotate(360deg); } }
@keyframes dyo-shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-4px); }
  40% { transform: translateX(4px); }
  60% { transform: translateX(-3px); }
  80% { transform: translateX(3px); }
}
.dyo-shake { animation: dyo-shake 0.45s ease-in-out; }

/* Mobile DYO sheet — expands from its docked position (below the
   preview card) to an exact content-sized top, and retracts back
   smoothly on close. `--dyo-sheet-top` is set from JS after measuring
   the sheet's scrollHeight so the sheet fits its content without ever
   leaving empty space above it. Falls back to 76px if JS hasn't
   measured yet (e.g. first paint before RAF). */
@keyframes dyo-sheet-expand {
  from { top: calc(82px + 70vw); }
  to   { top: var(--dyo-sheet-top, 76px); }
}
@keyframes dyo-sheet-collapse {
  from { top: var(--dyo-sheet-top, 76px); }
  to   { top: calc(82px + 70vw); }
}
/* Backdrop fades to a dark overlay while DYO is active so the
   preview area reads as "out of focus" for the moment. */
@keyframes dyo-backdrop-in {
  from { background: rgba(0,0,0,0); }
  to   { background: rgba(0,0,0,0.45); }
}
@keyframes dyo-backdrop-out {
  from { background: rgba(0,0,0,0.45); }
  to   { background: rgba(0,0,0,0); }
}

/* DYO pill inputs & selects — kill every browser default that draws
   around the pill: outlines, focus rings, spellcheck underlines. The
   pill's own soft brass hairline (set inline) is the only chrome.
   Explicit caret-color so the text cursor stays visible when the
   pill is focused — appearance:none on some iOS/Android builds
   suppresses the default caret otherwise. */
/* Higher-specificity selectors (input.dyo-pill / button.dyo-pill,
   scoped under .sp-app) deliberately to beat the mobile-section
   defensive rule `.sp-app input { font-size: 16px !important }`
   below. That rule prevents iOS auto-zoom on focused inputs, but
   its selector is more specific than a bare .dyo-pill so on mobile
   the 16px would override our 22px and the input blanks render
   smaller than the <button> Style picker (which doesn't match the
   `input` rule at all). With this 0,0,2,1 specificity our font-size
   declarations win on every device. */
.sp-app input.dyo-pill,
.sp-app button.dyo-pill {
  outline: none !important;
  box-shadow: none !important;
  caret-color: #1a1712 !important;
  /* Form controls (input / button) IGNORE JS-set font-size: inherit
     in Safari and some Chromium builds — they keep the UA default
     form-control font (~13px, system-ui) regardless. `font: inherit`
     in the shorthand DOES work, but it also drags in line-height
     from the parent, which here is 1.95-2.05 (intentionally large
     so the sentence doesn't crowd when it wraps to two lines). A
     tall line-height inflates the input's line-box and pushes the
     gold bottom-border far below the actual glyphs, leaving an ugly
     gap between the text and the underline.
     Solution: hard-code the font-size to match the sentence (22px
     mobile / 26px desktop), keep line-height at 1.25 so the border
     sits tight under the letters, and inherit only family / style.
     If the sentence font-size ever changes the @media values below
     need to follow it in lockstep. */
  font-family: inherit !important;
  font-style: italic !important;
  font-weight: 700 !important;
  font-size: 22px !important;
  line-height: 1.25 !important;
}
@media (min-width: 768px) {
  .sp-app input.dyo-pill,
  .sp-app button.dyo-pill {
    font-size: 26px !important;
  }
}
.dyo-pill:focus,
.dyo-pill:focus-visible,
.dyo-pill:active {
  outline: none !important;
  box-shadow: none !important;
  border-color: #C5A96C !important; /* brand gold — matches unfocused pill */
  caret-color: #1a1712 !important;
}

/* Hide the placeholder the moment the customer taps the input — they
   know what they're typing into (the sentence around it spells it out),
   so a visible placeholder competes with their own input and is a
   visual distraction. Input width is held stable by the autosize
   sizer, so hiding the text doesn't collapse the pill. */
.dyo-pill:focus::placeholder,
.dyo-pill:focus::-webkit-input-placeholder,
.dyo-pill:focus::-moz-placeholder,
.dyo-pill:focus:-ms-input-placeholder {
  color: transparent !important;
  opacity: 0 !important;
}
/* Hide the dropdown arrow added by some Firefox / Edge builds that
   don't respect appearance: none on <select>. */
.dyo-pill::-ms-expand { display: none; }

/* AI-generation overlay — three staggered ink-bloom rings (shared.jsx:581) */
@keyframes sp-gen-ink-bloom {
  0%   { transform: scale(0.35); opacity: 0; }
  15%  { opacity: 0.55; }
  100% { transform: scale(1.25); opacity: 0; }
}
@keyframes sp-gen-halo-pulse {
  0%, 100% { transform: scale(0.92); opacity: 0.35; }
  50%      { transform: scale(1.04); opacity: 0.65; }
}
@keyframes sp-gen-caption-dots {
  0%   { content: ''; }
  25%  { content: '.'; }
  50%  { content: '..'; }
  75%  { content: '...'; }
  100% { content: ''; }
}
@keyframes sp-gen-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.sp-gen-caption::after {
  content: '';
  display: inline-block;
  width: 16px;
  text-align: left;
  animation: sp-gen-caption-dots 1.6s steps(4, end) infinite;
}

/* Modal fade + pop (save-design-modal.jsx:363, extras-playful.jsx:730) */
@keyframes sp-modal-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes sp-modal-pop {
  from { opacity: 0; transform: scale(0.96) translateY(6px); }
  to   { opacity: 1; transform: scale(1) translateY(0); }
}
@keyframes sp-modal-slide-up {
  from { transform: translateY(100%); }
  to   { transform: translateY(0); }
}

/* ================================================================
   Pseudo-class states — can't be inlined from JS
   ================================================================ */

/* Top-bar + save button (mockup3.jsx:157-166) */
.sp-app .sp-topbar-btn {
  transition: background 0.15s ease, box-shadow 0.15s ease;
}
.sp-app .sp-topbar-btn:hover { background: rgba(255,255,255,0.6); }
.sp-app .sp-save-btn:hover {
  background: #fff;
  box-shadow: var(--sp-shadow-soft);
}
.sp-app .sp-topbar-btn:focus-visible {
  outline: 2px solid var(--sp-accent);
  outline-offset: 2px;
}

/* Section accordion trigger (mockup3.jsx:167-173) */
.sp-app .sp-section-trigger {
  transition: background 0.12s ease;
}
.sp-app .sp-section-trigger:hover { background: rgba(0,0,0,0.015); }
.sp-app .sp-section-trigger:focus-visible {
  outline: 2px solid var(--sp-accent);
  outline-offset: -2px;
  border-radius: 14px;
}

/* Mobile press / focus feedback (mockup3-mobile.jsx:179-181) */
.sp-app .sp-press {
  transition: transform 0.1s ease, background 0.15s ease;
}
.sp-app .sp-press:active { transform: scale(0.96); }
/* While loading: the sp-cta aria-busy state suppresses the press
   scale-down so the spinner doesn't jitter. */
.sp-app .sp-press[aria-busy="true"]:active { transform: none; }

/* Add-to-cart loading spinner — renders inside .sp-cta when
   STATE._checkoutInFlight is true. Wrapping animation target is the
   <svg> so only the glyph rotates; the label next to it stays
   readable. */
.sp-app .sp-cta-spinner {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}
.sp-app .sp-cta-spinner svg {
  animation: sp-cta-spin 0.85s linear infinite;
  transform-origin: center;
}
@keyframes sp-cta-spin {
  to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .sp-app .sp-cta-spinner svg { animation-duration: 2s; }
}
.sp-app .sp-btn:focus-visible {
  outline: 2px solid var(--sp-accent);
  outline-offset: 2px;
}

/* Generic clickable tile */
.sp-app .sp-tile {
  transition: transform 0.1s ease, border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
}
.sp-app .sp-tile:hover:not([disabled]) { background: rgba(0,0,0,0.02); }
.sp-app .sp-tile:active:not([disabled]) { transform: scale(0.98); }
.sp-app .sp-tile:focus-visible {
  outline: 2px solid var(--sp-accent);
  outline-offset: 2px;
}

/* Primary CTA (add-to-cart style) */
.sp-app .sp-cta {
  transition: transform 0.1s ease, box-shadow 0.15s ease, opacity 0.15s ease;
}
.sp-app .sp-cta:hover:not([disabled]) {
  box-shadow: 0 6px 18px rgba(80,60,40,0.18);
}
.sp-app .sp-cta:active:not([disabled]) { transform: scale(0.98); }
.sp-app .sp-cta[disabled] {
  opacity: 0.55;
  cursor: not-allowed;
}
.sp-app .sp-cta:focus-visible {
  outline: 2px solid var(--sp-accent);
  outline-offset: 3px;
}

/* Text inputs */
.sp-app .sp-input,
.sp-app .sp-textarea {
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.sp-app .sp-input:focus,
.sp-app .sp-textarea:focus {
  outline: none;
  border-color: var(--sp-accent);
  box-shadow: 0 0 0 3px var(--sp-accent-55);
}
.sp-app .sp-input::placeholder,
.sp-app .sp-textarea::placeholder {
  color: var(--sp-muted);
  opacity: 0.8;
}

/* Scrollbars inside the design picker (mockup3.jsx:1436-1439) */
.sp-app .sp-scroll::-webkit-scrollbar { width: 6px; height: 6px; }
.sp-app .sp-scroll::-webkit-scrollbar-track { background: transparent; }
.sp-app .sp-scroll::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--sp-accent) 40%, transparent);
  border-radius: 3px;
}
.sp-app .sp-scroll::-webkit-scrollbar-thumb:hover {
  background: color-mix(in srgb, var(--sp-accent) 70%, transparent);
}

/* Mobile scroll areas — hidden scrollbar (mockup3-mobile.jsx:183-184) */
.sp-app .sp-mscroll::-webkit-scrollbar { display: none; }
.sp-app .sp-mscroll { scrollbar-width: none; }

/* Disabled links and buttons */
.sp-app button[disabled],
.sp-app .sp-disabled {
  cursor: not-allowed;
}

/* ================================================================
   Mobile (≤ 768px)
   ================================================================ */
@media (max-width: 1024px) {
  .sp-app { font-size: 14px; }
  .sp-app .sp-desktop-only { display: none !important; }

  /* iOS Safari auto-zooms any focused input/textarea whose computed
     font-size is < 16px. That zoom never undoes cleanly — the page
     re-lays-out at the zoomed scale and mobile browsers get stuck.
     Forcing 16px on every editable field kills the zoom behaviour
     entirely and keeps typing smooth. */
  .sp-app input,
  .sp-app textarea,
  .sp-app select {
    font-size: 16px !important;
  }
}

/* AVIF fallback \u2014 if the browser can't decode paper-texture.avif,
   the tile stays white which is an acceptable graceful degradation.
   Every browser in our support matrix has AVIF since 2023, but this
   keeps the card from flashing an `alt`-style broken-image icon in
   edge cases (strict CSP, offline). */
@supports not (background-image: url('data:image/avif;base64,AAAA')) {
  .sp-app .sp-paper-tex--white {
    background-image: none !important;
    background: #fdfbf6 !important;
  }
}
@media (min-width: 1025px) {
  .sp-app .sp-mobile-only { display: none !important; }
}

/* ================================================================
   Paired-letters initials input (Text step, monogram designs only)
   ================================================================ */
.sp-app .sp-initials-label {
  font-family: var(--sp-font-ui, system-ui, sans-serif);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: #888;
  margin-bottom: 10px;
  text-align: center;
}
.sp-app .sp-initials-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
}
.sp-app .sp-initials-slot {
  width: 58px;
  height: 70px;
  padding: 0;
  margin: 0;
  font-family: 'B791-Roman', 'Cormorant Garamond', 'Georgia', serif;
  font-size: 38px;
  font-weight: 500;
  line-height: 1;
  text-align: center;
  color: var(--sp-ink, #2a2520);
  background: #fcf8f0;
  border: 1px solid rgba(42, 37, 32, 0.12);
  border-radius: 12px;
  outline: none;
  caret-color: var(--sp-accent, #8a7a3e);
  transition: background 0.15s ease, border-color 0.15s ease;
  -webkit-appearance: none;
  appearance: none;
  box-sizing: border-box;
}
.sp-app .sp-initials-slot:focus {
  background: #fff;
  border-color: var(--sp-accent, #8a7a3e);
}
.sp-app .sp-initials-dot {
  font-family: 'B791-Roman', 'Cormorant Garamond', 'Georgia', serif;
  font-size: 28px;
  color: rgba(42, 37, 32, 0.55);
  transform: translateY(-8px);
  line-height: 1;
  user-select: none;
  pointer-events: none;
}

/* ================================================================
   Variant B — single-page-scroll customiser layout
   ----------------------------------------------------------------
   VB reuses the .sp-app shell and chrome from Variant A in full —
   topbar (logo, close, save), preview panel, header (heading +
   progress pill), basket icon, ATC CTA, save modal, AI generation
   overlay, every animation. Only the right-column / mobile
   section content swaps from accordion sections to single-scroll
   sections with horizontal scrollers on mobile. Active when
   window.SP_EXPERIMENTS.customiser_design === 'b'.
   ================================================================ */

/* Hide the accordion chevron on VB section cards — they're always
   open, no toggle behaviour. */
.sp-vb .sp-section-card .sp-section-chevron { display: none !important; }

/* Allow the text inputs to flex-shrink below their intrinsic
   content width. Default flex item min-width is `auto`
   (= intrinsic content size) which on narrow viewports prevents
   the input from sharing space with its sibling case button —
   resulting in the input pushing the row wider than the section
   card and the visible cream pill being narrower than the input
   wants. min-width: 0 unlocks shrink-below-content-width so the
   input always fits its row regardless of placeholder length. */
.sp-vb .sp-section-card input.sp-input {
  min-width: 0 !important;
}

/* Horizontal scroller — used for Design tiles, Border swatches,
   and Handle Finish swatches on MOBILE only. Scroll-snap on each
   tile gives a deliberate "stop on each option" feel. Right-edge
   peek of the next tile is the "swipe for more" cue.
   On desktop the section bodies fall through to renderSectionStub_X
   directly (grid layouts that fit the wider viewport); horizontal
   scrolling is a mobile-only treatment. */
.sp-vb-hscroll {
  position: relative;
  margin: 0 -8px;
  padding: 4px 0 4px;
}
.sp-vb-hscroll__track {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding: 8px;
  padding: 4px 8px 12px;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.sp-vb-hscroll__track::-webkit-scrollbar { display: none; }

/* Right-edge fade-out cue. Subtle gradient over the fade colour
   of the section card (white) so the next-tile peek transitions
   into "more designs this way" rather than ending hard. */
.sp-vb-hscroll::after {
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 16px;
  width: 24px;
  background: linear-gradient(90deg, rgba(255,255,255,0) 0%, #fff 100%);
  pointer-events: none;
  border-top-right-radius: 12px;
  border-bottom-right-radius: 12px;
}

/* Persistent scroll indicator — sits below the track, always
   visible (unlike native mobile scrollbars which auto-hide).
   Brand-gold thumb on a faint warm-grey track. JS sets the
   thumb's width + translateX to mirror the scroll position. */
.sp-vb-hscroll__indicator {
  position: relative;
  margin: 4px 16px 4px;
  height: 3px;
  background: rgba(80, 60, 40, 0.10);
  border-radius: 2px;
  overflow: hidden;
  opacity: 1;
  transition: opacity 0.18s ease;
}
.sp-vb-hscroll__indicator-thumb {
  position: absolute;
  top: 0; bottom: 0; left: 0;
  width: 30%;
  background: rgba(202, 167, 94, 0.7);
  border-radius: 2px;
  transition: transform 0.05s linear;
  will-change: transform;
}
@media (min-width: 1025px) {
  .sp-vb-hscroll__indicator { margin-left: 0; margin-right: 0; }
}

/* Tile cells inside the scroller — sized so ~4 tiles peek on a
   375px viewport (the partial-tile edge IS the swipe cue). Smaller
   than VAs grid tiles by design: more variety visible at once,
   less commit per tap. Wide variant for handle-finish photos
   which read better landscape. Min width keeps them tappable.
   Yes, these scale with the viewport (22vw) within the px
   bounds, so phones in the 320-414 range get 76-91px and tablets
   cap at the max. */
.sp-vb-hscroll__tile {
  flex: 0 0 auto;
  width: 22vw;
  max-width: 96px;
  min-width: 76px;
  scroll-snap-align: start;
}
/* Tablet bump — at iPad widths the 96px cap looked cramped
   relative to the available section width. Bump max to 120px
   so design tiles read at a comfortable size on iPad (about 6-7
   visible per row at 820px) without going so big that the
   horizontal scroller becomes a single-line list. */
@media (min-width: 600px) {
  .sp-vb-hscroll__tile { max-width: 120px; }
}
.sp-vb-hscroll__tile--wide {
  /* Tighter again — labels like "Polished Brass" still fit on
     one line at 12.5px without wasted air around the swatch. */
  width: 32vw;
  max-width: 130px;
  min-width: 110px;
}

/* Tablet stretch — at iPad mini width (768) and up, the handle-
   finish row has ~720px of usable space inside the section card,
   plenty of room for all 4 tiles to share the row evenly. The
   horizontal scroller treatment looks wrong here (tiles cluster
   on the left, big empty space on the right). Flex-stretch the
   tiles so they fill the row, drop the snap + scrollbar +
   right-edge fade, and let the swatches read as a balanced
   grid. Below 600px the hscroll stays (small phones don't have
   the room to fit 4 tiles comfortably without crushing labels). */
/* Tablet stretch is SCOPED to the wide-tile variant only. The wide
   variant is used for handle finishes (4 tiles) where stretching
   to fill the row makes sense at iPad widths. Applying it to ALL
   hscroll tiles broke the design grid (90+ tiles squashed to
   ~12-45px each) and the border row (3 tiles spread across full
   width). The narrow / default variant keeps its mobile-style
   horizontal scrolling on tablets so design thumbnails stay
   tappable + readable.
   `:has()` lets us narrow the wrapper-level CSS (overflow, fade,
   indicator) to wrappers that contain a wide tile, so the design
   grid keeps its scrollbar / fade / indicator on tablets. */
@media (min-width: 600px) {
  .sp-vb-hscroll:has(.sp-vb-hscroll__tile--wide) .sp-vb-hscroll__track {
    overflow-x: visible;
    scroll-snap-type: none;
  }
  .sp-vb-hscroll:has(.sp-vb-hscroll__tile--wide)::after { display: none; }
  .sp-vb-hscroll:has(.sp-vb-hscroll__tile--wide) .sp-vb-hscroll__indicator { display: none; }
  .sp-vb-hscroll__tile--wide {
    flex: 1 1 0;
    width: auto;
    max-width: none;
    min-width: 0;
    scroll-snap-align: none;
  }
}

/* Desktop short-circuit. We never hit the hscroll branch on
   desktop (the JS routes desktop sections to renderSectionStub_X
   directly), but if a future caller mounts hscroll on desktop the
   wrap below ensures it doesn't look broken. */
@media (min-width: 1025px) {
  .sp-vb-hscroll__track {
    flex-wrap: wrap;
    overflow-x: visible;
    scroll-snap-type: none;
  }
  .sp-vb-hscroll::after { display: none; }
  .sp-vb-hscroll__tile {
    width: auto;
    max-width: none;
    min-width: 0;
    flex: 0 0 calc(25% - 8px);
    scroll-snap-align: none;
  }
}

/* "Describe your own" banner — full-width gold-gradient card sitting
   above the design scroller. Replaces the in-scroller DYO hero
   tile so the option to create something bespoke is unmissable
   but still feels deliberate (not mixed with the library). */
.sp-vb-dyo-banner {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  padding: 12px 14px;
  margin-bottom: 14px;
  background: linear-gradient(135deg, #f7efde 0%, #f0e6cf 100%);
  border: 1px solid rgba(138, 122, 62, 0.22);
  border-radius: 14px;
  cursor: pointer;
  text-align: left;
  transition: transform 0.1s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
.sp-vb-dyo-banner:hover {
  border-color: rgba(138, 122, 62, 0.45);
  box-shadow: 0 4px 14px rgba(138, 122, 62, 0.18);
}
.sp-vb-dyo-banner:active { transform: scale(0.985); }
.sp-vb-dyo-banner__icon {
  width: 40px; height: 40px;
  flex-shrink: 0;
  border-radius: 50%;
  background: rgba(255,255,255,0.7);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--sp-accent-ink);
}
.sp-vb-dyo-banner__text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.sp-vb-dyo-banner__title {
  font-family: var(--sp-font-display);
  font-size: 16px;
  font-weight: 600;
  font-style: italic;
  color: var(--sp-ink);
  line-height: 1.2;
}
.sp-vb-dyo-banner__sub {
  font-family: var(--sp-font-ui);
  font-size: 12.5px;
  color: var(--sp-ink-soft);
  line-height: 1.35;
}
.sp-vb-dyo-banner__cta {
  width: 32px; height: 32px;
  flex-shrink: 0;
  border-radius: 50%;
  background: rgba(255,255,255,0.5);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--sp-accent-ink);
}

/* ─── Progress stepper (3 named steps) ────────────────────────────
   Replaces VAs "X of N" pill with three clickable named steps:
   Design / Handle finish / Extras. Each step is a tappable button
   that smooth-scrolls the customer to the matching section card.
   Done state = filled gold circle + checkmark; future state =
   outlined circle + step number. Connector line bridges between
   adjacent steps in muted gold. */
.sp-vb-stepper {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 6px 0;
  margin-top: 4px;
}
.sp-vb-stepper__step {
  flex: 0 1 auto;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 4px 4px;
  background: transparent;
  border: none;
  cursor: pointer;
  min-width: 0;
}
.sp-vb-stepper__circle {
  width: 24px; height: 24px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 700;
  background: #fff;
  color: #CAA75E;
  border: 1.5px solid #CAA75E;
  box-shadow: 0 1px 2px rgba(0,0,0,0.04);
  transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease;
  flex-shrink: 0;
}
.sp-vb-stepper__circle--done {
  background: #CAA75E;
  border-color: #CAA75E;
  color: #fff;
  box-shadow: 0 2px 6px rgba(202,167,94,0.25);
}
.sp-vb-stepper__label {
  font-family: var(--sp-font-ui);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--sp-ink-soft);
  white-space: nowrap;
  text-align: center;
}
.sp-vb-stepper__label--done {
  color: var(--sp-ink);
  font-weight: 600;
}
.sp-vb-stepper__connector {
  flex: 1 1 auto;
  min-width: 6px;
  max-width: 24px;
  height: 1.5px;
  background: rgba(202,167,94,0.35);
  border-radius: 2px;
  margin-top: -18px;  /* align with the centre of the circle row */
  transition: background 0.18s ease;
}
@media (max-width: 360px) {
  .sp-vb-stepper__step { padding: 4px 2px; }
  .sp-vb-stepper__circle { width: 22px; height: 22px; font-size: 10px; }
  .sp-vb-stepper__label { font-size: 10px; }
}
@media (min-width: 1025px) {
  /* Stepper centres in the right column on desktop, matching
     the centre-aligned heading + subtitle above it. */
  .sp-vb-stepper { gap: 4px; justify-content: center; }
  .sp-vb-stepper__step { padding: 4px 8px; }
  .sp-vb-stepper__connector { max-width: 36px; }
  .sp-vb-stepper__circle { width: 28px; height: 28px; font-size: 12px; }
  .sp-vb-stepper__label { font-size: 11.5px; }
}
.sp-vb-stepper__step:focus-visible {
  outline: 2px solid var(--sp-accent);
  outline-offset: 4px;
  border-radius: 8px;
}

/* High-flicker elements get their own compositor layers + paint
   containment so the browser can preserve them across renders even
   when the surrounding tree rebuilds. will-change: opacity hints
   that opacity may animate; contain: paint isolates the subtree's
   paint operations so they don't invalidate neighbours.
   (We previously paired this with document.startViewTransition() to
   add an 80ms cross-fade, but the snapshot overhead added perceptible
   click-to-update latency on mobile -- the view transition has been
   removed; only the compositor hints remain.) */
[data-sp-vb-mini-preview],
[data-sp-vb-topbar] {
  will-change: opacity;
  contain: paint;
}

/* ================================================================
   Print — stamp preview should not be part of print output
   ================================================================ */
@media print {
  .sp-app { display: none !important; }
}
