/* ============================================================
   ThePawlure — Main Styles
   Screen 0: Hero / Home
   ============================================================ */


/* ------------------------------------------------------------
   1. Reset & Base
------------------------------------------------------------ */

*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  font-size: 16px;
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  overflow-x: hidden;
}

body {
  background-color: var(--bg-base);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-weight: 400;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

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

button {
  font-family: inherit;
  cursor: pointer;
  border: none;
  background: none;
}

a {
  text-decoration: none;
  color: inherit;
}

::selection {
  background: rgba(201, 168, 76, 0.25);
  color: var(--text-primary);
}


/* ------------------------------------------------------------
   2. Screen system
------------------------------------------------------------ */

.screen {
  width: 100%;
}

.screen--active {
  display: block;
}

.placeholder-screen {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-tertiary);
  font-size: var(--font-size-body-medium);
}


/* ------------------------------------------------------------
   3. Header
------------------------------------------------------------ */

.header {
  position: fixed;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  width: calc(100% - 48px);
  max-width: calc(var(--max-width) + 48px);
  height: var(--header-height);
  background: rgba(24, 20, 16, 0.72);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  /* 3-column grid: logo | nav (centered) | [right slot] */
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  padding: 0 var(--space-3);
  z-index: 100;
  transition: border-color var(--transition-base);
  /* Safari: promote to own compositing layer */
  -webkit-transform: translateX(-50%) translateZ(0);
  transform: translateX(-50%) translateZ(0);
}

/* Logo — column 1, left-aligned */
.header-logo {
  display: flex;
  align-items: center;
  height: 40px;
  flex-shrink: 0;
  /* Gold color via currentColor on inline SVG */
  color: var(--gold-primary);
  grid-column: 1;
  justify-self: start;
}

.header-logo svg {
  height: 28px;
  width: auto;
}

/* Desktop nav — column 2, centered */
.header-nav {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  grid-column: 2;
  justify-self: center;
}

.nav-link {
  font-family: var(--font-sans);
  font-size: var(--font-size-nav-link);
  font-weight: 400;
  letter-spacing: 0.01em;
  color: var(--nav-link);
  transition: color var(--transition-fast);
}

.nav-link:hover {
  color: var(--nav-link-hover);
}

/* Hamburger (hidden on desktop) — column 3, right-aligned */
.hamburger {
  grid-column: 3;
  justify-self: end;
  display: none; /* shown on mobile via media query */
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 32px;
  height: 32px;
  padding: 4px;
  cursor: pointer;
}

.hamburger-line {
  display: block;
  width: 20px;
  height: 1.5px;
  background: var(--text-secondary);
  border-radius: 2px;
  transition: transform var(--transition-base), opacity var(--transition-base);
  transform-origin: center;
}

/* Hamburger open state */
.hamburger[aria-expanded="true"] .hamburger-line:nth-child(1) {
  transform: translateY(6.5px) rotate(45deg);
}
.hamburger[aria-expanded="true"] .hamburger-line:nth-child(2) {
  opacity: 0;
}
.hamburger[aria-expanded="true"] .hamburger-line:nth-child(3) {
  transform: translateY(-6.5px) rotate(-45deg);
}


/* ------------------------------------------------------------
   4. Mobile menu
------------------------------------------------------------ */

.mobile-menu {
  position: fixed;
  top: calc(16px + var(--header-height) + 8px);
  left: 24px;
  right: 24px;
  background: rgba(24, 20, 16, 0.95);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  padding: var(--space-3);
  z-index: 99;
  display: none;
  flex-direction: column;
  gap: 4px;

  /* Entrance animation */
  opacity: 0;
  transform: translateY(-8px);
  transition:
    opacity var(--transition-base),
    transform var(--transition-base);
}

.mobile-menu.is-open {
  display: flex;
  opacity: 1;
  transform: translateY(0);
}

.mobile-nav-link {
  font-family: var(--font-sans);
  font-size: var(--font-size-body-medium);
  font-weight: 400;
  color: var(--nav-link);
  padding: 12px var(--space-2);
  border-radius: var(--radius-xs);
  transition: background var(--transition-fast), color var(--transition-fast);
}

.mobile-nav-link:hover {
  color: var(--nav-link-hover);
  background: rgba(201, 168, 76, 0.06);
}


/* ------------------------------------------------------------
   5. Hero Section
------------------------------------------------------------ */

/* Landing page — scrollable, not clipped */
#screen-home {
  background: var(--bg-base);
}

/* Hero zone — full-viewport, flex-centered content */
.hero {
  position: relative;
  height: 100vh;
  height: 100dvh;
  /* No overflow:hidden — .gallery already clips columns internally.
     Removing the clip prevents the hard cut at the hero bottom edge. */
  display: flex;
  align-items: center;
  justify-content: center;
}

/*
  Blend zone: visually dissolves the hero bottom into the next section.
  Sits below .hero with a negative margin that overlaps the hero boundary.
  The gradient from transparent → bg-base covers any rendering seam.
*/
.hero-blend {
  position: relative;
  z-index: 5;
  margin-top: -160px;
  height: 160px;
  background: linear-gradient(
    to bottom,
    transparent 0%,
    rgba(13, 11, 8, 0.5) 40%,
    var(--bg-base) 100%
  );
  pointer-events: none;
}


/* ------------------------------------------------------------
   6. Gallery — Background portrait grid
------------------------------------------------------------ */

.gallery {
  position: absolute;
  inset: 0;
  display: flex;
  justify-content: center;
  overflow: hidden;
  z-index: 0;
  /* Isolate from backdrop-filter elements above */
  isolation: isolate;
}

.gallery-grid {
  display: flex;
  gap: 24px;
  align-items: flex-start;
  width: 100%; /* Fill full viewport so edge columns always sit at screen edges */
}

/* Single column — distribute evenly across full width */
.gallery-col {
  flex: 1;
  min-width: 0;
  overflow: hidden;
}

/* Inner wrapper — animated */
.gallery-col-inner {
  display: flex;
  flex-direction: column;
  will-change: transform;
  animation: gallery-scroll var(--duration, 50s) linear var(--delay, 0s) infinite;
  /* Safari: prevent flickering during animation */
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}

/* Card set — two sets per column for seamless loop */
.gallery-set {
  display: flex;
  flex-direction: column;
  gap: 24px;
  /* No bottom gap between sets — handled by sibling gap if needed */
  padding-bottom: 24px; /* matches the gap so sets tile perfectly */
}

/* Individual portrait card — fills column width, keeps portrait proportions */
.gallery-card {
  width: 100%;
  aspect-ratio: 4 / 5;
  border-radius: var(--radius-xs);
  overflow: hidden;
  flex-shrink: 0;
}

.gallery-card img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: brightness(0.38) contrast(0.85) saturate(0.65);
  transition: filter 0.3s ease;
}

/* Scroll animation */
@keyframes gallery-scroll {
  from { transform: translateY(0); }
  to   { transform: translateY(-50%); }
}

/* Respect reduced motion preference */
@media (prefers-reduced-motion: reduce) {
  .gallery-col-inner {
    animation: none;
    will-change: auto;
  }
}


/* ------------------------------------------------------------
   7. Gallery Overlays
------------------------------------------------------------ */

.gallery-overlay-fade,
.gallery-overlay-veil {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
}

/* 4-directional fade to bg color.
   Bottom fade starts earlier (55%) to dissolve cards well before the clip edge. */
.gallery-overlay-fade {
  background:
    linear-gradient(
      to bottom,
      var(--bg-base) 0%,
      rgba(13, 11, 8, 0.92) 6%,
      rgba(13, 11, 8, 0.65) 16%,
      transparent 30%,
      transparent 55%,
      rgba(13, 11, 8, 0.50) 70%,
      rgba(13, 11, 8, 0.80) 82%,
      rgba(13, 11, 8, 0.95) 92%,
      var(--bg-base) 100%
    ),
    linear-gradient(
      to right,
      var(--bg-base) 0%,
      transparent 13%,
      transparent 87%,
      var(--bg-base) 100%
    );
}

/* Radial center veil — dims the area behind the content */
.gallery-overlay-veil {
  z-index: 2;
  background: radial-gradient(
    ellipse 55% 65% at 50% 50%,
    rgba(13, 11, 8, 0.84) 0%,
    rgba(13, 11, 8, 0.58) 42%,
    transparent 72%
  );
}


/* ------------------------------------------------------------
   8. Landing Logo
------------------------------------------------------------ */

.landing-logo {
  position: fixed;
  top: 40px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 100;
  display: flex;
  align-items: center;
  line-height: 0;
}


/* ------------------------------------------------------------
   9. Hero Content
------------------------------------------------------------ */

.hero-content {
  position: relative;
  z-index: 10;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  /* Symmetric top/bottom padding so the flex-centered content sits at the
     true optical center (was 48/80, which biased it ~16px upward). */
  padding: 48px clamp(20px, 5vw, 80px);
  /* Fluid vertical rhythm between the title block and the upload group.
     Floor 40px == the previous fixed value, so short viewports (13" laptops,
     phones) never get a larger gap than before — the hero can't stop fitting
     on one screen. Only tall viewports (which have spare vertical budget) open
     it up toward 56px for more breathing room. Mobile keeps its own fixed
     32px via the max-width:768px override below. */
  gap: clamp(40px, 5vh, 56px);
  width: 100%;
}

/* Title block */
.hero-text {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  max-width: 720px;
}

.hero-title {
  font-family: var(--font-serif);
  font-size: var(--font-size-h1);
  font-weight: var(--font-weight-serif-bold);
  line-height: var(--line-height-h1);
  letter-spacing: var(--letter-spacing-h1);
  color: var(--text-primary);
}

.hero-subtitle {
  font-family: var(--font-sans);
  font-size: var(--font-size-lead);
  font-weight: 400;
  line-height: 1.6;
  color: var(--text-secondary);
  max-width: 600px;
  margin: 0 auto;
  /* Balance the two-line wrap so lines are roughly even and no lone word
     is orphaned on the last line. Modern browsers only (Chrome 114+, Safari
     17.5+, Firefox 121+); older browsers ignore it and wrap as before. The
     &nbsp; on "and gift" in the markup is the belt-and-suspenders fallback. */
  text-wrap: balance;
}

/* Upload group — section label + upload area + button + hint */
.hero-upload-group {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  width: 384px;
}

.section-label {
  font-family: var(--font-sans);
  font-size: var(--font-size-section-label);
  font-weight: 500;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin-bottom: 4px;
}

/* Price badge — quiet expectation-setter under the section label.
   Preview free, finished portrait $24.99. Reuses the glass settings of the
   header "Your Portraits" button (.res-action-btn) — translucent white fill,
   blur, neutral hairline border — but at label scale (our copy, 11px, no
   icon) with a full pill radius, so it stays quieter than the primary CTA.
   Flex child of .hero-upload-group; inherits its fade-in via .anim-hero-cta
   — no separate animation. */
.price-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0 auto 4px;
  padding: 5px 12px;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-pill);
  /* translucent fill + blur mirror .res-action-btn; the design system does
     not tokenize this glass value, so we match the button's literal rgba */
  background: rgba(255, 255, 255, 0.05);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  font-family: var(--font-sans);
  font-size: var(--font-size-badge);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-secondary);
  white-space: nowrap;
}

.price-badge-free   { color: var(--gold-light); }
.price-badge-sep    { color: var(--text-tertiary); }
.price-badge-amount { color: var(--text-secondary); }


/* ------------------------------------------------------------
   9. Upload Area
------------------------------------------------------------ */

.upload-area {
  position: relative;
  width: 100%;
  min-height: 180px;
  border: 1.5px dashed var(--gold-muted);
  border-radius: var(--radius-sm);
  /*
    Semi-transparent dark panel — separates the upload block from the
    gallery behind without blur. Simple, stable, no compositing issues.
  */
  background: rgba(13, 11, 8, 0.62);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition:
    border-color var(--transition-base),
    background var(--transition-base);
  overflow: hidden;
}

.upload-area:hover {
  border-color: var(--gold-border);
  background: rgba(13, 11, 8, 0.74);
}

.upload-area:focus-within {
  outline: none;
  border-color: var(--gold-border);
}

/* Hidden file input — full area clickable */
#file-input {
  position: absolute;
  inset: 0;
  opacity: 0;
  cursor: pointer;
  width: 100%;
  height: 100%;
  z-index: 2;
}

/* Drag-over state */
.upload-area.is-dragging {
  border-color: var(--gold-primary);
  background: rgba(201, 168, 76, 0.04);
}

/* Default state — icon + labels */
.upload-default {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: var(--space-4) var(--space-3);
  pointer-events: none;
  user-select: none;
}

.upload-icon-btn {
  position: relative; /* contains absolutely-positioned thumb */
  width: 60px;
  height: 60px;
  border-radius: 10px;
  border: 1px solid var(--border-default);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-tertiary);
  transition:
    border-color var(--transition-base),
    color var(--transition-base);
}

.upload-area:hover .upload-icon-btn {
  border-color: var(--border-medium);
  color: var(--text-secondary);
}

.upload-labels {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

.upload-title {
  font-family: var(--font-sans);
  font-size: var(--font-size-body-large);
  font-weight: 400;
  color: var(--text-primary);
  line-height: 1.4;
}

.upload-hint {
  font-family: var(--font-sans);
  font-size: var(--font-size-caption);
  color: var(--text-tertiary);
  line-height: 1.4;
}

/* ── State visibility ────────────────────────────────────── */
/*
  Default (no class): plus visible, spinner + thumb hidden.
  .is-loading:          spinner visible, plus + thumb hidden.
  .has-preview:         thumb visible, plus + spinner hidden.
*/

.upload-plus    { display: flex; }
.upload-spinner { display: none; }
.upload-thumb   { display: none; }

.upload-area.is-loading .upload-plus    { display: none; }
.upload-area.is-loading .upload-spinner { display: flex; }
.upload-area.is-loading                 { cursor: default; }

.upload-area.has-preview .upload-plus   { display: none; }
.upload-area.has-preview .upload-thumb  { display: block; }
.upload-area.has-preview                { cursor: default; }

/* Block file-input clicks while loading or in preview */
.upload-area.is-loading #file-input,
.upload-area.has-preview #file-input   { pointer-events: none; }

/* No hover border-change on area while in preview — hover lives on thumb */
.upload-area.has-preview:hover {
  border-color: var(--gold-muted);
  background: rgba(13, 11, 8, 0.62);
}

/* ── Loading spinner ─────────────────────────────────────── */

@keyframes upload-spin {
  to { transform: rotate(360deg); }
}

.upload-spinner {
  transform-origin: center;
  animation: upload-spin 900ms linear infinite;
}

/* ── Thumbnail — 2 px inset, square, cropped center ─────── */

.upload-thumb {
  position: absolute;
  inset: 2px;             /* 2 px gap between icon-btn border and image */
  border-radius: 8px;     /* slightly less than parent 10px */
  overflow: hidden;
}

.upload-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
}

/* ── Reveal animation — simple opacity fade-in the instant the preview
   decode finishes. Works the same whether decode is instant (the vast
   majority of cases — local FileReader read, no network involved) or
   has a brief delay on a slow device: never reads as a stuck/buggy
   state the way a wipe or spinner could. ── */
@keyframes upload-reveal {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.upload-area.preview-revealing .upload-thumb img,
.upload-area.preview-revealing .upload-thumb-generic {
  animation: upload-reveal 180ms ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .upload-area.preview-revealing .upload-thumb img,
  .upload-area.preview-revealing .upload-thumb-generic {
    animation: none;
  }
}

/* ── Generic preview fallback (e.g. HEIC the browser can't decode inline) ── */
/* Hidden by default; .upload-area.has-preview-generic swaps visibility with
   #preview-img below — only one of the two is ever shown at a time. */
.upload-thumb-generic {
  display: none;
  width: 100%;
  height: 100%;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  background: var(--bg-surface, rgba(255, 255, 255, 0.04));
  color: var(--text-tertiary);
  text-align: center;
}

.upload-thumb-generic span {
  font-family: var(--font-sans);
  font-size: 9px;
  line-height: 1.3;
  padding: 0 4px;
}

.upload-area.has-preview-generic .upload-thumb img {
  display: none;
}

.upload-area.has-preview-generic .upload-thumb-generic {
  display: flex;
}

/* ── Remove overlay ──────────────────────────────────────── */
/*
  Desktop: overlay appears on hover.
  Mobile (@media hover:none): always visible at softer opacity —
  permanent affordance since touch has no hover state.
*/

.upload-remove-overlay {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.40);
  border-radius: inherit;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity var(--transition-base);
}

.upload-area.has-preview .upload-icon-btn:hover .upload-remove-overlay {
  opacity: 1;
}

/* Remove button — covers full overlay, icon centered */
.upload-remove-btn {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--text-primary);  /* #F5EDD6, not pure white */
  pointer-events: auto;
}

/* Mobile: always-visible overlay (softer opacity) */
@media (hover: none) {
  .upload-area.has-preview .upload-remove-overlay {
    opacity: 1;
    background: rgba(0, 0, 0, 0.30);
  }
}


/* ------------------------------------------------------------
   10. Buttons
------------------------------------------------------------ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-sans);
  font-size: var(--font-size-button);
  font-weight: 500;
  letter-spacing: 0.02em;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition:
    background var(--transition-fast),
    color var(--transition-fast),
    border-color var(--transition-fast);
  white-space: nowrap;
  border: none;
  outline: none;
}

/* Primary */
.btn-primary {
  width: 100%;
  height: 60px;
  background: var(--button-primary-bg);
  color: var(--button-primary-text);
}

.btn-primary:hover {
  background: var(--button-primary-hover);
}

.btn-primary:active {
  transform: scale(0.99);
}

/* Disabled state */
.btn-primary:disabled,
.btn-primary.is-disabled {
  background: var(--button-disabled-bg);
  color: var(--button-disabled-text);
  cursor: not-allowed;
  pointer-events: none;
}

/* Disabled-state spinner: replaces the label text while busy (file
   analysis / generation in flight) instead of leaving static grayed-out
   text, so the disabled state visibly communicates "something is
   happening". Hidden by default; swapped in only when the button is
   actually disabled. */
.btn-primary .btn-spinner {
  display: none;
  width: 18px;
  height: 18px;
  border: 2px solid transparent;
  border-top: 2px solid currentColor;
  border-radius: 50%;
  animation: btn-spinner-spin 0.7s linear infinite;
}

.btn-primary:disabled .btn-label,
.btn-primary.is-disabled .btn-label {
  display: none;
}

.btn-primary:disabled .btn-spinner,
.btn-primary.is-disabled .btn-spinner {
  display: inline-block;
}

@keyframes btn-spinner-spin {
  to { transform: rotate(360deg); }
}

/* Secondary */
.btn-secondary {
  height: 60px;
  padding: 0 var(--space-4);
  background: transparent;
  color: var(--button-secondary-text);
  border: 1px solid var(--button-secondary-border);
}

.btn-secondary:hover {
  color: var(--button-secondary-hover);
  border-color: var(--button-secondary-hover);
}

/* Ghost */
.btn-ghost {
  height: 44px;
  padding: 0 20px;
  background: transparent;
  color: var(--button-ghost-text);
  border: 1px solid var(--button-ghost-border);
  border-radius: var(--radius-xs);
}

.btn-ghost:hover {
  color: var(--button-ghost-hover-text);
  border-color: var(--button-ghost-hover-border);
}


/* ------------------------------------------------------------
   11. Micro text
------------------------------------------------------------ */

.hero-micro {
  font-family: var(--font-sans);
  font-size: var(--font-size-micro);
  font-weight: 400;
  color: var(--text-tertiary);
  line-height: 1.4;
}


/* ------------------------------------------------------------
   12. Responsive — Tablet (max 1024px)
------------------------------------------------------------ */

@media (max-width: 1024px) {
  .gallery-col:nth-child(5),
  .gallery-col:nth-child(6) {
    display: none;
  }

  .gallery-grid {
    gap: 20px;
  }
}


/* ------------------------------------------------------------
   13. Responsive — Mobile (max 768px)
------------------------------------------------------------ */

@media (max-width: 768px) {

  /* Header */
  .header {
    width: calc(100% - 32px);
    padding: 0 var(--space-2);
  }

  .header-nav {
    display: none;
  }

  .hamburger {
    display: flex;
  }

  /* Gallery: show 3 columns — cols 4–6 hidden */
  .gallery-col:nth-child(4),
  .gallery-col:nth-child(5),
  .gallery-col:nth-child(6) {
    display: none;
  }

  .gallery-grid {
    gap: 12px;
  }

  .gallery-set {
    gap: 12px;
    padding-bottom: 12px;
  }

  /* Hero height on mobile = dvh (dynamic viewport height) so the hero
     always fills the actually-visible area in ANY Safari toolbar state,
     and the flex-centered content stays optically centered (equal space
     above and below). Previously 100svh, which only matched the toolbar-
     EXPANDED height — once the toolbar collapsed to the pill state, the
     shorter svh box left a large empty strip below the content (reported
     on iPhone 16 Pro Max). dvh tracks the live height and removes that.
     Fit is never worse than svh: expanded state → dvh == svh (same as
     before); collapsed state → dvh is taller (more room). Older iOS Safari
     mis-measured dvh on first paint; iOS 17+/current devices are reliable.
     ROLLBACK: set height back to 100svh here. */
  .hero {
    height: 100dvh;
  }

  /* Hero content — symmetric vertical padding so the flex centering reads
     as a true optical center (was 40/60, which biased content ~10px up). */
  .hero-content {
    padding: 40px 20px;
    gap: var(--space-4);
  }

  .hero-upload-group {
    width: 100%;
  }

  .hero-subtitle {
    font-size: 17px;
  }

  /* Upload area: taller on mobile for easy tapping */
  .upload-area {
    min-height: 160px;
  }

  /* Button: full width already via parent */
}


/* ------------------------------------------------------------
   14. Responsive — Small mobile (max 390px)
------------------------------------------------------------ */

@media (max-width: 390px) {
  .hero-content {
    padding-left: 16px;
    padding-right: 16px;
  }

  /* On the narrowest phones let the badge wrap to two centered lines
     instead of pressing against the edges. Only permits a break — where
     it still fits on one line (most phones) nothing changes. */
  .price-badge {
    flex-wrap: wrap;
    justify-content: center;
    row-gap: 2px;
  }

  .gallery-grid {
    gap: 8px;
  }

  .gallery-set {
    gap: 8px;
    padding-bottom: 8px;
  }
}


/* ------------------------------------------------------------
   14b. Responsive — Compact-HEIGHT phones (logo clearance)

   The .landing-logo is position:fixed at top:40px (occupies y 40–80),
   so it does NOT take flow space — it floats over the top gap left by
   the .hero's vertical flex-centering. That gap = (visible height -
   content) / 2, so on TALL phones (16 Pro Max, 15 Pro Max) it is large
   and the logo sits comfortably above the headline. On shorter phones
   (16 Pro, 15 Pro, 12/13/14 Pro, 12 mini, SE) the gap shrinks below the
   logo and the fixed wordmark collides with the title.

   Fix (two levers, kept balanced — NOT top-aligned, so tall states
   don't go bottom-empty):
     1. padding-top on .hero reserves a floor of clearance under the
        logo while KEEPING align-items:center — the centered content is
        pushed down just enough to clear the wordmark; the spare room
        (which was symmetric anyway) is absorbed from the bottom.
     2. a gentle trim of the vertical rhythm + upload height shrinks the
        tall content block so it still fits with that clearance on the
        small on-load Safari viewport (toolbar expanded). Font sizes and
        proportions are untouched — only spacing.

   Tall phones are excluded by max-height:900 and keep the current
   centering byte-for-byte — the etalon (16 Pro Max) does not move.
   The 900px threshold splits 16 Pro (874, fixed) from 15 Pro Max
   (932, untouched). ROLLBACK: delete this block and 14c below.
------------------------------------------------------------ */

@media (max-width: 768px) and (max-height: 900px) {
  /* Reserve on .hero-content (an in-flow flex item), NOT on .hero: the
     .gallery background is position:absolute inset:0 against .hero's
     padding box, so padding on .hero would shove the whole portrait grid
     down and open a blank strip under the logo. Padding on .hero-content
     gives the identical centering math without touching the background. */
  .hero-content {
    padding-top: 92px;
    gap: var(--space-3);          /* 24px, was 32px */
  }

  .hero-upload-group {
    gap: 10px;                    /* was 16px */
  }

  .upload-area {
    min-height: 128px;           /* was 160px */
  }
}


/* ------------------------------------------------------------
   14c. Responsive — Very short phones (iPhone SE and smaller)

   On the shortest on-load viewports the content is taller than the
   space below the reserved logo band. Per the design decision, we keep
   everything on the first screen (no scroll) by trimming the rhythm a
   little further and lowering the reserved band — proportions/font
   sizes stay untouched. Comes AFTER 14b so these values win on SE.
------------------------------------------------------------ */

@media (max-width: 768px) and (max-height: 700px) {
  .hero-content {
    padding-top: 76px;
    gap: var(--space-2);          /* 16px */
  }

  .hero-upload-group {
    gap: var(--space-1);          /* 8px */
  }

  .upload-area {
    min-height: 108px;           /* was 160px */
  }
}


/* ============================================================
   15. Atmospheric background
============================================================ */

/*
  Pure CSS radial gradients on a fixed layer.
  Renders once, GPU-composited, zero JS, zero CWV impact.
  Fixed positioning creates natural depth as content scrolls "through" it.
*/
.bg-atmosphere {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    /* Warm amber bloom — bottom-right anchor */
    radial-gradient(ellipse 75% 55% at 82% 88%,
      rgba(61, 32, 16, 0.32) 0%,
      transparent 65%),
    /* Subtle gold whisper — top-left */
    radial-gradient(ellipse 55% 40% at 12% 12%,
      rgba(201, 168, 76, 0.045) 0%,
      transparent 60%),
    /* Deep warm center glow */
    radial-gradient(ellipse 70% 35% at 50% 105%,
      rgba(61, 32, 16, 0.22) 0%,
      transparent 60%);
}

/* Disabled on mobile: this gradient's positions/sizes are percentage-
   based, calculated relative to the element's own box. As position:
   fixed, that box is one viewport tall, so the gradient sits correctly
   near the bottom of the screen. Tried position:absolute to dodge a
   separate overscroll bug (see memory), but absolute stretches the box
   to the full page height on this long landing page, which relocates
   the gradient's "hot spots" thousands of pixels down — effectively
   invisible. Properly fixing this needs the gradient redefined with
   viewport-relative units instead of percentages; deprioritized for
   now. Desktop is unaffected and keeps the original fixed version. */
@media (max-width: 768px) {
  .bg-atmosphere {
    display: none;
  }
}


/* ============================================================
   16. Fixed top gradient
============================================================ */

/*
  Stays fixed above all scrolling content.
  Creates a visual boundary that "holds" the logo area.
  z-index between bg-atmosphere (–1) and landing-logo (100).
*/
.landing-top-gradient {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 180px;
  background: linear-gradient(
    to bottom,
    var(--bg-base) 0%,
    rgba(13, 11, 8, 0.88) 28%,
    rgba(13, 11, 8, 0.45) 60%,
    transparent 100%
  );
  z-index: 50;
  pointer-events: none;
}


/* ============================================================
   17. Landing sections — shared layout
============================================================ */

/*
  Landing section base: min full-viewport height, flex column.
  Content group is vertically centered — header + reel act as a single unit.
  padding provides minimum breathing room on very short screens.
*/
.landing-section {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 64px 0;
}

.section-container {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 clamp(20px, 5vw, 80px);
}


/* ============================================================
   18. Section: The Portraits
============================================================ */

/* Section header */
.portraits-header {
  text-align: center;
  margin-bottom: var(--space-8); /* 64px */
}

.portraits-title {
  font-family: var(--font-serif);
  font-size: var(--font-size-h2);
  font-weight: var(--font-weight-serif-semibold);
  color: var(--text-primary);
  line-height: var(--line-height-h2);
  letter-spacing: var(--letter-spacing-h2);
  margin-bottom: var(--space-2);
}

.portraits-subtitle {
  font-family: var(--font-sans);
  font-size: 20px;
  font-weight: 400;
  color: var(--text-secondary);
  line-height: 1.6;
  text-wrap: balance;
}

/* Reel wrapper — clips the overflowing track, positions edge fades */
.portraits-reel-wrapper {
  position: relative;
  overflow: hidden;
  cursor: grab;
}

.portraits-reel-wrapper.is-dragging {
  cursor: grabbing;
}

/* The scrolling track — two identical sets side by side */
/* Position driven by JS RAF loop — no CSS animation */
.portraits-reel {
  display: flex;
  gap: 0;
  will-change: transform;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}

/* One set of 15 cards */
.portraits-set {
  display: flex;
  gap: 16px;
  padding-right: 16px; /* preserves gap at the seamless loop point */
}

/* Individual portrait card — 4:5 ratio, ~4 cards visible on 1440px viewport */
.portrait-card {
  width: 400px;
  height: 500px;
  border-radius: var(--radius-md);
  overflow: hidden;
  flex-shrink: 0;
  background: var(--bg-elevated);
}

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

/* prefers-reduced-motion — JS reads this flag and sets SPEED = 0 */
@media (prefers-reduced-motion: reduce) {
  .portraits-reel {
    will-change: auto;
  }
}

/* Edge fades — left and right dissolve into background */
.portraits-fade {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 100px;
  pointer-events: none;
  z-index: 2;
}

.portraits-fade--left {
  left: 0;
  background: linear-gradient(to right, var(--bg-base) 0%, transparent 100%);
}

.portraits-fade--right {
  right: 0;
  background: linear-gradient(to left, var(--bg-base) 0%, transparent 100%);
}



/* ============================================================
   19. Portraits section — Responsive
============================================================ */

@media (max-width: 768px) {
  /* Cards: 4:5 ratio, ~2.5 visible on mobile viewport */
  .portrait-card {
    width: 260px;
    height: 325px;
  }

  .landing-section {
    padding: 48px 0;
  }

  .portraits-fade {
    width: 60px;
  }

  .portraits-header {
    margin-bottom: var(--space-6); /* 48px on mobile */
  }
}

@media (max-width: 390px) {
  .portrait-card {
    width: 210px;
    height: 262px;
  }

  .portraits-set {
    gap: 12px;
    padding-right: 12px;
  }

  .portraits-fade {
    width: 48px;
  }
}


/* ============================================================
   20. How It Works Section
============================================================ */

/* Anchor for .hiw-glow — section has no overflow:hidden, so the
   glow is free to bleed into the sections above/below */
.landing-section#section-how-it-works {
  position: relative;
}

/* Ambient warm glow — soft, off-center, barely-there atmosphere.
   Rotated rectangle + radial-gradient + blur for an organic, non-
   banal shape rather than a plain circular gradient ring. Painted
   first in the DOM so it sits behind the section content without
   needing z-index (which could fight neighboring sections' own
   stacking contexts). */
.hiw-glow {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 1700px;
  height: 950px;
  transform: translate(-50%, -50%) rotate(-20deg);
  background: radial-gradient(
    ellipse at center,
    rgba(201, 168, 76, 0.14) 0%,
    rgba(201, 168, 76, 0.11) 20%,
    rgba(201, 168, 76, 0.07) 40%,
    rgba(201, 168, 76, 0.04) 55%,
    rgba(201, 168, 76, 0.02) 70%,
    rgba(201, 168, 76, 0.006) 85%,
    transparent 100%
  );
  filter: blur(120px);
  pointer-events: none;
}

/* Disabled on mobile: causes the glow to bleed into the iOS/Chrome
   overscroll (rubber-band) area below the page content — unresolved,
   see memory. Desktop is unaffected and keeps the full effect. */
@media (max-width: 768px) {
  .hiw-glow {
    display: none;
  }
}

/* Section header — centered H2 + subtitle */
.hiw-header {
  text-align: center;
  margin-bottom: var(--space-8); /* 64px */
}

.hiw-title {
  font-family: var(--font-serif);
  font-size: var(--font-size-h2);
  font-weight: var(--font-weight-serif-semibold);
  color: var(--text-primary);
  line-height: var(--line-height-h2);
  letter-spacing: var(--letter-spacing-h2);
  margin: 0 0 var(--space-2);
}

.hiw-subtitle {
  font-family: var(--font-sans);
  font-size: 20px;
  font-weight: 400;
  color: var(--text-secondary);
  line-height: 1.6;
  margin: 0;
}

/* Two-column grid
   Left: 320px fixed preview | gap | Right: 1fr steps
   max-width caps layout on very wide screens */
.hiw-layout {
  display: grid;
  grid-template-columns: 320px 1fr;
  align-items: center;
  gap: clamp(28px, 4.2vw, 56px); /* −30% от исходных clamp(40px,6vw,80px) */
  max-width: 960px;
  margin: 0 auto;
}

/* Preview block — vertical 9:16 ratio */
.hiw-preview {
  position: relative;
  width: 100%;
  aspect-ratio: 9 / 16;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--bg-elevated);
}

/* Shared styles for placeholder img and future <video> */
.hiw-preview-img,
.hiw-preview-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Steps list */
.hiw-steps {
  display: flex;
  flex-direction: column;
  gap: var(--space-5); /* 40px between steps */
}

/* Individual step: number + content side by side */
.hiw-step {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2); /* 16px */
}

/* Step number — H4 style, gold */
.hiw-step-num {
  font-family: var(--font-serif);
  font-size: var(--font-size-h4);
  font-weight: var(--font-weight-serif-regular);
  color: var(--gold-primary);
  line-height: var(--line-height-h4);
  flex-shrink: 0;
}

/* Step title + description stacked */
.hiw-step-body {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Step title — H4 style, primary text */
.hiw-step-title {
  font-family: var(--font-serif);
  font-size: var(--font-size-h4);
  font-weight: var(--font-weight-serif-semibold);
  color: var(--text-primary);
  line-height: var(--line-height-h4);
  letter-spacing: var(--letter-spacing-h4);
  margin: 0;
}

/* Step description — body text, secondary color */
.hiw-step-desc {
  font-family: var(--font-sans);
  font-size: var(--font-size-body-large); /* 17px */
  font-weight: 400;
  color: var(--text-secondary);
  line-height: 1.6;
  margin: 0;
}

/* ── How It Works — Responsive ───────────────────────────── */

@media (max-width: 768px) {
  /* Stack to single column: header → subtitle → preview → steps */
  .hiw-layout {
    grid-template-columns: 1fr;
    gap: var(--space-5); /* 40px */
  }

  /* Center and constrain preview + steps to same width — unified column */
  .hiw-preview,
  .hiw-steps {
    max-width: 340px;
    margin: 0 auto;
    width: 100%;
  }

  .hiw-header {
    margin-bottom: var(--space-6); /* 48px */
  }

  /* This section's content nearly fills the viewport (min-height:100vh
     flex-centering), so unlike shorter sections it gets no bonus
     centering gap before the next section — compensate so the visual
     gap to "Why The Pawlure" matches the other section boundaries.
     Live-measured on dev: gap was 243px here vs 321px/416px on the
     neighboring boundaries — the earlier +24px compensation undershot.
     150px targets the smaller of the two (321px); recalibrate if it
     over/undershoots on an actual phone. */
  .landing-section#section-how-it-works {
    padding-bottom: 150px; /* 48px base + 102px compensation */
  }
}


/* ============================================================
   21. Why The Pawlure Section
============================================================ */

.why-header {
  text-align: center;
  margin-bottom: var(--space-8); /* 64px */
}

.why-title {
  font-family: var(--font-serif);
  font-size: var(--font-size-h2);
  font-weight: var(--font-weight-serif-semibold);
  color: var(--text-primary);
  line-height: var(--line-height-h2);
  letter-spacing: var(--letter-spacing-h2);
  margin: 0 0 var(--space-2);
}

.why-subtitle {
  font-family: var(--font-sans);
  font-size: 20px;
  font-weight: 400;
  color: var(--text-secondary);
  line-height: 1.6;
  margin: 0;
}

/* Three-column card grid */
.why-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-3); /* 24px */
}

/* Benefit card */
.why-card {
  background: var(--bg-secondary);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md); /* 16px */
  padding: 24px;
  display: flex;
  flex-direction: column;
  transition: border-color var(--transition-fast);
  min-height: 185px;
}

.why-card:hover {
  border-color: var(--border-medium);
}

/* Icon — 24×24, gold, 24px gap to text below */
.why-card-icon {
  width: 24px;
  height: 24px;
  flex-shrink: 0;
  color: var(--gold-primary);
  margin-bottom: 24px;
}

/* Card title — H4 */
.why-card-title {
  font-family: var(--font-serif);
  font-size: var(--font-size-h4);
  font-weight: var(--font-weight-serif-semibold);
  color: var(--text-primary);
  line-height: var(--line-height-h4);
  letter-spacing: var(--letter-spacing-h4);
  margin: 0 0 8px;
}

/* Card description — Body Medium 15px */
.why-card-desc {
  font-family: var(--font-sans);
  font-size: var(--font-size-body-medium); /* 15px */
  font-weight: 400;
  color: var(--text-secondary);
  line-height: 1.6;
  margin: 0;
}

/* ── Why — Responsive ────────────────────────────────────── */

@media (max-width: 768px) {
  /* Stack cards vertically on mobile */
  .why-grid {
    grid-template-columns: 1fr;
    gap: var(--space-2); /* 16px */
  }

  /* Remove min-height — on mobile cards are wide, text wraps less,
     min-height would create artificial empty space at the bottom */
  .why-card {
    min-height: 0;
  }

  .why-header {
    margin-bottom: var(--space-6); /* 48px */
  }
}


/* ============================================================
   22. Good to Know — FAQ Section
============================================================ */

.faq-header {
  text-align: center;
  margin-bottom: var(--space-8); /* 64px */
}

.faq-title {
  font-family: var(--font-serif);
  font-size: var(--font-size-h2);
  font-weight: var(--font-weight-serif-semibold);
  color: var(--text-primary);
  line-height: var(--line-height-h2);
  letter-spacing: var(--letter-spacing-h2);
  margin: 0 0 var(--space-2);
}

.faq-subtitle {
  font-family: var(--font-sans);
  font-size: 20px;
  font-weight: 400;
  color: var(--text-secondary);
  line-height: 1.6;
  margin: 0;
}

/* Vertical list — max-width for readability */
.faq-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2); /* 16px between cards */
  max-width: 760px;
  margin: 0 auto;
}

/* FAQ card — matches Why The Pawlure card style */
.faq-card {
  background: var(--bg-secondary);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  overflow: hidden;
  transition: border-color var(--transition-fast);
}

.faq-card:hover {
  border-color: var(--border-medium);
}

/* Open state — border stays highlighted */
.faq-card.is-open {
  border-color: var(--border-medium);
}

/* Question button — full-width clickable row */
.faq-question {
  appearance: none;
  background: none;
  border: none;
  width: 100%;
  text-align: left;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 24px;
  font-family: var(--font-sans);
  font-size: var(--font-size-body-large); /* 17px */
  font-weight: 400;
  color: var(--text-primary);
  line-height: 1.5;
}

.faq-question:focus-visible {
  outline: 2px solid var(--gold-primary);
  outline-offset: -2px;
  border-radius: var(--radius-md);
}

/* Plus icon — rotates 45° to become × when open */
.faq-icon {
  width: 24px;
  height: 24px;
  flex-shrink: 0;
  color: var(--text-secondary);
  transition: transform var(--transition-base), color var(--transition-fast);
}

.faq-card.is-open .faq-icon {
  transform: rotate(45deg);
  color: var(--text-primary);
}

/* Answer — animated with grid-template-rows trick (no JS height calc needed) */
.faq-answer {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 280ms var(--ease-out);
}

.faq-card.is-open .faq-answer {
  grid-template-rows: 1fr;
}

/* Inner wrapper needed for grid overflow:hidden trick */
.faq-answer-inner {
  overflow: hidden;
  padding: 0 24px 0 24px;
  transition: padding-bottom 280ms var(--ease-out);
}

.faq-card.is-open .faq-answer-inner {
  padding-bottom: 24px;
}

/* Answer text — Body Medium 15px */
.faq-answer-text {
  font-family: var(--font-sans);
  font-size: var(--font-size-body-medium); /* 15px */
  font-weight: 400;
  color: var(--text-secondary);
  line-height: 1.6;
  margin: 0;
  padding-top: 8px; /* 24px (question padding-bottom) + 8px = 32px total gap */
  border-top: 1px solid var(--border-subtle);
}

/* ── FAQ — Responsive ────────────────────────────────────── */

@media (max-width: 768px) {
  .faq-header {
    margin-bottom: var(--space-6); /* 48px */
  }
}


/* ============================================================
   23. Footer
============================================================ */

.landing-footer {
  /* Extra bottom padding equal to the cookie banner's real rendered
     height (already includes its floating bottom gap), while it's
     open, so the card never covers the footer nav links — see
     --cookie-banner-h (js/main.js, set via ResizeObserver). */
  padding: 48px 0 calc(32px + var(--cookie-banner-h, 0px));
  transition: padding-bottom var(--transition-base);
  text-align: center;
}

/* Nav links row */
.footer-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  row-gap: 8px;
}

.footer-link {
  font-family: var(--font-sans);
  font-size: var(--font-size-nav-link); /* 14px */
  font-weight: 400;
  color: var(--text-disabled);
  text-decoration: none;
  transition: color var(--transition-fast);
}

.footer-link:hover {
  color: var(--text-tertiary);
}

/* Separator dot between links */
.footer-sep {
  font-size: 14px;
  color: var(--text-disabled);
  margin: 0 12px;
  user-select: none;
}

/* Copyright line — same size and color as the nav links above it.
   --text-disabled is already the dimmest token in the palette, so no
   extra opacity is needed to keep this quiet; stacking one on top of
   the other just hurt legibility without adding hierarchy. */
.footer-copy {
  font-family: var(--font-sans);
  font-size: var(--font-size-nav-link); /* 14px, matches .footer-link */
  font-weight: 400;
  color: var(--text-disabled);
  line-height: 1;
  margin: 32px 0 0;
}


/* ============================================================
   24. Animations — Hero Load & Scroll Reveal
============================================================ */

/*
   Easing strategy — single curve for everything:
   cubic-bezier(0.22, 1, 0.36, 1)  EaseOutQuint
   Fast start, very long smooth tail. Nothing snaps. Nothing bounces.
   Premium, unhurried, deliberate.
*/

/* ── Keyframes ───────────────────────────────────────────── */

@keyframes hero-fade-up {
  from { opacity: 0; transform: translateY(24px); }
  to   { opacity: 1; transform: translateY(0);    }
}

/*
  Logo: preserves the centering translateX(-50%) at all times.
  Without this, Y animation would erase the X offset and shift the logo.
*/
@keyframes hero-logo-fade-up {
  from { opacity: 0; transform: translateX(-50%) translateY(24px); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0);    }
}

/*
  CTA block: simple opacity fade-in. No backdrop-filter on the upload
  area means no stacking-context conflict — parent opacity animation
  is safe to use directly.
*/
@keyframes hero-fade-only {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* ── Hero load sequence ──────────────────────────────────── */
/*
  Curtain-rise pacing: each element waits for the previous to land.
  Total sequence ~2.5 s. Unhurried, deliberate, premium.
*/

.anim-hero-title,
.anim-hero-subtitle {
  opacity: 0;
  animation-name: hero-fade-up;
  animation-fill-mode: both;
  animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
}

.anim-hero-cta {
  opacity: 0;
  animation-name: hero-fade-only;
  animation-fill-mode: both;
  animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
}

.anim-hero-logo {
  opacity: 0;
  animation-name: hero-logo-fade-up;
  animation-fill-mode: both;
  animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
}

.anim-hero-title    { animation-duration: 1200ms; animation-delay: 350ms;  }
.anim-hero-subtitle { animation-duration: 1100ms; animation-delay: 700ms;  }
.anim-hero-cta      { animation-duration: 1100ms; animation-delay: 1100ms; }
.anim-hero-logo     { animation-duration: 1500ms; animation-delay: 1400ms; }

/* «Your Portraits» button — appears after logo, simple fade */
.anim-your-portraits {
  opacity: 0;
  animation-name: hero-fade-only;
  animation-duration: 900ms;
  animation-delay: 1700ms;
  animation-fill-mode: both;
  animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
}

/* ── Scroll reveal — base ────────────────────────────────── */
/*
  Standard content blocks: titles, subtitles, benefit cards, FAQ cards.
  Y offset 30px. Duration 1150ms — a slow, confident glide.
*/

.anim-hidden {
  opacity: 0;
  transform: translateY(30px);
  transition:
    opacity      1150ms cubic-bezier(0.22, 1, 0.36, 1),
    transform    1150ms cubic-bezier(0.22, 1, 0.36, 1),
    border-color var(--transition-fast); /* preserves card hover */
}

.anim-hidden.anim-visible {
  opacity: 1;
  transform: translateY(0);
}

/* ── Stagger delays (via data-anim-delay attribute) ──────── */

[data-anim-delay="80"].anim-visible  { transition-delay:  80ms; }
[data-anim-delay="160"].anim-visible { transition-delay: 160ms; }
[data-anim-delay="200"].anim-visible { transition-delay: 200ms; }
[data-anim-delay="230"].anim-visible { transition-delay: 230ms; }
[data-anim-delay="250"].anim-visible { transition-delay: 250ms; }
[data-anim-delay="300"].anim-visible { transition-delay: 300ms; }
[data-anim-delay="380"].anim-visible { transition-delay: 380ms; }

/* ── Portraits reel — emergence from darkness ────────────── */
/*
  The reel fades in over 3600ms — a slow, luxurious emergence from darkness.
  No scale or Y movement: the RAF loop runs during reveal and any
  additional transform would interfere with the scroll position.
*/

.anim-reel-hidden {
  opacity: 0;
  transition: opacity 3600ms cubic-bezier(0.22, 1, 0.36, 1);
}

.anim-reel-hidden.anim-visible {
  opacity: 1;
}

/* ── Atmospheric emergence — HIW preview image/video ──────── */
/*
  Slower, more cinematic than standard reveals.
  Scale 1.02 → 1 adds the feeling of the image "focusing in"
  as it brightens. Duration 2400ms — cinematic and unhurried.
*/

.anim-emerge {
  opacity: 0;
  transform: scale(1.02);
  transition:
    opacity   2400ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 2400ms cubic-bezier(0.22, 1, 0.36, 1);
}

.anim-emerge.anim-visible {
  opacity: 1;
  transform: scale(1);
}

/* ── Ambient glow — slow, delayed emergence ──────────────── */
/*
  Fires from the same observer as .hiw-preview's .anim-emerge, but
  with a longer delay and a much slower fade — the glow should
  feel like it gradually warms up behind the preview block after
  it has already appeared, not arrive at the same time.
  Opacity-only: no transform, so it never fights the positioning/
  rotation transform already set on .hiw-glow itself.
*/

.anim-glow {
  opacity: 0;
  transition: opacity 3200ms cubic-bezier(0.22, 1, 0.36, 1);
  transition-delay: 1500ms;
}

.anim-glow.anim-visible {
  opacity: 1;
}

/* ── Footer — opacity only ───────────────────────────────── */

.anim-footer {
  opacity: 0;
  transition: opacity 1100ms cubic-bezier(0.22, 1, 0.36, 1);
}

.anim-footer.anim-visible {
  opacity: 1;
}

/* ============================================================
   Section 10. Screen — Loading / Generation
   ============================================================

   The loading screen is a position:fixed overlay that covers
   the hero at z-index 200. Opacity is controlled by JS for
   the entrance / exit transitions.
   ============================================================ */

#screen-loading {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: var(--bg-base);
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

/* JS removes [hidden] and sets opacity before revealing.
   Use display:flex only when the screen is active.        */
#screen-loading:not([hidden]) {
  display: flex;
}

/* Logo — pinned to the top-center of the overlay. Floats in with a
   short delay after the screen itself appears, same fade-up keyframe
   and easing as the home hero logo (hero-logo-fade-up), just retimed
   for this screen's much shorter reveal. */
.loading-logo {
  position: absolute;
  top: 32px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  text-decoration: none;
  flex-shrink: 0;
  user-select: none;
  opacity: 0;
  animation: hero-logo-fade-up 1500ms cubic-bezier(0.22, 1, 0.36, 1) 700ms both;
}

/* Central column: frame + status stacked */
.loading-center {
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Frame wrapper: canvas behind, SVG frame overlay on top */
.loading-frame-wrap {
  position: relative;
  width: 210px;
  height: 258px;
  flex-shrink: 0;
}

.loading-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* Transparent — frame SVG is outline only, background shows through */
}

.loading-frame-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  user-select: none;
}

/* Status text — outer container */
.loading-status {
  margin-top: 16px; /* var(--space-2) */
  font-family: var(--font-sans);
  font-size: var(--font-size-nav-link); /* 14px */
  font-weight: 400;
  color: var(--text-secondary); /* #C8BA9E */
  text-align: center;
  letter-spacing: 0.01em;
  white-space: nowrap;
}

/*
  Inner wrapper is inline-block — its width equals the text span only.
  text-align:center on the parent centers it over the frame.
  Dots are position:absolute and do not affect this width,
  so the text never shifts or jumps during animation.
*/
.loading-status-inner {
  position: relative;
  display: inline-block;
}

/* Dots — positioned outside the text's bounding box */
.loading-dots {
  position: absolute;
  left: 100%;
  top: 0;
  width: 1.2em;
  text-align: left;
}

/* Mobile: scale down frame proportionally (80%) */
@media (max-width: 767px) {
  .loading-logo {
    top: 24px;
  }

  .loading-frame-wrap {
    width: 168px;
    height: 207px;
  }
}


/* ============================================================
   Toast Notifications
   Single global pattern for every error message on the site —
   overlays the UI instead of inserting into the document flow,
   so showing/hiding it never shifts surrounding layout. Replaces
   the old per-component inline error text (.upload-error,
   .ps-cta-error), which used to push content down when shown.
   ============================================================ */

.toast-container {
  position: fixed;
  left: 50%;
  bottom: max(24px, env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: 1000;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
  pointer-events: none; /* container itself never blocks clicks */
  width: 100%;
  max-width: 420px;
  padding: 0 var(--space-2);
  box-sizing: border-box;
}

.toast {
  pointer-events: auto;
  width: 100%;
  background: var(--bg-elevated);
  border: 1px solid var(--border-medium);
  border-radius: var(--radius-sm);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
  padding: 14px 18px;
  font-family: var(--font-sans);
  font-size: var(--font-size-body-small);
  line-height: 1.5;
  color: var(--text-primary);
  text-align: center;

  opacity: 0;
  transform: translateY(12px);
  transition: opacity var(--transition-base), transform var(--transition-base);
}

.toast--visible {
  opacity: 1;
  transform: translateY(0);
}

.toast--error {
  border-color: var(--input-border-error);
}


/* ============================================================
   Screen 3 — Results
   ============================================================ */

/* ── «Your Portraits» button on the landing page header ───── */

.your-portraits-btn {
  position: fixed;
  top: 40px;
  /* Stays within 1280px content area: as viewport grows beyond 1280px,
     right offset grows with the container margin + 40px inner padding. */
  right: max(40px, calc((100vw - 1280px) / 2 + 40px));
  z-index: 100;
}

/* ── Screen container ────────────────────────────────────── */

#screen-results {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: var(--bg-base);
  overflow: hidden;
}

#screen-results:not([hidden]) {
  display: flex;
  flex-direction: column;
}

/* ── Header ─────────────────────────────────────────────── */

.res-header {
  flex-shrink: 0;
  background: linear-gradient(
    to bottom,
    var(--bg-base) 0%,
    rgba(13, 11, 8, 0.88) 28%,
    rgba(13, 11, 8, 0.45) 60%,
    transparent 100%
  );
}

.res-header-inner {
  max-width: 1280px;
  margin: 0 auto;
  /* padding-top: 40px matches landing-logo top: 40px exactly */
  padding: 40px 40px;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
}

/* Logo: full SVG (desktop) / «P» initial (mobile) */
.res-logo {
  display: flex;
  align-items: center;
  line-height: 0;
  flex-shrink: 0;
}

.res-logo-full { display: block; }

.res-logo-p {
  display: none;
  width: 40px;
  height: 40px;
}

/* Header right: counter + action button */
.res-header-right {
  display: flex;
  align-items: center;
}

.res-state-wrap:not([hidden]) {
  display: flex;
  align-items: center;
  gap: 16px;
}

.res-counter {
  font-family: var(--font-sans);
  font-size: var(--font-size-body-small);
  color: var(--counter-active);
  white-space: nowrap;
}

.res-counter--empty {
  color: var(--counter-empty);
}

/* Action buttons: Regenerate / Get More Previews / Your Portraits */
/* :not([hidden]) — иначе это правило перебивает атрибут hidden по специфичности
   (оба селектора 0,1,0, но это правило идёт позже в каскаде), и кнопка
   .your-portraits-btn (которая живёт вне #screen-results, поэтому не скрыта
   родительским контейнером) остаётся видимой с первой загрузки страницы,
   хотя в разметке у неё стоит hidden. См. похожий баг — feedback_ios_fixed_overlay_hidden. */
.res-action-btn:not([hidden]) {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 40px;
  padding: 0 12px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-default);
  background: rgba(255, 255, 255, 0.05);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: var(--font-size-nav-link);
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: border-color var(--transition-fast), background var(--transition-fast);
}

.res-action-btn:hover {
  border-color: var(--border-medium);
  background: rgba(255, 255, 255, 0.09);
}

.res-action-btn svg {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
}

/* Upsell variant: gold border + gold text */
.res-action-btn--upsell {
  border-color: var(--border-active-muted);
  color: var(--gold-primary);
}

.res-action-btn--upsell:hover {
  border-color: var(--gold-border);
  background: var(--gold-glow);
}

/* ── Body — одна центральная колонка (фейк-дор чат, 2026-07-27) ──
   Паттерн мобильного концепта на ВСЕХ брейкпоинтах (решение Ивана):
   вертикальный скролл-поток, блоки капятся на ~640px и центрируются.
   Прежний двухколоночный grid (портрет + .res-pcard) удалён вместе
   с .res-pcard и нижней панелью .res-purchase-bar. ─────────────── */

.res-body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  padding: 0 0 64px;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
}

/* Кап ширины колонки ~600–680px (утверждено Иваном): на десктопе
   колонка центрируется, на мобиле блоки занимают всю ширину. 680px
   (было 640) — правка структуры 2026-07-28: две карточки-опции по
   300px + gap 24px = 624px контента, при 640px переносились в столбик
   даже на десктопе (Иван указал на баг), 680px даёт им поместиться
   в один ряд с небольшим запасом. */
.cv-wrap {
  width: 100%;
  max-width: 680px;
  margin-left: auto;
  margin-right: auto;
}

/* ── Share Portrait (Чат 77) — кнопки сняты с экрана результата
   на время фейк-дор теста (решение Ивана, 2026-07-27); базовые
   стили .btn-share/.share-copied-tip сохранены для возврата фичи.
   Прозрачный фон, бордер --border-default, текст/иконка
   --text-secondary; hover/press → --border-medium + --text-primary
   (переходы уже в .btn: --transition-fast). */
.btn-share {
  position: relative; /* якорь для .share-copied-tip */
  gap: 10px;
  background: transparent;
  border: 1px solid var(--border-default);
  color: var(--text-secondary);
}

.btn-share:hover,
.btn-share:active {
  border-color: var(--border-medium);
  color: var(--text-primary);
}

.btn-share .btn-share-icon {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
}

/* Тост «Link copied» — в области кнопки (над ней), повисел →
   плавно исчез. Управляется классом .is-visible из main.js. */
.share-copied-tip {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 10px);
  transform: translateX(-50%) translateY(4px);
  background: var(--bg-elevated);
  border: 1px solid var(--border-medium);
  border-radius: var(--radius-sm);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
  padding: 8px 14px;
  font-family: var(--font-sans);
  font-size: var(--font-size-body-small);
  font-weight: 400;
  letter-spacing: normal;
  color: var(--text-primary);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--transition-base), transform var(--transition-base);
  z-index: 5;
}

.share-copied-tip.is-visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}


/* ============================================================
   Screen 3 — Results — DESKTOP  (≥ 1024px)
   Хедер — тот же «плавающий» градиент-паттерн, что и на мобиле
   (Иван 2026-07-27): после перехода на единую одноколоночную
   раскладку десктоп тоже стал длинным скроллящимся экраном, и без
   absolute-позиционирования градиент за хедером ничего не перекрывал
   (контент шёл в потоке НИЖЕ хедера, а не скроллился под ним — сам
   градиент технически был, но визуально никак не проявлялся).
   Высота зоны 240px = ~120px реального контента хедера
   (40 паддинг + 40 логотип + 40 паддинг) + тот же «хвост» затухания,
   что и в мобильной версии. Колонке нужен клиренс под этот
   плавающий хедер — 144px = 120px контента + 24px зазор (тот же
   принцип, что и в мобильном 88px = 64px + 24px). */

@media (min-width: 1024px) {
  .res-header {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 240px;
    background: linear-gradient(
      to bottom,
      var(--bg-base) 0%,
      rgba(13, 11, 8, 0.88) 28%,
      rgba(13, 11, 8, 0.45) 60%,
      transparent 100%
    );
    z-index: 10;
    pointer-events: none; /* gradient tail doesn't block scroll/tap */
  }

  .res-header-inner {
    /* Логотип уходит из flex-потока (см. .res-logo ниже) — правый блок
       (счётчик/кнопка) остаётся прижат к правому краю тем же способом,
       что и раньше через justify-content, теперь просто с одним
       flex-элементом. */
    justify-content: flex-end;
    pointer-events: auto; /* buttons remain clickable */
  }

  /* Лого 1:1 как на лендинге (.landing-logo) — по центру экрана,
     тот же размер/позиция (top:40px), а не слева в паре с правым
     блоком (Иван 2026-07-27). */
  .res-logo {
    position: absolute;
    top: 40px;
    left: 50%;
    transform: translateX(-50%);
  }
}

/* ============================================================
   Screen 3 — Results — MOBILE / TABLET  (≤ 1023px)
   ============================================================ */

@media (max-width: 1023px) {

  /* Header: floating gradient overlay (same principle as landing page) */
  .res-header {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 180px;
    background: linear-gradient(
      to bottom,
      var(--bg-base) 0%,
      rgba(13, 11, 8, 0.88) 28%,
      rgba(13, 11, 8, 0.45) 60%,
      transparent 100%
    );
    z-index: 10;
    pointer-events: none; /* gradient tail doesn't block scroll/tap */
  }

  .res-header-inner {
    padding: 0 20px;
    height: var(--header-height);
    align-items: center;
    pointer-events: auto; /* buttons remain clickable */
  }

  .res-logo-full { display: none; }
  .res-logo-p    { display: flex; }

  /* Hide icon inside action buttons on mobile */
  .res-action-btn svg { display: none; }
  .res-action-btn { padding: 0 12px; font-size: 13px; }

  /* Your Portraits button on landing page — icon + text at 601–1023px */
  .your-portraits-btn {
    top: 40px;
    right: 16px;
    font-size: 13px;
    padding: 0 12px;
  }
  /* Restore SVG for this button — overrides the global action-btn svg hide above */
  .your-portraits-btn svg { display: flex; }

}

/* ── «Your Portraits» — icon-only at ≤600px ──────────────── */

@media (max-width: 600px) {
  .your-portraits-btn span { display: none; }
  .your-portraits-btn {
    width: 40px;   /* match height: 40px → square */
    padding: 0;
    justify-content: center;
  }
}

/* ============================================================
   Screen 3 — Results — THE FRAMED CANVAS (фейк-дор тест, 2026-07-27)
   Новые ИЗОЛИРОВАННЫЕ классы cv-* — общие правила сайта не трогаются.
   Копирайт — из концепт-HTML; цены — статичный fallback, перезапись
   из GET /pricing в applyPricing() (js/main.js).
   ============================================================ */

.cv-wrap {
  /* 122px = клиренс под absolute-хедер на ≤1023 (был на .res-mobile-top,
     удалённом 2026-07-28 вместе со старым верхним блоком — .cv-wrap
     теперь первый элемент в .res-body и должен сам освобождать место
     под хедер, иначе заголовок рендерится под ним, см. desktop-override
     ниже для 150px). Настоящий нижний край лого (40×40 SVG, центрирован
     в 64px хедере) — 52px от верха + 70px зазор до заголовка (Иван,
     28.07.2026) = 122px (было по ошибке 134px — считал от высоты
     хедера целиком, а не от реального нижнего края лого).
     20px — тот же боковой паддинг, что и раньше. */
  padding: 122px 20px 0;
}

.cv-title {
  font-family: var(--font-serif);
  font-size: var(--font-size-h3);
  font-weight: var(--font-weight-serif-semibold);
  line-height: var(--line-height-h3);
  letter-spacing: var(--letter-spacing-h3);
  color: var(--text-primary);
  margin: 0;
  text-align: center;
}

.cv-sub {
  font-family: var(--font-sans);
  font-size: var(--font-size-body-medium);
  color: var(--text-secondary);
  margin: 8px 0 0;
  text-align: center;
}

/* Мокап рамы — этап 3 (фейк-дор, чат "mockup"). Размер/пропорция —
   точная копия верхнего превью портрета (.res-main-img): та же высота
   (400px на мобиле) и та же пропорция 4:5, вместо прежней вольной
   clamp-плашки на всю ширину карточки (Иван 2026-07-27). Высота (не
   max-height) нужна, потому что внутренний .cv-frame считает свои 100%
   от родителя, а не от aspect-ratio напрямую. */
/* Тёплое размытое пятно за рамой — по образцу .hiw-glow (см. раздел
   "How It Works" выше), но масштаб под сам мокап (~400/520px), а не
   во всю секцию: ellipse + blur, тот же акцентный золотой оттенок
   (--border-active), pointer-events:none, без z-index (первый в DOM
   перед .cv-frame). Позиционируется от #cv-mockup напрямую — версия
   через отдельную обёртку-колонку съезжала/обрезалась не по центру
   (% считались от ширины всей ~640px колонки, а не узкой рамки;
   Иван указал на баг). В отличие от .hiw-glow — не отключаем на
   мобилке: масштаб на порядок компактнее, экран результата — почти
   весь мобильный трафик (Иван, 2026-07-27). Если на живой странице
   всё же появится нахлёст в iOS/Chrome overscroll-зоне — см.
   комментарий у .hiw-glow, тот же паттерн отключения на mobile. */
.cv-mockup-glow {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 150%;
  height: 135%;
  transform: translate(-50%, -50%);
  /* farthest-side (не farthest-corner по умолчанию) — иначе прозрачность
     долетает до нуля только в углах блока, а по бокам (короткая ось) на
     краю ещё остаётся заметная альфа → резкий обрез именно слева/справа
     (Иван указал на живой странице). farthest-side гарантирует нулевую
     альфу ровно на каждой стороне независимо от пропорции блока. */
  background: radial-gradient(
    ellipse farthest-side at center,
    /* Ярче — Иван 28.07.2026: пик был 0.21, почти не читался на
       компактной раме (для сравнения — .hiw-glow на главной такой же
       яркости, но на холсте 1700×950 против ~380–500px здесь). Пики
       подняты ×~1.8, форма затухания и цвет не менялись.
       Приглушено на ~20% (2026-07-28, тот же чат, после переделки
       рамы под рецепт companionarchive) — форма/цвет те же, только
       альфа ×0.8. Old/rollback: .38/.24/.11/.045. */
    rgba(201, 168, 76, 0.3) 0%,
    rgba(201, 168, 76, 0.19) 35%,
    rgba(201, 168, 76, 0.09) 55%,
    rgba(201, 168, 76, 0.036) 72%,
    transparent 100%
  );
  filter: blur(50px);
  pointer-events: none;
}

.cv-mockup {
  position: relative;
  /* Отступ от текстового блока 1 — 32px (Иван, правка структуры
     2026-07-28). Высота контейнера = видимая высота рамы напрямую
     (.cv-frame ниже — height:100%, без «мёртвой» зоны): 380px мобайл,
     500px десктоп — см. override ниже (Иван 28.07.2026, референс-
     скриншот пропорции). Раньше .cv-frame был 82% от контейнера —
     из-за этого между заголовком и рамой набегал лишний зазор сверх
     margin-top (Иван указал на живом скрине 28.07.2026). */
  margin-top: var(--space-4);
  height: 380px;
  width: auto;
  max-width: 100%;
  aspect-ratio: 4 / 5;
  margin-left: auto;
  margin-right: auto;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Рама — чистый CSS, без PNG-ассетов (техника подсмотрена у
   companionarchive.com при исследовании: три вложенных слоя вместо
   картинки рамы — свои цвета/тени, никаких чужих файлов не берём).
   .cv-frame = сам багет (цвет меняется по data-frame Black/White),
   .cv-frame-gap = всегда тёмный «зазор»-паспарту (не зависит от цвета
   рамы — это тень внутренней канавки, как в реальном багете),
   .cv-frame-photo = утопленное фото со своей inset-тенью. Портрет
   внутри (#cv-mockup-img) — тот же _selectedPreviewUrl, что уже
   публично отдаётся как основное превью выше: тот же URL, тот же
   уровень ватермарка, ничего нового не генерится и не раздаётся
   (решение Ивана 2026-07-27, next-chat-fakedoor-canvas-mockup.md). */
.cv-frame {
  position: relative;
  height: 100%;
  width: auto;
  aspect-ratio: 4 / 5;
  box-sizing: border-box;
  padding: 14px;
  border-radius: 2px;
}

.cv-frame[data-frame="black"] {
  /* Реализм рамы v2 (2026-07-28, тот же чат) — рецепт "1 в 1" с
     companionarchive.com/preview (Иван прогнал их живой upload-флоу,
     мы разобрали computed styles их рамы прямо в браузере). Их подход
     ИНАЧЕ устроен, чем наши прошлые попытки: НЕ градиент по плоскости
     (background: сплошной #0e0e0e, backgroundImage: none), вся
     "объёмность" — только через box-shadow: отдельная инсет-линия на
     КАЖДОЙ из 4 сторон (верх/лево светлее — .08/.05, низ/право
     заметно темнее — .7/.5, а не единый диагональный градиент), плюс
     тонкий inset 0 0 0 1px .03 (у нас он уже был с прошлого шага) и
     жёсткая внешняя обводка 0 0 0 1px rgba(0,0,0,.6), которая чётко
     отделяет раму от фона страницы. Наши пропорции/паддинги/aspect-
     ratio не трогаем — переносим только цвет+тени. Значения — 1:1 их
     computed style, без адаптации альфы под наш более тёмный фон
     (Иван попросил именно так, точная копия рецепта).
     Old/rollback (135deg-градиент): linear-gradient(135deg, #302f2d 0%,
     #1d1c1a 45%, #100f0e 100%) + box-shadow 0 18px 40px -18px rgba(0,0,0,.55),
     0 6px 14px -8px rgba(0,0,0,.35), inset 0 0 0 1px rgba(255,255,255,.03)
     — см. git history. */
  background: #0e0e0e;
  box-shadow:
    0 0 0 1px rgba(0, 0, 0, 0.6),
    inset 0 1px 0 rgba(255, 255, 255, 0.08),
    inset 1px 0 0 rgba(255, 255, 255, 0.05),
    inset 0 -1px 0 rgba(0, 0, 0, 0.7),
    inset -1px 0 0 rgba(0, 0, 0, 0.5),
    inset 0 0 0 1px rgba(255, 255, 255, 0.03),
    0 18px 40px -18px rgba(0, 0, 0, 0.35),
    0 6px 12px -6px rgba(0, 0, 0, 0.18);
}

.cv-frame[data-frame="white"] {
  background: linear-gradient(160deg, #f7f4ee 0%, #e8e2d6 55%, #f2ede4 100%);
  box-shadow:
    0 18px 40px -18px rgba(0, 0, 0, 0.45),
    0 6px 14px -8px rgba(0, 0, 0, 0.25),
    inset 0 1px 0 rgba(255, 255, 255, 0.85),
    inset 0 -1px 0 rgba(0, 0, 0, 0.18),
    inset 1px 0 0 rgba(255, 255, 255, 0.55),
    inset -1px 0 0 rgba(0, 0, 0, 0.12);
}

.cv-frame-gap {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  padding: 6px;
  /* Откат (2026-07-28, тот же чат) — тёмный нейтральный вариант зазора
     Иван попросил вернуть: со смещённой тенью рамы (см. .cv-frame выше)
     тёмная щель перестала читаться как объём вставки холста, стало
     непонятно/плоско. Возвращён прежний светлый вариант; направление
     дальнейшей доработки — обсудить отдельно. */
  background: linear-gradient(180deg, #2a2a2a 0%, #3a3a3a 50%, #242424 100%);
  box-shadow:
    inset 0 2px 4px rgba(0, 0, 0, 0.55),
    inset 0 -1px 2px rgba(0, 0, 0, 0.35),
    inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

.cv-frame-photo {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  /* Микро-скругление углов холста — 2px (2026-07-28, тот же чат, Иван;
     было 3px, показалось многовато) — идеально прямой 90°-угол не
     встречается в жизни, едва заметное скругление добавляет реализма.
     px, не % — на маленьком элементе (~380–500px) % даёт эллиптическую,
     а не круглую фаску. overflow:hidden уже на этом же элементе —
     обрезает и сам img (.cv-frame-img отдельного radius не требует). */
  border-radius: 2px;
  background: var(--bg-elevated);
  box-shadow:
    inset 0 4px 8px -2px rgba(0, 0, 0, 0.55),
    inset 0 2px 4px -1px rgba(0, 0, 0, 0.45),
    inset 0 0 0 1px rgba(0, 0, 0, 0.5);
}

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

/* Этап 3b фейк-дора (2026-07-27): graceful degrade, если у #cv-mockup-img
   нет готового clean-превью (job ещё генерируется / legacy без clean_key /
   объект истёк по R2-lifecycle) — скрывает битую иконку браузера, рамка
   остаётся целой на фоне .cv-frame-photo (--bg-elevated). Класс навешивает
   canvasProduct.setPreviewJobId() (js/main.js) по onerror; изолированный
   селектор, .cv-frame-img без модификатора не затронут. */
.cv-frame-img.cv-frame-img-error {
  opacity: 0;
}

.cv-row-label {
  font-family: var(--font-sans);
  font-size: var(--font-size-section-label);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin: var(--space-3) 0 8px; /* низ 10px→8px, макет физ. карточек 28.07.2026 (Чат 4) */
}

/* Frame toggle: Black / White (микро-компонент в рамках ДС). */
.cv-frame-toggle {
  display: flex;
  gap: 10px;
}

.cv-frame-opt {
  flex: 1;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  color: var(--text-secondary);
  font-family: var(--font-sans);
  font-size: var(--font-size-nav-link);
  font-weight: 500;
  cursor: pointer;
  transition: border-color var(--transition-fast), color var(--transition-fast), background var(--transition-fast);
}

.cv-frame-opt:hover {
  border-color: var(--border-medium);
  color: var(--text-primary);
}

.cv-frame-opt.is-active {
  border-color: var(--border-active-muted);
  background: var(--gold-glow);
  color: var(--text-primary);
  font-weight: 600;
}

/* Скрыт по макету физ. карточек (Чат 4, 28.07.2026) — Иван: рама
   Framed Canvas всегда чёрная, тумблер пока не нужен, но может
   вернуться позже — код/разметка/аналитика (canvas_frame_select)
   НЕ удалены, только спрятаны. Правило идёт ПОСЛЕ .cv-frame-toggle
   выше в каскаде, иначе display:flex там перебил бы display:none. */
#cv-frame-label,
.cv-frame-toggle {
  display: none;
}

/* Included */
.cv-included {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.cv-included-item {
  display: flex;
  align-items: flex-start;
  gap: 4px; /* иконка-текст 8px→4px, макет физ. карточек 28.07.2026 (Чат 4) */
  font-family: var(--font-sans);
  font-size: var(--font-size-nav-link); /* 14px — переиспользуем ближайший токен, Иван подтвердил (Чат 4) */
  color: var(--text-tertiary); /* было --text-secondary, макет физ. карточек (Чат 4) */
  line-height: 1.4;
}

.cv-included-icon {
  width: 16px;
  height: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--text-tertiary);
  margin-top: 1px;
}

/* ── Текстовый блок 2 (над карточками) — правка структуры 2026-07-28 ──
   Отступ сверху 56px — Иван указал точное значение (28.07.2026), не
   попадает в шаг сетки spacing (48/64) — оставлено числом сознательно,
   без нового spacing-токена. */
.cv-title--options {
  margin-top: 56px;
  /* H4 по дизайн-системе (~26px), а не H3 как у первого заголовка —
     пропущено в первой правке структуры (Иван указал отдельно). */
  font-size: var(--font-size-h4);
  line-height: var(--line-height-h4);
  letter-spacing: var(--letter-spacing-h4);
}

/* ── Две карточки-приманки (Fine Art Print / Framed Canvas) ───────────
   Правка структуры 2026-07-28 (next-chat-results-preview-text.md):
   паритетная вёрстка обеих карточек — .cv-card/.cv-sizes/.cv-badge
   заменены на .cv-option-card ниже (значения — из макета Ивана:
   300px/24px gap/radius-md/bg-secondary/border-default). На мобиле
   карточки в столбик на всю ширину — см. @media ниже. */
.cv-options {
  display: flex;
  justify-content: center;
  gap: var(--space-3);
  margin-top: var(--space-4);
  flex-wrap: wrap;
}

.cv-option-card {
  position: relative;
  /* Мобайл/планшет (≤1023px) — на всю ширину колонки (те же боковые
     20px, что и в .cv-wrap/мобильном хедере, Иван 28.07.2026); 300px
     фиксированной ширины возвращается на десктопе — см. override
     в @media (min-width: 1024px) ниже. */
  width: 100%;
  max-width: 100%;
  padding: var(--space-4) 20px 20px;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  background: var(--bg-base); /* было --bg-secondary, макет физ. карточек 28.07.2026 (Чат 4) */
  display: flex;
  flex-direction: column;
}

/* Тег над карточкой («Most Popular» / «The Perfect Gift», без эмодзи) —
   тот же приглушённый pill-паттерн, что раньше был у .cv-badge на строке
   размера, просто крупнее и по центру верхнего края карточки.
   Чат 4 (28.07.2026, макет физ. карточек): бордер medium→default,
   размер шрифта переведён на готовый токен --font-size-badge (11px),
   цвет gold-light→gold-primary («Active»). */
.cv-option-badge {
  position: absolute;
  top: -11px;
  left: 50%;
  transform: translateX(-50%);
  height: 22px;
  display: inline-flex;
  align-items: center;
  padding: 0 12px;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-pill);
  background: var(--bg-base);
  color: var(--gold-primary);
  font-family: var(--font-sans);
  font-size: var(--font-size-badge);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1;
  white-space: nowrap;
}

.cv-option-title {
  /* 20px — числовое значение только для карточек (в системе нет такого
     H-размера), line-height переведён на готовый --line-height-h4
     (1.25 = 125%). Макет физ. карточек, Чат 4, 28.07.2026. */
  font-family: var(--font-serif);
  font-size: 20px;
  font-weight: var(--font-weight-serif-semibold);
  line-height: var(--line-height-h4);
  color: var(--text-primary);
  margin: var(--space-2) 0 0;
  text-align: center;
}

.cv-option-price {
  /* ⚠️Правка (28.07.2026, тот же чат): --font-size-h3 — clamp(22px,
     2.8vw,32px), привязанный к ширине VIEWPORT, а не карточки. На
     десктопе 2.8vw упирается в потолок 32px (смотрелось нормально),
     но на мобиле карточка растянута на 100% колонки, а не привязана
     к viewport — 2.8vw там ~10-12px, клэмп проваливался в свой
     минимум 22px, цена становилась мельче, чем была раньше (34px).
     Возврат на фиксированный размер 34px (как и было изначально) —
     без vw-логики, одинаково на всех экранах. */
  font-family: var(--font-serif);
  font-size: 34px;
  font-weight: var(--font-weight-serif-semibold);
  color: var(--gold-primary);
  line-height: var(--line-height-h3);
  letter-spacing: var(--letter-spacing-h3);
  margin: 8px 0 0;
  text-align: center;
}

.cv-option-desc {
  /* Размер 14px — переиспользован ближайший готовый токен
     (--font-size-nav-link), Иван подтвердил, новый токен не нужен
     (Чат 4). line-height 160% и line-clamp — числом, без токена
     (аналогично прецеденту 56px в .cv-title--options). Текст
     черновой, финальная копия — от Ивана/маркетинга позже. */
  font-family: var(--font-sans);
  font-size: var(--font-size-nav-link);
  color: var(--text-secondary);
  line-height: 1.6;
  margin: 16px 0 0;
  text-align: center;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.cv-option-field {
  margin-top: var(--space-3);
}

/* Choose size — первый select-компонент на сайте (решение Ивана
   2026-07-28: нативный <select> вместо кастомного dropdown — меньше
   кода, меньше edge cases с фокусом/клавиатурой/закрытием, на мобиле
   открывается родной пикер ОС). Закрытое состояние стилизовано под
   токены инпутов дизайн-системы; раскрытый список рисует браузер. */
.cv-select-wrap {
  position: relative;
}

.cv-select {
  width: 100%;
  height: 40px; /* было 44px, макет физ. карточек 28.07.2026 (Чат 4) */
  padding: 0 40px 0 14px;
  background: var(--input-bg);
  border: 1px solid var(--input-border);
  border-radius: var(--radius-sm);
  color: var(--input-text);
  font-family: var(--font-sans);
  font-size: var(--font-size-nav-link);
  font-weight: 500;
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  transition: border-color var(--transition-fast);
}

.cv-select:hover {
  border-color: var(--border-medium);
}

.cv-select:focus {
  outline: none;
  border-color: var(--input-border-focus);
}

.cv-select-arrow {
  position: absolute;
  top: 50%;
  right: 14px;
  transform: translateY(-50%);
  color: var(--text-tertiary);
  pointer-events: none;
  display: flex;
}

/* Плашка Free Shipping — Чат 4 (28.07.2026), макет физ. карточек:
   разворот предыдущего решения (было — заливка bg-elevated без
   бордера, «чтобы не выглядело кликабельно»). Теперь — прозрачный
   фон + тёмный бордер (--border-subtle), высота ~50px за счёт второй
   строки (срок доставки). Иван подтвердил это как финальную версию. */
.cv-shipping-plate {
  margin-top: var(--space-4);
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 50px;
  padding: 12px 14px;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  background: transparent;
}

.cv-shipping-icon {
  width: 24px;
  height: 24px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-secondary);
}

.cv-shipping-copy {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.cv-shipping-title {
  font-family: var(--font-sans);
  font-size: var(--font-size-caption);
  font-weight: 600;
  color: var(--text-secondary);
  line-height: 1.3;
}

.cv-shipping-sub {
  /* Заглушка — реальный срок доставки Printful ещё не подтверждён
     (Чат 4, 28.07.2026). Только для теста фейк-дора спроса. */
  font-family: var(--font-sans);
  font-size: var(--font-size-micro);
  color: var(--text-tertiary);
  line-height: 1.3;
}

/* Подпись рядом с кнопкой о том, что цифровой файл включён.
   Чат 4 (28.07.2026), уточнение: одна строка, выравнивание по левому
   краю, без цены в скобках (текст убран из index.html), иконка+текст
   — --text-secondary (было --text-tertiary/центр). */
.cv-option-note {
  margin: var(--space-3) 0 0;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 4px;
  font-family: var(--font-sans);
  font-size: var(--font-size-savings);
  color: var(--text-secondary);
  text-align: left;
  white-space: nowrap;
}

.cv-option-note-icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-secondary);
}

.cv-option-cta {
  /* Высота/шрифт переопределены ТОЛЬКО в скоупе карточки — общий
     .btn-primary (60px/16px) по сайту не трогаем (Чат 4, 28.07.2026).
     Базовое (мобиль/планшет) значение — 44px: на десктопе карточка
     фиксированная 300px и 40px смотрится пропорционально, но на
     растянутой на 100% ширины мобильной карточке та же высота
     визуально казалась ниже/уже — Иван, 28.07.2026 (уточнение).
     Десктопный override 40px — в @media (min-width: 1024px) ниже. */
  margin-top: var(--space-2);
  height: 44px;
  font-size: 15px;
}

/* ── Широкая карточка снизу — единственный реальный товар на экране
   (цифровой файл, ведёт напрямую в Purchase Sheet). Высота ~82px,
   radius/border — как у карточек выше (Иван 2026-07-28). Без отдельной
   кнопки — весь баннер кликабелен, содержимое центрировано (Иван,
   28.07.2026, референс-скрин); клик навешен на #cv-digital-wide в
   js/main.js (role="button" в разметке). ────────────── */
.cv-digital-wide {
  margin-top: var(--space-4);
  min-height: 82px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 14px 16px;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: border-color var(--transition-fast);
}

/* Hover — тот же паттерн, что .why-card/.faq-card на сайте (28.07.2026,
   Иван): только рамка светлеет, заливка/свечение не трогаем. */
.cv-digital-wide:hover {
  border-color: var(--border-medium);
}

.cv-digital-wide-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  vertical-align: middle;
  margin-right: 6px;
  color: var(--text-primary);
}

.cv-digital-wide-title {
  /* НЕ flex (Иван указал на живой баг 28.07.2026) — на границе
     анонимных flex-боксов браузер съедал пробел между «for» и ценой
     («for$14.99» слитно). Обычный инлайн-поток + text-align:center
     родителя (.cv-digital-wide) + inline-flex/vertical-align у иконки
     ниже — того же выравнивания без потери пробелов. */
  font-family: var(--font-sans);
  font-size: var(--font-size-body-medium);
  font-weight: 600;
  color: var(--text-primary);
  margin: 0;
}

.cv-digital-wide-sub {
  font-family: var(--font-sans);
  font-size: var(--font-size-body-medium);
  color: var(--text-tertiary);
  margin: 2px 0 0;
}

/* ── Desktop (≥1024px) — Иван 2026-07-27, п.2 обновлён 2026-07-28 ────
   1) Превью портрета вверху (.res-main-img) крупнее примерно на 30%
      (400 → 520px); мобильная версия не трогается (там 400px оставлен
      как есть). .cv-mockup (рама) больше НЕ имеет отдельного desktop-
      override — высота унифицирована на 500px на всех брейкпоинтах
      (правка структуры 2026-07-28, см. .cv-mockup выше).
   2) Карточки-опции (.cv-options) и широкая карточка цифрового файла
      (.cv-digital-wide) — .cv-card/.cv-digital сняты вместе со старой
      разметкой, .cv-options уже имеет фиксированную ширину карточек
      (300px) из макета, доп. desktop-правка не нужна; .cv-digital-wide
      ограничиваем той же шириной, что и раньше (80% колонки). */
@media (min-width: 1024px) {

  .cv-wrap {
    /* Нижний край лого (top:40px + высота 40px SVG) — 80px от верха +
       70px зазор до заголовка (Иван, 28.07.2026) = 150px (было по
       ошибке 190px — считал от «120px зоны» с лишним буфером сверх
       реального лого, а не от его настоящего нижнего края). */
    padding-top: 150px;
  }

  .cv-mockup {
    /* Видимая рама (.cv-frame — height:100%) = 500px на десктопе
       (Иван, 28.07.2026, референс-скриншот пропорции) */
    height: 500px;
  }

  .cv-option-card {
    /* Фиксированная ширина карточек возвращается на десктопе — на
       мобайле/планшете растянуты на 100% (см. базовое правило выше). */
    width: 300px;
  }

  .cv-option-cta {
    /* На десктопе карточка узкая (300px) — та же высота, что и
       мобильный базовый override, смотрится непропорционально высокой;
       возвращаем 40px (Иван подтвердил на десктопе выглядит нормально). */
    height: 40px;
  }

  .cv-frame {
    padding: 18px;
  }

  .cv-frame-gap {
    padding: 8px;
  }

  .cv-digital-wide {
    /* 624px = ширина двух карточек-опций (300+300) + gap 24px между ними
       (.cv-options) — баннер выравнивается по их общей ширине на десктопе
       (Иван, 28.07.2026). margin-top 24px = var(--space-3), тот же токен,
       что и gap карточек; на мобиле margin-top остаётся var(--space-4)
       (базовое правило .cv-digital-wide выше, тут не переопределяется). */
    width: 624px;
    margin: var(--space-3) auto 0;
  }
}


/* ============================================================
   FAKE-DOOR SHEET (fd-*) — надстройка над общими ps-*-классами
   (общие стили ps-sheet/ps-email/ps-cta НЕ меняются).
   ============================================================ */

/* Невалидный email / ошибка отправки — строка под полем */
.fd-error {
  margin: 8px 0 0;
  font-family: var(--font-sans);
  font-size: var(--font-size-body-small);
  color: var(--input-border-error);
}

/* Success-состояние: золотой чек-круг (паттерн .pc-icon) + текст */
.fd-success {
  text-align: center;
  padding: var(--space-4) 0 var(--space-2);
}

.fd-success-icon {
  width: 64px;
  height: 64px;
  margin: 0 auto;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--gold-primary);
  background: var(--bg-elevated);
  border: 1px solid var(--gold-primary);
}

.fd-success-text {
  margin: var(--space-2) auto 0;
  max-width: 320px;
  font-family: var(--font-sans);
  font-size: var(--font-size-body-medium);
  color: var(--text-primary);
  line-height: 1.45;
}

/* Вторичный блок: цифровой файл (ведёт в существующий Purchase Sheet) */
.fd-digital {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-subtle);
}

.fd-digital-text {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--font-size-body-small);
  color: var(--text-secondary);
  text-align: center;
  line-height: 1.45;
}

.fd-digital-text b {
  color: var(--text-primary);
  font-weight: 600;
}

.fd-digital-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  /* 56px = высота primary-CTA этого шита (.ps-cta-btn «Notify me…»):
     обе кнопки в окне одной высоты (Иван 2026-07-27). */
  height: 56px;
  margin-top: 12px;
  background: transparent;
  border: 1px solid var(--button-secondary-border);
  border-radius: var(--radius-sm);
  color: var(--button-secondary-text);
  font-family: var(--font-sans);
  font-size: var(--font-size-nav-link);
  font-weight: 600;
  cursor: pointer;
  transition: color var(--transition-fast), border-color var(--transition-fast);
}

.fd-digital-btn:hover {
  color: var(--button-secondary-hover);
  border-color: var(--button-secondary-hover);
}

.fd-fineprint {
  margin: var(--space-2) 0 0;
  padding-bottom: var(--space-4);
  font-family: var(--font-sans);
  font-size: var(--font-size-micro);
  color: var(--text-tertiary);
  text-align: center;
}

/* Mobile bottom sheet: нижний клиренс с учётом safe area */
@media (max-width: 680px) {
  .fd-fineprint {
    padding-bottom: calc(32px + env(safe-area-inset-bottom));
  }
}

/* Переключение состояний формы — надёжный display:none (как pw-step) */
.fd-form[hidden],
.fd-success[hidden] {
  display: none !important;
}


/* Preview cards row */
.results-previews {
  display: flex;
  gap: var(--space-2);
  justify-content: center;
  margin-bottom: var(--space-3);
  flex-wrap: wrap;
}

.results-card {
  position: relative;
  cursor: pointer;
  border: 2px solid var(--border-default);
  border-radius: var(--radius-xs);
  overflow: hidden;
  transition: border-color var(--transition-base), transform var(--transition-base);
  width: 180px;
  flex-shrink: 0;
}

.results-card:hover {
  border-color: var(--gold-muted);
  transform: translateY(-3px);
}

.results-card.is-selected {
  border-color: var(--gold-primary);
}

/* Checkmark badge on selected card */
.results-card.is-selected::after {
  content: '✓';
  position: absolute;
  top: 8px;
  right: 8px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--gold-primary);
  color: var(--bg-base);
  font-size: 12px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 22px;
  text-align: center;
}

.results-card-img {
  display: block;
  width: 100%;
  height: 220px;
  object-fit: cover;
  pointer-events: none;
}

/* Mobile: 2-up grid */
@media (max-width: 480px) {
  .results-previews {
    gap: var(--space-1);
  }
  .results-card {
    width: calc(50% - 4px);
  }
  .results-card-img {
    height: 160px;
  }
}

/* Generations remaining */
.results-remaining {
  font-family: var(--font-sans);
  font-size: var(--font-size-body-small);
  color: var(--text-tertiary);
  margin-bottom: var(--space-4);
}

/* Action buttons */
.results-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-width: 400px;
  margin: 0 auto;
}

/* Ghost button variant for results screen */
.results-btn-ghost {
  width: 100%;
  height: 52px;
  background: transparent;
  border: 1px solid var(--button-ghost-border);
  border-radius: var(--radius-xs);
  color: var(--button-ghost-text);
  font-family: var(--font-sans);
  font-size: var(--font-size-body-medium);
  font-weight: 500;
  cursor: pointer;
  transition: border-color var(--transition-base), color var(--transition-base);
}

.results-btn-ghost:hover {
  border-color: var(--button-ghost-hover-border);
  color: var(--button-ghost-hover-text);
}

/* Disabled "Get portrait" button */
#get-portrait-btn:disabled {
  background: var(--button-disabled-bg);
  color: var(--button-disabled-text);
  cursor: not-allowed;
}


/* ── Reduced motion override ─────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  .anim-hidden,
  .anim-reel-hidden,
  .anim-emerge,
  .anim-footer {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }

  /* No "transform: none" here — .hiw-glow uses transform for its
     own positioning/rotation, not for the reveal animation, so it
     must be preserved even with motion reduced. */
  .anim-glow {
    opacity: 1 !important;
    transition: none !important;
  }

  .anim-hero-title,
  .anim-hero-subtitle,
  .anim-hero-cta,
  .anim-hero-logo {
    opacity: 1 !important;
    transform: none !important;
    animation: none !important;
  }

  /* transform preserved (not "none") — translateX(-50%) here is doing
     the logo's horizontal centering, not part of the reveal motion. */
  .loading-logo {
    opacity: 1 !important;
    transform: translateX(-50%) !important;
    animation: none !important;
  }

  /* Class-toggle (not a keyframe animation) — killing the transition
     alone makes .is-open jump straight to its end state, no fade/slide.
     JS also skips the hero-sequence wait state's matching close delay
     (see CLOSE_TRANSITION_MS in main.js). */
  .cookie-consent {
    transition: none !important;
  }
}


/* ============================================================
   PURCHASE SHEET
   Desktop: центральный модал. Mobile (≤680px): bottom sheet.
============================================================ */

/* ── Overlay (backdrop) ───────────────────────────────────── */
.ps-overlay {
  position: fixed;
  inset: 0;
  z-index: 900;
  background: rgba(0, 0, 0, 0.72);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition-base);
}

.ps-overlay.is-open {
  opacity: 1;
  pointer-events: auto;
  touch-action: none; /* блокирует фоновый скролл на мобайле без position:fixed на body */
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

/* ── Sheet base ───────────────────────────────────────────── */
.ps-sheet {
  position: fixed;
  z-index: 901;
  /* Neutral tonal gradient, no accent hue — just the card's own two dark
     tones (elevated → secondary), brighter near the same top point the
     .ps-chrome hairline lights from, settling back to the flat secondary
     tone for the rest of the card. Reads as "the light from the top
     edge gently warms the surface near it," not a separate decoration —
     restrained on purpose, felt more than seen. */
  background: radial-gradient(ellipse 140% 70% at 38% -5%, var(--bg-elevated) 0%, var(--bg-secondary) 65%, var(--bg-secondary) 100%);
  /* Explicit solid fallback — the `background` shorthand above only ever
     sets background-image, so getComputedStyle(...).backgroundColor was
     literally "rgba(0,0,0,0)" (fully transparent). Safari 26's bottom
     system-bar tint heuristic reads that computed background-color of
     the topmost fixed element at the screen edge — finding it
     transparent here, it was falling through to a solid-colored
     descendant instead (the Stripe-purple #635BFF .ps-cta-btn near the
     bottom of the sheet), tinting the bar purple. This line changes
     nothing visually (the gradient above is already fully opaque by the
     time it reaches --bg-secondary) — it only gives Safari a real color
     to find first. */
  background-color: var(--bg-secondary);
  display: flex;
  flex-direction: column;
  overflow: hidden; /* сам sheet не скроллится — скроллится только .ps-scroll внутри */
}

/* Гарантируем, что hidden-атрибут работает даже при display:flex выше */
.ps-sheet[hidden] {
  display: none !important;
}

/* ── Desktop: центральный модал ──────────────────────────── */
@media (min-width: 681px) {
  .ps-sheet {
    top: 50%;
    left: 50%;
    transform: translate(-50%, -48%);
    width: 480px;
    max-height: 90vh;
    border-radius: var(--radius-md);
    box-shadow: 0 24px 80px rgba(0, 0, 0, 0.6);
    opacity: 0;
    transition:
      opacity var(--transition-base),
      transform var(--transition-base);
  }

  .ps-sheet.is-open {
    opacity: 1;
    transform: translate(-50%, -50%);
  }

  /* drag handle скрыт на десктопе */
  .ps-drag-handle { display: none; }
}

/* ── Mobile: bottom sheet ────────────────────────────────── */
@media (max-width: 680px) {
  .ps-sheet {
    bottom: 0;
    left: 0;
    right: 0;
    border-radius: 40px 40px 0 0;
    max-height: 90dvh;
    transform: translateY(100%);
    transition: transform 400ms cubic-bezier(0.22, 1, 0.36, 1);
  }

  .ps-sheet.is-open {
    transform: translateY(0);
  }

  /* Drag handle */
  .ps-drag-handle {
    width: 32px;
    height: 4px;
    background: var(--text-tertiary);
    opacity: 0.4;
    border-radius: var(--radius-pill);
    margin: 12px auto 0;
    flex-shrink: 0;
  }

  /* На мобайле верхний отступ от верхней границы sheet до заголовка */
  .ps-header {
    padding-top: 70px;
  }

  /* На мобайле навигационные иконки — 24px от верхней границы sheet */
  .ps-back,
  .ps-close {
    top: 24px;
  }
}

/* ── Chrome: drag handle + back/close — зафиксированы над .ps-scroll,
   не участвуют в скролле контента. Градиент от фона шита к прозрачности
   для мягкого перехода с контентом, проезжающим под низом. ──────────── */
.ps-chrome {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 2;
  pointer-events: none;
  /* Gold hairline via inset box-shadow, not a background trick — inset
     shadows are clipped by the element's own border-radius, so this
     naturally curves into the rounded top corners instead of getting
     cut off square the way a flat background line did. Single flat
     tone, no left-to-right falloff: an earlier "gold fill + opaque
     cover" version could fake that horizontal variation, but the cover
     had to be opaque to hide the fill outside the exposed 1px ring —
     which blocked the ::after blur mask below from ever reaching real
     content underneath (blur can only affect what's actually visible
     through it). A box-shadow doesn't occlude anything, so it coexists
     with the blur cleanly. */
  box-shadow: inset 0 1px 0 rgba(201, 168, 76, 0.18);
}

/* Progressive blur — dissolves content scrolling underneath the header
   optically, no color layer to mismatch .ps-sheet's radial gradient (a
   flat color fade here always read as a visible tinted rectangle, no
   matter how carefully matched). Starts at top:1px, not 0 — this keeps
   it from ever painting over the box-shadow ring above: box-shadow
   paints in the same tier as the box's own background (the bottom-most
   layer), while this pseudo-element (z-index:-1, still above that tier)
   would otherwise blur the ring right along with the content, washing
   the thin hairline out to near-invisibility.
   The mask uses several stops, not a straight 0%→100% linear fade —
   a plain linear alpha ramp over backdrop-filter reads as "strong blur,
   then a sudden snap to sharp" rather than a gradual dissolve, because
   perceived blurriness doesn't fall off linearly with mask opacity.
   Holding near-full strength briefly, then easing down through several
   steps, feels like an actual gradual dissolve instead of a hard edge.
   z-index:-1 keeps all of this below the drag handle / back / close
   icons so those stay crisp; it doesn't interact with the box-shadow
   ring's own visibility otherwise — only the overlap in area was the
   problem, fixed by the top:1px offset above. */
.ps-chrome::after {
  content: '';
  position: absolute;
  top: 1px;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: -1;
  border-radius: inherit;
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  mask-image: linear-gradient(
    to bottom,
    rgba(0, 0, 0, 1) 0%,
    rgba(0, 0, 0, 1) 15%,
    rgba(0, 0, 0, 0.7) 40%,
    rgba(0, 0, 0, 0.35) 65%,
    rgba(0, 0, 0, 0.1) 85%,
    rgba(0, 0, 0, 0) 100%
  );
  -webkit-mask-image: linear-gradient(
    to bottom,
    rgba(0, 0, 0, 1) 0%,
    rgba(0, 0, 0, 1) 15%,
    rgba(0, 0, 0, 0.7) 40%,
    rgba(0, 0, 0, 0.35) 65%,
    rgba(0, 0, 0, 0.1) 85%,
    rgba(0, 0, 0, 0) 100%
  );
}

/* TEST: blur header disabled per request — clips ascenders of scrolled
   text at the mask boundary, and doesn't read as a gradient in practice.
   Ring (box-shadow above) stays untouched. Remove this rule to restore
   the blur once we've looked at the plain version. */
.ps-chrome::after {
  content: none;
}

.ps-chrome > * {
  pointer-events: auto;
}

@media (min-width: 681px) {
  .ps-chrome {
    height: 64px;
    border-radius: var(--radius-md) var(--radius-md) 0 0;
  }
}

@media (max-width: 680px) {
  .ps-chrome {
    height: 80px;
    border-radius: 40px 40px 0 0;
  }
}

/* ── Scroll: единственная скроллящаяся область внутри sheet ──────────── */
.ps-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

@media (min-width: 681px) {
  .ps-scroll {
    padding: 60px 40px;
  }
}

@media (max-width: 680px) {
  .ps-scroll {
    padding: 0 20px;
  }
}

/* ── Доп. нижний отступ под CTA на email-шаге Paywall Sheet —
   чтобы кнопка не липла к системной панели браузера (Safari/Chrome). ── */
@media (max-width: 680px) {
  #pw-step-email {
    padding-bottom: calc(32px + env(safe-area-inset-bottom));
  }
}

/* ── Nav icons: Back + Close ─────────────────────────────────
   Back используется только в многошаговых sheet (скрыт через [hidden],
   когда у sheet нет предыдущего шага — см. Unlock Portrait). */
.ps-back,
.ps-close {
  position: absolute;
  top: 20px;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--text-tertiary);
  opacity: 0.6;
  padding: 0;
  border-radius: var(--radius-pill);
  transition: color var(--transition-fast), opacity var(--transition-fast);
}

.ps-back {
  left: 20px;
}

.ps-close {
  right: 20px;
}

.ps-back:hover,
.ps-close:hover {
  color: var(--text-secondary);
  opacity: 1;
}

.ps-back[hidden] {
  display: none;
}

/* ── Header ───────────────────────────────────────────────── */
.ps-header {
  flex-shrink: 0;
}

@media (min-width: 681px) {
  /* на десктопе padding уже в .ps-sheet, header просто текст */
  .ps-header {
    padding-top: 0;
  }
}

.ps-title {
  font-family: var(--font-serif);
  font-size: var(--font-size-h4);
  font-weight: var(--font-weight-serif-semibold);
  line-height: var(--line-height-h4);
  letter-spacing: var(--letter-spacing-h4);
  color: var(--text-primary);
  margin: 0;
  text-align: center;
}

.ps-subtitle {
  font-family: var(--font-sans);
  font-size: var(--font-size-body-medium); /* 15px */
  font-weight: 400;
  color: var(--text-tertiary);
  margin: 4px 0 0;
  text-align: center;
}

/* ── Product card ─────────────────────────────────────────── */
.ps-product-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 80px;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  margin-top: 24px;
  flex-shrink: 0;
  overflow: hidden;
}

.ps-product-left {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 0 10px 10px;
  min-width: 0;
}

.ps-product-thumb {
  width: 60px;
  height: 60px;
  border-radius: 4px;
  object-fit: cover;
  flex-shrink: 0;
}

/* Fallback shown instead of .ps-product-thumb if the preview image fails to
   load — same fixed-size box treatment as .res-img-fallback, scaled to the
   60px thumb. Hidden by default; toggled via .ps-product-left.has-error. */
.ps-product-thumb-fallback {
  display: none;
  width: 60px;
  height: 60px;
  border-radius: 4px;
  background: var(--bg-elevated);
  color: var(--text-tertiary);
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.ps-product-left.has-error .ps-product-thumb          { display: none; }
.ps-product-left.has-error .ps-product-thumb-fallback { display: flex; }

/* ── Иконка-плейсхолдер вместо превью — для шага email Paywall Sheet,
   где нет конкретного изображения, только выбранный пак генераций. ── */
.pw-product-icon {
  width: 60px;
  height: 60px;
  border-radius: 4px;
  border: 1px solid var(--border-medium);
  background: var(--bg-elevated);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-primary);
  flex-shrink: 0;
}

.ps-product-info {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.ps-product-name {
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500; /* Inter Medium */
  color: var(--text-primary);
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ps-product-desc {
  font-family: var(--font-sans);
  font-size: var(--font-size-caption); /* 12px */
  font-weight: 400;
  color: var(--text-tertiary);
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ps-product-price {
  font-family: var(--font-serif);
  font-size: var(--font-size-h4); /* из дизайн-системы, согласован с .pw-card-price (Paywall Sheet) */
  font-weight: var(--font-weight-serif-semibold);
  line-height: var(--line-height-h4);
  color: var(--gold-primary);
  margin: 0;
  padding-right: 24px;
  flex-shrink: 0;
}

/* ── Email block ──────────────────────────────────────────── */
.ps-email-block {
  display: flex;
  flex-direction: column;
  margin-top: 24px;
  flex-shrink: 0;
}

.ps-email-label {
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  color: var(--text-primary);
  margin-bottom: 8px;
}

.ps-email-field {
  display: flex;
  align-items: center;
  height: 60px;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  background: var(--input-bg);
  padding: 0 16px;
  gap: 10px;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.ps-email-field:focus-within {
  border-color: var(--text-tertiary);
  box-shadow: none;
}

.ps-email-icon {
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--text-tertiary);
}

.ps-email-input {
  flex: 1;
  background: none;
  border: none;
  outline: none;
  font-family: var(--font-sans);
  font-size: 16px; /* минимум 16px — предотвращает авто-зум iOS при фокусе */
  color: var(--input-text);
  min-width: 0;
}

.ps-email-input::placeholder {
  color: var(--input-placeholder);
}

/* ── CTA button ───────────────────────────────────────────── */
/* Grid (not flex): keeps the label perfectly centered on the
   button regardless of the trailing arrow's width — the arrow
   sits in its own column to the right with the same gap as
   before, without pulling the centered text off-axis. */
.ps-cta-btn {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  width: 100%;
  height: 56px;
  margin-top: 24px;
  background: #635BFF;
  color: #ffffff;
  font-family: var(--font-sans);
  font-size: var(--font-size-button);
  font-weight: 600; /* semi-bold */
  border: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
  flex-shrink: 0;
  transition: background var(--transition-fast), opacity var(--transition-fast);
}

.ps-cta-btn > span:first-child {
  grid-column: 2;
  justify-self: center;
}

.ps-cta-btn:hover {
  background: #7A74FF;
}

.ps-cta-btn:active {
  opacity: 0.88;
}

.ps-cta-arrow {
  grid-column: 3;
  justify-self: start;
  margin-left: 8px;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.ps-cta-btn:disabled {
  opacity: 0.65;
  cursor: default;
  pointer-events: none;
}

/* Paywall email step has no .ps-benefits after the CTA — the button is the
   last element, so it needs its own bottom breathing room instead of
   borrowing .ps-benefits' margin/padding (see #pw-step-email mobile fix
   below for the safe-area version of this same spacing on small screens). */
#pw-cta-btn {
  margin-bottom: 24px;
}

/* ── Benefits ─────────────────────────────────────────────── */
.ps-benefits {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-top: 24px;
  padding-bottom: 60px;
  flex-shrink: 0;
}

.ps-benefits-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
}

.ps-benefit {
  display: flex;
  align-items: center;
  gap: 8px;
}

.ps-benefit-icon {
  width: 16px;
  height: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--text-tertiary);
}

.ps-benefit-text {
  font-family: var(--font-sans);
  font-size: var(--font-size-caption); /* 12px */
  color: var(--text-tertiary);
  white-space: nowrap;
}

/* ── Mobile bottom padding (safe area) ───────────────────── */
@media (max-width: 680px) {
  .ps-benefits {
    padding-bottom: calc(60px + env(safe-area-inset-bottom));
  }
}

/* ── iOS Safari bottom-bar tint fix (Purchase Sheet only) ──────────
   Safari on iOS colors its home-indicator bar by sampling whatever's
   actually rendered at the safe-area strip in the moment — and that
   moment isn't fixed: focusing #ps-email-input opens the keyboard,
   shrinking dvh and forcing .ps-scroll to scroll, so the CTA button's
   own blue background can end up being the last thing rendered flush
   against that strip even though .ps-benefits (after it) already has
   its own safe-area padding — that padding doesn't help if the clipped
   scroll boundary lands before ever reaching it. Attaching this margin
   directly to the button (not a later sibling) guarantees a dark gap
   travels with it regardless of scroll position or keyboard state.
   Scoped to #ps-sheet only — Paywall already has its own dedicated
   fix (#pw-step-email padding-bottom above), and Payment Confirmation
   has no keyboard interaction to trigger this in the first place. ── */
@media (max-width: 680px) {
  #ps-sheet .ps-cta-btn {
    margin-bottom: calc(20px + env(safe-area-inset-bottom));
  }
}


/* ============================================================
   PAYMENT CONFIRMATION SHEET
   Переиспользует .ps-overlay/.ps-sheet/.ps-scroll/.ps-title/
   .ps-subtitle/.ps-cta-btn. Своя сетка отступов (60 / 48 / 24 / 60),
   одинаковая на десктопе и мобиле. Золотая обводка .ps-chrome остаётся
   (единый стиль для всех модалок) — отключаем только ::after (блюр-
   маска скролла): здесь контент не скроллится, маскировать нечего,
   а блюр перекрывал иконку подтверждения на bottom sheet.
============================================================ */

/* Блюр-маска скролла не нужна — .ps-chrome::after её несёт; отключаем
   только её, золотая обводка (.ps-chrome, база) остаётся видна. */
#pc-sheet .ps-chrome::after {
  content: none;
}

/* Своя верхняя отбивка вместо общей .ps-header padding-top:70px
   (та была рассчитана под подложку chrome, которой здесь больше нет). */
#pc-sheet .ps-header {
  padding-top: 0;
}

#pc-sheet .ps-scroll {
  padding-top: 60px;
}

.pc-icon {
  width: 64px;
  height: 64px;
  margin: 0 auto;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--gold-primary);
  background: var(--bg-elevated);
  border: 1px solid var(--gold-primary);
}

.pc-header {
  margin-top: 48px;
}

.pc-cta-btn {
  background: var(--border-default);
  color: var(--text-primary);
  margin-bottom: 60px;
}

.pc-cta-btn:hover {
  background: var(--button-ghost-hover-border);
}

@media (max-width: 680px) {
  .pc-cta-btn {
    margin-bottom: calc(60px + env(safe-area-inset-bottom));
  }
}


/* ============================================================
   PAYWALL SHEET
   Открывается на «Get More Previews». Отдельный компонент от
   Purchase Sheet (Unlock Portrait) — та же hidden-механика,
   те же общие .ps-back/.ps-close/.ps-drag-handle/.ps-header/
   .ps-title/.ps-subtitle/.ps-product-card/.ps-email-* / .ps-cta-btn.
   Desktop: центральный модал. Mobile (≤680px): bottom sheet.
============================================================ */

/* ── Overlay (backdrop) ───────────────────────────────────── */
.pw-overlay {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 900;
  background: rgba(0, 0, 0, 0.72);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition-base);
}

.pw-overlay.is-open {
  opacity: 1;
  pointer-events: auto;
  touch-action: none;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

/* ── Sheet base ───────────────────────────────────────────── */
.pw-sheet {
  position: fixed;
  z-index: 901;
  /* Same neutral tonal gradient as .ps-sheet — see its comment. */
  background: radial-gradient(ellipse 140% 70% at 38% -5%, var(--bg-elevated) 0%, var(--bg-secondary) 65%, var(--bg-secondary) 100%);
  /* Same Safari 26 tint fallback as .ps-sheet — see its comment. */
  background-color: var(--bg-secondary);
  display: flex;
  flex-direction: column;
  overflow: hidden; /* сам sheet не скроллится — скроллится только .ps-scroll внутри */
}

.pw-sheet[hidden] {
  display: none !important;
}

/* ── Desktop: центральный модал ──────────────────────────── */
@media (min-width: 681px) {
  .pw-sheet {
    top: 50%;
    left: 50%;
    transform: translate(-50%, -48%);
    width: 760px;
    max-height: 90vh;
    border-radius: var(--radius-md);
    box-shadow: 0 24px 80px rgba(0, 0, 0, 0.6);
    opacity: 0;
    transition:
      opacity var(--transition-base),
      transform var(--transition-base);
  }

  .pw-sheet.is-open {
    opacity: 1;
    transform: translate(-50%, -50%);
  }
}

/* ── Mobile: bottom sheet ────────────────────────────────── */
@media (max-width: 680px) {
  .pw-sheet {
    bottom: 0;
    left: 0;
    right: 0;
    border-radius: 40px 40px 0 0;
    max-height: 90dvh;
    transform: translateY(100%);
    transition: transform 400ms cubic-bezier(0.22, 1, 0.36, 1);
  }

  .pw-sheet.is-open {
    transform: translateY(0);
  }
}

/* ── Pricing cards (Step 1) ───────────────────────────────── */
.pw-cards {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-top: 24px;
  padding-bottom: 60px;
  flex-shrink: 0;
}

@media (min-width: 681px) {
  .pw-cards {
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-items: start;
  }
}

@media (max-width: 680px) {
  .pw-cards {
    padding-bottom: calc(60px + env(safe-area-inset-bottom));
  }
}

.pw-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 24px 20px 16px;
  border: 1px solid var(--border-medium);
  border-radius: var(--radius-md); /* 16px */
  background: var(--bg-secondary);
}

.pw-card--featured {
  border-color: var(--border-active);
}

.pw-badge {
  position: absolute;
  top: 20px;
  right: 20px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  padding: 0 14px;
  border: 1px solid var(--border-active);
  border-radius: var(--radius-pill);
  background: var(--bg-base);
  color: var(--gold-primary);
  font-family: var(--font-sans);
  font-size: var(--font-size-badge); /* 11px */
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  line-height: 1;
}

.pw-card-name {
  font-family: var(--font-serif);
  font-size: 18px;
  font-weight: var(--font-weight-serif-semibold);
  line-height: 1.1;
  color: var(--text-secondary);
  margin: 0;
  padding-right: 80px; /* не наезжать на бейдж */
}

.pw-card-price {
  font-family: var(--font-serif);
  font-size: var(--font-size-h4); /* из дизайн-системы, согласован с .ps-product-price (Purchase Sheet) */
  font-weight: var(--font-weight-serif-semibold);
  line-height: var(--line-height-h4);
  color: var(--gold-primary);
  margin: 0;
}

.pw-card-tagline {
  font-family: var(--font-sans);
  font-size: var(--font-size-nav-link); /* 14px */
  color: var(--text-tertiary);
  margin: 0;
}

.pw-card-divider {
  width: 100%;
  height: 1px;
  margin: 0;
  border: 0;
  background: var(--border-medium);
}

.pw-card-benefits {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.pw-card-benefit {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--text-secondary);
}

.pw-card-benefit-icon {
  width: 16px;
  height: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  /* цвет наследуется от .pw-card-benefit (text-secondary) — совпадает с цветом текста строки */
}

.pw-card-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 48px;
  background: none;
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 600;
  border: 1px solid var(--border-active);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
}

.pw-card-cta:hover {
  background: var(--gold-primary);
  color: var(--bg-base);
}

.pw-card--featured .pw-card-cta {
  background: var(--gold-primary);
  color: var(--bg-base);
  border-color: var(--gold-primary);
}

.pw-card--featured .pw-card-cta:hover {
  opacity: 0.88;
}

.pw-card-note {
  margin: 8px 0 0; /* + 16px gap контейнера = 24px от кнопки */
  font-family: var(--font-sans);
  font-size: var(--font-size-micro); /* 11px */
  color: var(--text-secondary);
  text-align: center;
}

/* ── Step switching ───────────────────────────────────────── */
.pw-step[hidden] {
  display: none !important;
}


/* ============================================================
   COOKIE CONSENT BANNER
   Bottom bar, full width, shown until the visitor accepts or
   declines analytics cookies (Google Analytics, Microsoft Clarity).
   Glass background (bg-elevated + blur), border only on top.
   Desktop: text + actions in one row, aligned to the site's central
   axis (.section-container). Mobile: everything centered, stacked.
============================================================ */

.cookie-consent {
  position: fixed;
  /* Centered card, not edge-to-edge: width shrinks to fill the gap on
     narrow screens, then stops growing past max-width and just stays
     centered with extra space on either side — text and buttons never
     drift apart on wide viewports. */
  left: 50%;
  /* Side/bottom gap matches .section-container's own padding formula
     (the landing page's general side margin) exactly — not the larger
     padding used by the benefit/FAQ cards, which is intentionally
     bigger and stays as-is. */
  width: calc(100% - 2 * clamp(20px, 5vw, 80px));
  max-width: 680px;
  /* Bottom gap stays flat (matches the side-padding clamp's own floor) —
     unlike the side margins, it must NOT grow with viewport width, or the
     card "flies up" on wide monitors and overlaps the footer copyright line. */
  bottom: calc(20px + env(safe-area-inset-bottom));
  z-index: 800; /* above content/footer, below modal sheets/overlays (.ps-/.pw-overlay at 900+) so an open sheet covers the banner */
  /* Exact site background color, slightly more see-through than a flat
     fill so the page genuinely reads through the blur instead of just
     looking like a solid dark card. */
  background: rgba(13, 11, 8, 0.75); /* var(--bg-base) at 75% — ~10pt more see-through than before */
  border: 1px solid var(--border-default); /* full border now it's a floating card, not a full-width bar */
  border-radius: var(--radius-lg);
  backdrop-filter: blur(38px);
  -webkit-backdrop-filter: blur(38px);
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.4);
  /* Float up through opacity, not a slide — small 20px offset paired
     with a fade, same premium ease-out-quint curve as the hero load
     sequence. Opacity 0 (not distance) is what actually conceals the
     card while [hidden] is still pending, so the offset can stay
     subtle instead of pushing the card fully off-screen. */
  opacity: 0;
  transform: translate(-50%, 20px);
  transition: opacity 650ms cubic-bezier(0.22, 1, 0.36, 1),
              transform 650ms cubic-bezier(0.22, 1, 0.36, 1);
}

.cookie-consent.is-open {
  opacity: 1;
  transform: translate(-50%, 0);
}

.cookie-consent[hidden] {
  display: none !important;
}

.cookie-consent-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 32px;
  padding: 14px 24px;
}

/* US «Your Privacy Choices» контрол (opt-out): ТОЛЬКО когда баннер открыт как
   контрол выбора (класс .is-prefs добавляет main.js), включаем перенос строки,
   чтобы статус-строка встала на свою строку. Дефолтный баннер (EU opt-in и
   первый показ) сюда не попадает — его раскладка не меняется. */
.cookie-consent.is-prefs .cookie-consent-inner {
  flex-wrap: wrap;
  row-gap: 12px;
}

/* Статус текущего выбора — показывается ТОЛЬКО в US-контроле opt-out
   (в дефолтном EU opt-in баннере элемент [hidden] → display:none). */
.cookie-consent-status {
  flex-basis: 100%;
  margin: 0;
  font-family: var(--font-sans);
  font-size: 13px;
  line-height: 1.45;
  color: var(--text-secondary, #b7b1a4);
}

.cookie-consent-text {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 14px;
  color: var(--text-secondary);
  line-height: 1.4;
}

.cookie-consent-link {
  color: var(--gold-primary);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.cookie-consent-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-shrink: 0;
}

.cookie-consent-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 34px;
  padding: 0 16px;
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
}

.cookie-consent-btn-ghost {
  background: none;
  color: var(--button-ghost-text);
  border: 1px solid var(--button-ghost-border);
}

.cookie-consent-btn-ghost:hover {
  color: var(--button-ghost-hover-text);
  border-color: var(--button-ghost-hover-border);
}

.cookie-consent-btn-primary {
  /* Cream fill, not gold — gold is reserved for the page's one
     primary action ("Create My Pet Portrait"). This still reads as
     bright/clickable via contrast against the dark glass bar, without
     competing with that button for "the" accent color. */
  background: var(--text-primary);
  color: var(--bg-base);
  border: 1px solid var(--text-primary);
}

.cookie-consent-btn-primary:hover {
  opacity: 0.88;
}

@media (max-width: 680px) {
  .cookie-consent-inner {
    flex-direction: column;
    align-items: center;
    gap: 12px;
    padding: 14px;
  }

  .cookie-consent-text {
    text-align: center;
  }

  .cookie-consent-actions {
    width: 100%;
    justify-content: center;
  }

  .cookie-consent-btn {
    flex: 1;
    max-width: 180px;
  }
}
