/* Pixaverse /v2 catalog world — dark, premium, motion-friendly.
   Scoped to the pv- classes used by the new React frontend. */

:root {
  --pv-bg: #07060b;
  --pv-fg: #f4efe6;
  --pv-muted: #b9b1a6;
  --pv-accent: #e8b15a;
  --pv-card-radius: 14px;
  --pv-gap: 18px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--pv-bg) !important;
  color: var(--pv-fg) !important;
  font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

/* ---------- Hero (floating gallery wall) ---------- */
.pv-hero {
  position: relative;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background:
    radial-gradient(1200px 800px at 50% 30%, rgba(232,177,90,0.10), transparent 60%),
    var(--pv-bg);
}
.pv-hero__stage {
  position: absolute;
  inset: 0;
  z-index: 0;
  perspective: 1200px;
}
.pv-float {
  position: absolute;
  transform: translate(-50%, -50%);
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.55);
  display: block;
  will-change: transform;
}
.pv-float img {
  display: block;
  width: 100%;
  aspect-ratio: 5 / 7;
  object-fit: cover;
}
.pv-hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  background:
    radial-gradient(40% 32% at 50% 52%, rgba(7, 6, 11, 0.72), rgba(7, 6, 11, 0.28) 60%, transparent 85%),
    linear-gradient(180deg, transparent 72%, var(--pv-bg) 100%);
}
.pv-hero__inner {
  position: relative;
  z-index: 6;
  text-align: center;
  max-width: 720px;
  padding: 0 1.5rem;
}
@keyframes pvRise {
  from { opacity: 0; transform: translateY(20px); }
  to { opacity: 1; transform: translateY(0); }
}
.pv-rise {
  opacity: 0;
  animation: pvRise 0.8s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
@media (prefers-reduced-motion: reduce) {
  .pv-rise { animation: none; opacity: 1; }
}

.pv-hero__eyebrow {
  letter-spacing: 0.22em;
  font-size: 0.78rem;
  color: var(--pv-accent);
  margin: 0 0 1rem;
  font-weight: 600;
}
.pv-hero__title {
  /* Stepped rem sizes at fixed breakpoints, not vw-based: vw ties size to the
     CSS viewport width, which shrinks at high browser zoom, fighting the zoom
     magnification and producing unpredictable results across zoom levels.
     Plain breakpoints make the size predictable at any zoom. */
  font-size: 3rem;
  line-height: 1.04;
  font-weight: 800;
  margin: 0 0 0.8rem;
  color: var(--pv-fg) !important;
  text-shadow: 0 4px 40px rgba(0, 0, 0, 0.85);
}
.pv-hero__sub {
  font-size: 1.1rem;
  color: var(--pv-muted);
  margin: 0 0 1.8rem;
  max-width: 46ch;
  text-shadow: 0 2px 20px rgba(0, 0, 0, 0.7);
}

/* Scroll-storytelling ambient background (fixed, behind content). */
.pv-scrollbg {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(1200px 900px at 50% 28%, hsla(268, 50%, 16%, 0.4), hsla(268, 50%, 16%, 0.22) 40%, hsla(268, 40%, 12%, 0.08) 65%, transparent 85%), var(--pv-bg);
}
/* ---------- Featured hero (cinematic bestseller) ---------- */
.pv-fhero {
  position: relative;
  min-height: 90vh;
  display: flex;
  align-items: center;
  /* clip instead of hidden: same visual clipping, avoids the GPU stacking-
     context that causes horizontal tile artifacts during GSAP pin. */
  overflow: clip;
  background: transparent;
  isolation: isolate;
}
/* Ambient atmosphere made of the actual rotating print, not a generic
   gradient — a heavily blurred macro of the bestseller's own image, opacity
   set via Framer Motion (FeaturedHero.tsx) for the crossfade on rotation.
   Split into two layers so the autonomous CSS drift (this wrapper) and the
   cursor-parallax (the inner .pv-fhero__bgart, set inline from React state)
   each own the `transform` property on their own element instead of
   fighting over one. The scaled-up inset gives the pan room to move
   without ever showing a hard edge. */
.pv-fhero__bgart-layer {
  position: absolute;
  inset: -25%;
  z-index: 0;
  pointer-events: none;
  /* Blur lives on the CHILD (.pv-fhero__bgart), NOT here. Applying filter on
     a parent while a child has an active transform (cursor parallax via
     translate3d) forces the GPU to tile-render the filtered layer separately,
     producing visible horizontal seam lines during scroll. Keeping blur on the
     same element as the transform collapses both into a single compositing
     pass and eliminates the artifact. */
  will-change: opacity;
}
/* pvHeroArtDrift intentionally NOT applied to .pv-fhero__bgart-layer:
   CSS transform animation on a blur-filtered element inside a GSAP-pinned
   (position:fixed) parent forces GPU to tile-render the layer separately,
   producing visible horizontal seam lines during scroll. The Framer Motion
   crossfade + cursor parallax provide enough ambient motion without artifacts.
   The keyframe stays because .pv-worldroot__bgart still uses it (no GSAP pin). */
@keyframes pvHeroArtDrift {
  0% { transform: scale(1.06) translate3d(-1.5%, -1%, 0); }
  100% { transform: scale(1.13) translate3d(1.5%, 1%, 0); }
}
.pv-fhero__bgart {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  /* Blur on the same element as the transform — single GPU compositing pass,
     no tile-seam artifacts. 72px turns any image into a pure colour wash. */
  filter: blur(72px) saturate(0.9);
  /* Smooths the parallax follow — React state updates on every pointermove,
     this keeps the layer gliding instead of snapping frame to frame. */
  transition: transform 0.4s ease-out;
}
@media (max-width: 640px) {
  /* Below ~1024px the hero's single stacked column is much taller than the
     viewport (up to ~1300px), and this layer's -25% inset scales that up
     further (~1950px total). A blur(72px) filter on an element that tall
     hits a Chromium GPU compositing threshold and tile-splits the layer,
     rendering a visible horizontal seam mid-blur — confirmed live on a
     375x812 viewport, a plain horizontal line cutting across the hero
     headline. Shrinking both the overscan and the blur radius keeps the
     same ambient "ombre wash" read at a much smaller total pixel area,
     staying under the threshold instead of losing the effect outright. */
  .pv-fhero__bgart-layer { inset: -8%; }
  .pv-fhero__bgart { filter: blur(44px) saturate(0.9); }
}
.pv-fhero__inner {
  position: relative;
  z-index: 2;
  max-width: 1420px;
  margin: 0 auto;
  width: 100%;
  padding: 5rem 3rem;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-areas: "copy" "visual" "dots";
  gap: 4rem;
  align-items: start;
}
@media (min-width: 1024px) {
  /* Equal columns — image and copy should read as the same visual weight,
     not one dominating the other. */
  .pv-fhero__inner {
    grid-template-columns: 1fr 1fr;
    grid-template-areas: "copy visual" "dots dots";
  }
}
.pv-fhero__copy {
  grid-area: copy;
  max-width: 42rem;
  padding-top: 1.5rem;
  will-change: transform, opacity;
}
.pv-fhero__feature {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  flex-wrap: wrap;
  margin: 0 0 1.6rem;
}
.pv-badge {
  background: var(--pv-accent);
  color: #1a1206;
  font-weight: 700;
  font-size: 0.78rem;
  letter-spacing: 0.03em;
  padding: 0.35rem 0.7rem;
  border-radius: 999px;
}
.pv-fhero__feature-title {
  font-weight: 600;
  color: var(--pv-fg);
}
.pv-fhero__feature-sold {
  color: var(--pv-muted);
  font-size: 0.92rem;
}
.pv-fhero__actions {
  display: flex;
  gap: 0.8rem;
  flex-wrap: wrap;
  margin-top: 0.4rem;
}
.pv-fhero__dots {
  grid-area: dots;
  display: flex;
  gap: 0.4rem;
  margin-top: 0.5rem;
  width: 100%;
  max-width: 28rem;
  justify-self: center;
}
/* Displate-style progress segments: one per featured print, filling over the
   rotation interval so the current position is always visible and clicking
   any segment jumps straight to that print and restarts its timer. */
.pv-segment {
  flex: 1;
  height: 3px;
  border: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.18);
  cursor: pointer;
  padding: 0;
  overflow: hidden;
  position: relative;
}
.pv-segment__fill {
  display: block;
  height: 100%;
  width: 0%;
  background: var(--pv-accent);
  border-radius: inherit;
}
.pv-segment__fill.is-done {
  width: 100%;
}
.pv-segment__fill.is-active {
  animation: pvSegmentFill linear forwards;
}
@keyframes pvSegmentFill {
  from { width: 0%; }
  to { width: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .pv-segment__fill.is-active {
    animation: none;
    width: 100%;
  }
}
/* Scroll cue, bottom-center of the hero — fades out once the visitor starts
   scrolling, so it never lingers and competes with the content below. */
.pv-fhero__scrollhint {
  position: absolute;
  left: 50%;
  bottom: 2rem;
  transform: translateX(-50%);
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
  opacity: 0.7;
  transition: opacity 0.4s ease;
  pointer-events: none;
}
.pv-fhero__scrollhint.is-hidden {
  opacity: 0;
}
.pv-fhero__scrollhint-arrow {
  width: 22px;
  height: 22px;
  color: var(--pv-accent);
  animation: pvScrollArrow 1.8s ease-in-out infinite;
}
@keyframes pvScrollArrow {
  0%, 100% { transform: translateY(0); opacity: 0.7; }
  50% { transform: translateY(8px); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .pv-fhero__scrollhint-arrow { animation: none; }
}
.pv-fhero__visual {
  grid-area: visual;
  display: flex;
  justify-content: center;
  align-items: center;
  position: relative;
  will-change: transform, opacity;
}
@media (min-width: 1024px) {
  /* Locks to the copy column's measured pixel height (set as a CSS var by
     FeaturedHero.tsx via ResizeObserver) only on the two-column layout.
     .pv-fhero__copy has 1.5rem of padding-top before its visible text
     starts, but no trailing padding (its last element's bottom sits flush
     on the box's own bottom edge). Matching the box's full height edge-to-
     edge made the print start above where the headline visually starts,
     while still ending level with the buttons — reading as floating too
     high. Push the image down by that same 1.5rem and shrink its height by
     it, so the print's top lines up with the headline's top and its bottom
     still lands exactly on the buttons row. */
  .pv-fhero__visual {
    height: calc(var(--pv-copy-h, 0px) - 1.5rem);
    margin-top: 1.5rem;
  }
}
/* Glow anchored to the print itself, so it never drifts with viewport/zoom.
   .pv-fhero__visual gets an explicit pixel height set inline by JS (measured
   live off the copy column via ResizeObserver, see FeaturedHero.tsx).
   The stage fills that fixed height edge-to-edge with no aspect-ratio of its
   own (avoids fighting max-width against an explicit height — that combo
   lets the UA discard the height and recompute it from the clamped width,
   which is what caused the print to render taller than the copy column).
   The 5:7 shape is derived once, on the innermost .pv3d/img, purely from
   height — nothing upstream constrains their width, so there's nothing for
   the ratio to conflict with. Targets .pv3d__stage directly (what Print3D
   actually renders) scoped to the hero — the standalone .pv-fhero__stage
   class was never applied to any element, so all earlier rules under that
   name had no effect. */
.pv-fhero__visual .pv3d__stage {
  perspective: 1200px;
  height: 100%;
  width: 100%;
  margin: 0;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}
.pv-fhero__visual .pv3d__stage .pv3d {
  height: 100%;
  width: auto;
}
.pv-fhero__visual .pv3d__stage .pv3d img {
  height: 100%;
  width: auto;
  aspect-ratio: 5 / 7;
}
.pv-fhero__visual .pv3d__stage::before {
  content: "";
  position: absolute;
  inset: -22% -22% -22% -22%;
  z-index: 0;
  background:
    radial-gradient(closest-side, rgba(232, 177, 90, 0.22), transparent 72%),
    radial-gradient(closest-side, rgba(60, 110, 150, 0.22), transparent 80%);
  filter: blur(8px);
  pointer-events: none;
}
/* Section afterglow — GSAP animates --pv-rail-glow (0→1→0) during the
   scroll-driven art-to-card transition. The ::before pseudo-element keeps
   z-index: -1 so it stays behind cards but lights up the section background.
   isolation: isolate on the section keeps the stacking context contained. */
.pv-rail-section {
  position: relative;
  isolation: isolate;
  --pv-rail-glow: 0;
}
.pv-rail-section::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: -1;
  /* Sized/positioned so the gradient reaches its own transparent stop
     (68% of its radius) well inside the box on every side, at any box
     size — 30%/50% position with a 40%/65% radius fades out by ~2.8% from
     the left edge and ~5.8%/~94.2% top/bottom, all comfortably inside
     inset:0's own bounds. No transform/scale bleed trick needed: an
     earlier version used transform:scale(1.6) to give the (much larger,
     more off-center) old ellipse room to fade before the box edge, but on
     very wide screens (.pv-section gets max-width:1800px past 2000px
     viewports) that showed a hard-edged cutoff anyway — confirmed live
     with ScrollBackground hidden too, so it was this element specifically:
     scaling a gradient far enough past its own layout box apparently hits
     a real paint/rasterization limit in the browser, not just a math
     mistake. Fading out inside the box's own bounds sidesteps that
     entirely rather than trying to outrun it with a bigger bleed radius. */
  background: radial-gradient(
    ellipse 40% 65% at 30% 50%,
    rgba(232, 177, 90, calc(var(--pv-rail-glow) * 0.26)) 0%,
    rgba(232, 177, 90, calc(var(--pv-rail-glow) * 0.08)) 42%,
    transparent 68%
  );
}
.pv-print {
  position: relative;
  z-index: 1;
  border-radius: 10px;
  overflow: hidden;
  transform-style: preserve-3d;
  box-shadow:
    0 40px 90px rgba(0, 0, 0, 0.6),
    0 8px 24px rgba(0, 0, 0, 0.5);
  will-change: transform;
}
.pv-print img {
  display: block;
  width: 100%;
  aspect-ratio: 5 / 7;
  object-fit: cover;
}
.pv-print__edge {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.18), inset 0 0 40px rgba(0, 0, 0, 0.35);
}
.pv-print__sheen {
  position: absolute;
  inset: 0;
  pointer-events: none;
  mix-blend-mode: screen;
  border-radius: inherit;
}
.pv-btn--ghost {
  background: transparent;
  color: var(--pv-fg);
  border: 1px solid rgba(255, 255, 255, 0.22);
}
.pv-btn--ghost:hover {
  border-color: var(--pv-accent);
  transform: translateY(-2px);
}
@media (max-width: 860px) {
  .pv-fhero__inner {
    grid-template-columns: 1fr;
    /* Dots moved up to sit right under the image (was "visual copy dots" —
       by the time they appeared after the whole copy block, title, badge,
       and both CTA buttons, they read as unrelated to the image they
       actually control). Grid areas can render in any order regardless of
       source order, so this is a pure CSS reorder — no DOM/JSX change. */
    grid-template-areas: "visual" "dots" "copy";
    text-align: center;
    padding: 5rem 1.5rem;
    /* The 4rem (64px) row gap makes sense at desktop width (spacing the
       image column from the copy column), but grid gap applies between
       EVERY stacked row here too — with dots newly sitting between visual
       and copy, that meant 64px of grid gap PLUS the dots' own margins on
       both sides, ~93px of dead space between the dots and the title.
       Tightened just for this stacked layout. */
    gap: 1.25rem;
  }
  .pv-fhero__copy {
    max-width: none;
  }
  .pv-fhero__visual .pv3d__stage {
    height: auto;
    width: min(420px, 92%);
  }
  .pv-fhero__visual .pv3d__stage .pv3d {
    height: auto;
    width: 100%;
  }
  .pv-fhero__visual .pv3d__stage .pv3d img {
    height: auto;
    width: 100%;
  }
  .pv-fhero__feature,
  .pv-fhero__actions,
  .pv-fhero__dots {
    justify-content: center;
  }
  .pv-fhero__dots {
    margin: 1.1rem auto 1.8rem;
    /* Up to 18 dots (see FeaturedHero.tsx) at a real tap target need a
       tighter gap than the desktop bar-segments to still fit one row on
       a 375px screen — confirmed live that 16px dots + 0.15rem gap were
       ~1px over budget and silently wrapped to 2 rows, so both the dot
       size (16px → 15px) and gap came down slightly for real margin, not
       a hairline fit. flex-wrap stays as a safety net regardless. */
    gap: 0.12rem;
    row-gap: 0.5rem;
    flex-wrap: wrap;
  }
  .pv-fhero__scrollhint {
    display: none;
  }
  /* On narrow screens, progress bars look like grid lines (dashes).
     Convert to circular dots for a cleaner, unambiguous indicator. The
     visible dot stays small, but the BUTTON (the actual tap target) is
     roughly 2x bigger via centered padding — at 8x8px the whole clickable
     area was smaller than a fingertip and hard to hit precisely. */
  .pv-segment {
    flex: 0 0 15px;
    width: 15px;
    height: 15px;
    border-radius: 50%;
    background: none;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .pv-segment__fill {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.28);
    transition: background 0.25s ease, width 0.25s ease, height 0.25s ease, box-shadow 0.25s ease;
  }
  /* A linear width-wipe (the desktop bar-segment progress cue) reads as a
     lopsided half-filled circle at 6px, not a clean "filling up" — swap
     it on mobile for a simple, legible state change instead: brighter +
     slightly larger for the active dot, filled but dim once done. */
  .pv-segment__fill.is-active {
    width: 8px;
    height: 8px;
    background: var(--pv-accent);
    box-shadow: 0 0 8px rgba(232, 177, 90, 0.55);
    animation: none;
  }
  .pv-segment__fill.is-done {
    width: 6px;
    background: var(--pv-accent);
    opacity: 0.5;
  }
}

/* ---------- Grabbable 3D print (Textra-style) ---------- */
.pv3d__stage {
  perspective: 1300px;
  width: min(460px, 92%);
  margin: 0 auto;
}
.pv3d {
  position: relative;
  transform-style: preserve-3d;
  border-radius: 10px;
  overflow: hidden;
  cursor: pointer;
  transition: transform 0.18s ease-out;
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.12),
    inset 0 -1px 0 rgba(0, 0, 0, 0.32),
    0 40px 90px rgba(0, 0, 0, 0.6),
    0 8px 24px rgba(0, 0, 0, 0.5);
}
.pv3d img,
.pv3d__canvas {
  display: block;
  width: 100%;
  aspect-ratio: 5 / 7;
  object-fit: cover;
  user-select: none;
  -webkit-user-drag: none;
}
/* Subtle fixed-source reflection: invisible at rest, stronger as the plate tilts. */
.pv3d__spot {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  mix-blend-mode: screen;
  transition: opacity 0.16s ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .pv3d__spot {
    display: none;
  }
}
.pv3d__relief {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.32),
    inset 0 3px 30px rgba(255, 255, 255, 0.16),
    inset 0 -22px 70px rgba(0, 0, 0, 0.62),
    inset 0 14px 50px rgba(0, 0, 0, 0.3);
}
.pv-card .pv3d__stage {
  width: 100%;
}
.pv-card .pv3d {
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.12),
    inset 0 -1px 0 rgba(0, 0, 0, 0.32),
    0 8px 30px rgba(0, 0, 0, 0.45);
}

/* ---------- Metal tilt (cards) ---------- */
.pv-tilt {
  position: relative;
  transform-style: preserve-3d;
  will-change: transform;
}
.pv-card__plate {
  position: relative;
  border-radius: var(--pv-card-radius);
  overflow: hidden;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.45);
}
.pv-card__plate::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12);
}
.pv-tilt__sheen {
  position: absolute;
  inset: 0;
  pointer-events: none;
  mix-blend-mode: screen;
  opacity: 0;
  transition: opacity 0.25s ease;
  border-radius: var(--pv-card-radius);
}
.pv-tilt:hover .pv-tilt__sheen {
  opacity: 1;
}

/* ---------- Sections ---------- */
.pv-section {
  max-width: 1280px;
  margin: 0 auto;
  padding: 4.5rem 1.5rem;
}
.pv-section__head {
  margin-bottom: 2rem;
}
.pv-section__title {
  font-size: clamp(1.6rem, 3vw, 2.4rem);
  font-weight: 800;
  margin: 0 0 0.3rem;
  color: var(--pv-fg) !important;
}
.pv-section__title--link {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font-family: inherit;
  /* Browsers default <button> text to text-align:center — invisible for a
     short title that fits on one line (a snug single line looks the same
     either way), but on a longer collection name that wraps to two lines,
     it visibly centers instead of matching every other left-aligned
     heading on the page (h2's text-align is inherited normally, but a
     <button>'s isn't). */
  text-align: left;
  display: inline-flex;
  align-items: baseline;
  gap: 0.4rem;
  transition: color 0.15s ease;
}
.pv-section__title--link span {
  color: var(--pv-accent);
  font-size: 0.7em;
  opacity: 0;
  transition: opacity 0.15s ease, transform 0.15s ease;
  transform: translateX(-4px);
}
.pv-section__title--link:hover {
  color: var(--pv-accent) !important;
}
.pv-section__title--link:hover span {
  opacity: 1;
  transform: translateX(0);
}
/* New In's signature entrance — a one-shot light sweep bound to the actual
   glyph shapes (background-clip: text), not a floating bar beside them.
   display:inline-block shrinks the box to the text's own width, so the
   gradient/animation tracks the word instead of the full-width <h2>
   column. Played once by adding .is-active (see ArtworkRail.tsx) the first
   time the rail scrolls into view. Never replays, never loops. */
.pv-section__title--shimmer {
  display: inline-block;
  background-image: linear-gradient(
    100deg,
    var(--pv-fg) 42%,
    #fff6e0 50%,
    var(--pv-fg) 58%
  );
  background-size: 250% 100%;
  background-position: 180% 0;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent !important;
}
.pv-section__title--shimmer.is-active {
  animation: pvTitleShimmer 1.3s cubic-bezier(0.22, 1, 0.36, 1) 1;
}
@keyframes pvTitleShimmer {
  to {
    background-position: -70% 0;
  }
}
.pv-section__sub {
  color: var(--pv-muted);
  margin: 0;
}
.pv-section--alt { background: rgba(255, 255, 255, 0.02); }
.pv-btn--secondary {
  background: transparent;
  color: var(--pv-muted);
  border: 1px solid rgba(255, 255, 255, 0.14);
  cursor: not-allowed;
}

/* ---------- Collections ---------- */
.pv-collections {
  max-width: 1280px;
  margin: 0 auto;
  padding: 2rem 1.5rem 0;
}
.pv-collections__head {
  text-align: center;
  margin-bottom: 1rem;
}
/* Back-to-back collection rails don't need a full section's worth of
   breathing room between each other — that's what made the gap feel
   bloated. Bestsellers (outside .pv-collections) keeps the spacious
   padding since it stands alone. */
.pv-collections .pv-rail-section {
  padding: 1.6rem 1.5rem;
}
.pv-collections .pv-rail-section .pv-section__head {
  margin-bottom: 1.1rem;
}

/* ---------- Bestseller rail ---------- */
.pv-rail__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
}
.pv-rail__nav {
  display: flex;
  gap: 0.5rem;
}
.pv-rail__arrow {
  width: 42px;
  height: 42px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(255, 255, 255, 0.04);
  color: var(--pv-fg);
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
  transition: border-color 0.2s ease, background 0.2s ease;
}
.pv-rail__arrow:hover {
  border-color: var(--pv-accent);
  background: rgba(255, 255, 255, 0.08);
}
@media (max-width: 640px) {
  /* Arrows are back on mobile (see .pv-rail touch-action below for why) —
     they're now the RELIABLE way to move a rail on a phone, not a
     redundant desktop leftover. `align-items: flex-end` used to align
     them against whichever line of the title happened to be LAST, which
     floated oddly next to just the final line of a wrapped multi-word
     collection name; flex-start keeps them pinned to the top of the
     title block regardless of how many lines it wraps to. */
  .pv-rail__head { align-items: flex-start; }
  .pv-rail__arrow { width: 36px; height: 36px; font-size: 1.1rem; }
  /* The 24px side padding a rail section uses at desktop width eats
     visibly into how much artwork actually shows — reclaimed almost all
     of it on mobile so cards run close to the screen edge (rather than
     shrinking the card itself, which has its own sizing quirks — see
     .pv-rail__item below). Targets both the plain Bestseller rail and
     the higher-specificity `.pv-collections .pv-rail-section` (which
     otherwise wins on padding-left/right too, since its shorthand sets
     all four sides). `.pv-collections` itself ALSO carries its own 24px
     side padding (it wraps several stacked rail sections) — that's
     separate from and additive with .pv-rail-section's own padding, so
     Collections rails were visibly narrower than Bestsellers (which
     isn't wrapped in .pv-collections) even after the above fix. Zeroed
     out here too so the inner rail section's padding is the only source
     of horizontal inset, matching Bestsellers exactly. */
  .pv-collections {
    padding-left: 0;
    padding-right: 0;
  }
  /* .pv-collections__head (the "Collections" heading + subtitle) is also
     a direct child, so it loses its inset along with the rail sections
     above — restore it here since that text isn't part of the
     edge-to-edge rail treatment. */
  .pv-collections__head {
    padding-left: 0.5rem;
    padding-right: 0.5rem;
  }
  .pv-rail-section,
  .pv-collections .pv-rail-section {
    padding-left: 0.5rem;
    padding-right: 0.5rem;
  }
}
.pv-rail {
  display: flex;
  /* Flex's default align-items is stretch: every item in the row gets
     stretched to match the tallest one. Items far down a long rail (still
     off-screen horizontally, never scrolled into view) can briefly render
     wider than their fixed 240px flex-basis — a real, reproducible
     off-screen flex-item sizing quirk — which makes their 5:7-ratio card
     taller too, and stretch then propagates that into every visible card in
     the same row. Cards already carry their own fixed aspect-ratio; nothing
     needs to stretch to a sibling's height. */
  align-items: flex-start;
  gap: var(--pv-gap);
  padding-bottom: 0.5rem;
  cursor: grab;
}
.pv-rail:active {
  cursor: grabbing;
}
/* Embla's "viewport" — the element its ref attaches to. Embla drives
   scrolling internally via a transform on .pv-rail (its "container"), not
   native overflow scrollLeft, so this only needs to clip.
   `touch-action: pan-y` keeps a vertical swipe starting on a card an
   unconditional native page-scroll no matter what: this used to be a
   custom pointer/touch drag handler here, rebuilt from scratch three
   times, each "verified" only via synthetic event dispatch — which never
   exercises the browser's real touch-action enforcement, so none of those
   fixes were ever actually provable. Same bug reproduced identically
   across three different rendering engines, ruling out an engine-specific
   quirk. Switched to Embla Carousel (client/src/sections/ArtworkRail.tsx)
   — a purpose-built, years-battle-tested library for exactly this
   "carousel nested in a scrollable page" problem — instead of a fourth
   from-scratch attempt; it owns drag-axis detection, momentum, and
   click-vs-drag disambiguation, this touch-action declaration is the one
   piece still worth stating explicitly as a hard backstop. */
.pv-rail-viewport {
  overflow: hidden;
  touch-action: pan-y;
}
.pv-rail__item {
  flex: 0 0 240px;
  /* flex-basis alone isn't holding: items far down a long rail (still
     off-screen horizontally) have been observed rendering wider than 240px
     despite flex-shrink:0 — a genuine browser quirk for off-screen flex
     children in this row, confirmed live (one measured 350.75px). A wider
     box also means a taller 5:7-ratio card, which inflates the whole
     rail's height and produces uneven gaps between collection sections.
     Pin width/max-width explicitly so nothing can recompute it. */
  width: 240px;
  max-width: 240px;
  user-select: none;
}

/* ---------- Buttons ---------- */
.pv-btn {
  display: inline-block;
  padding: 0.85rem 1.6rem;
  border-radius: 10px;
  font-weight: 600;
  text-decoration: none;
  text-align: center;
  cursor: pointer;
  border: 1px solid transparent;
  transition: transform 0.2s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.2s ease, background 0.2s ease;
}
.pv-btn--primary {
  background: var(--pv-accent);
  color: #1a1206;
  box-shadow: 0 0 0 0 rgba(232, 177, 90, 0);
}
.pv-btn--primary:hover {
  transform: translateY(-2px) scale(1.02);
  box-shadow: 0 8px 24px rgba(232, 177, 90, 0.35);
}
.pv-btn--primary:active {
  transform: translateY(0) scale(0.98);
  box-shadow: 0 4px 12px rgba(232, 177, 90, 0.25);
}

/* ---------- Gallery ---------- */
.pv-gallery {
  max-width: 1280px;
  margin: 0 auto;
  padding: 4rem 1.5rem 6rem;
}
.pv-filters {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 2rem;
}
.pv-filters input,
.pv-filters select {
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.12);
  color: var(--pv-fg);
  border-radius: 10px;
  padding: 0.7rem 1rem;
  font-size: 0.95rem;
  min-width: 200px;
}
.pv-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: var(--pv-gap);
}
/* Below ~508px available width (viewport minus 2x .pv-section's 1.5rem
   padding), a 220px minimum only ever fits a single column — the full
   archive read as one oversized card per row with no sense of "browsing a
   grid". Two narrower columns fit comfortably down to phone-width screens
   and match how a dense image archive is expected to read on mobile. */
@media (max-width: 560px) {
  .pv-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 12px;
  }
}
.pv-card {
  display: block;
  text-decoration: none;
  color: inherit;
  /* /www/styles.css (the legacy v1 site, still loaded on this page) has its
     own unrelated .pv-card rule with content-visibility:auto and a guessed
     contain-intrinsic-size of 360x520 — a class-name collision. Chrome uses
     that guessed size for any card scrolled off-screen in a rail until it's
     actually brought into view, which is the real cause of the rail-height
     inconsistency between collections: confirmed live, off-screen cards
     were measuring ~360px wide / ~506-521px tall, the exact placeholder
     size, instead of their real ~233x384. Override back to normal so v2
     cards always measure their real size. */
  content-visibility: visible;
  contain-intrinsic-size: auto;
}
.pv-card img {
  width: 100%;
  aspect-ratio: 5 / 7;
  object-fit: cover;
  background: rgba(255, 255, 255, 0.04);
  display: block;
}
.pv-card__meta {
  padding: 0.75rem 0.4rem 0.1rem;
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--pv-fg);
  /* Fixed, not min-height: a min-height is only a floor, so a title that
     genuinely wraps to 2 lines could still render a few px taller than a
     1-line title clamped to the same space, leaving cards (and therefore
     whole rail rows) at slightly different heights across collections. The
     line-clamp below already caps text at 2 lines, so a fixed height is
     always enough and keeps every card identical.
     `2.7em` alone was exactly the 2-line TEXT height (2 x 1.35 line-height
     at this element's own 0.9rem font-size) with nothing left over for this
     same element's own 0.85rem of top+bottom padding — the padding ate
     into the fixed box from the inside, silently clipping roughly the
     bottom half of every second line site-wide (bestseller rail,
     collections, full archive — confirmed live, not a mobile-only bug).
     Add the padding back into the height budget instead of overlapping it. */
  height: calc(2.7em + 0.85rem);
  overflow: hidden;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  text-align: center;
}
.pv-card__meta span {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: 1.35;
}

/* "Load more" */
.pv-gallery > .pv-btn {
  display: block;
  margin: 3rem auto 0;
  width: max-content;
}

/* Empty / loading state */
.pv-empty {
  text-align: center;
  color: var(--pv-muted);
  padding: 4rem 1rem;
}

/* ---------- Artwork lightbox modal (blur backdrop) ---------- */
.pvlb {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem;
  background: rgba(4, 3, 8, 0.94);
  opacity: 1;
}
/* Transform-only rise: even if the animation is skipped, the panel stays visible. */
@keyframes pvModalRise {
  from { transform: translateY(26px) scale(0.97); }
  to { transform: translateY(0) scale(1); }
}
.pvlb__panel {
  position: relative;
  animation: pvModalRise 0.45s cubic-bezier(0.22, 1, 0.36, 1) both;
  /* Flex, not grid-with-centered-columns: the print needs a *definite*
     height to fill (grid's align-items:center just centers it at its own
     intrinsic size, which is how a flat 420px stage cap went unnoticed —
     widening the panel left empty space around a still-tiny image instead
     of making the print itself bigger). The panel now has its own fixed
     height; the visual column fills it edge to edge and derives its width
     from the print's 5:7 ratio, so the print is the dominant element this
     modal opens to show, not a small thumbnail next to a wall of text. */
  display: flex;
  align-items: stretch;
  height: min(86vh, 900px);
  max-width: min(1400px, 92vw);
  background: rgba(18, 16, 24, 0.94);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 40px 120px rgba(0, 0, 0, 0.7);
}
.pvlb__close {
  position: absolute;
  top: 1.1rem;
  right: 1.1rem;
  z-index: 2;
  width: 40px;
  height: 40px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(4, 3, 8, 0.55);
  backdrop-filter: blur(6px);
  color: var(--pv-fg);
  cursor: pointer;
  font-size: 1rem;
  transition: border-color 0.2s ease, transform 0.2s ease;
}
.pvlb__close:hover {
  border-color: var(--pv-accent);
  transform: scale(1.06);
}
.pvlb__visual {
  position: relative;
  flex-shrink: 0;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    radial-gradient(120% 90% at 30% 20%, rgba(232, 177, 90, 0.10), transparent 60%),
    rgba(0, 0, 0, 0.35);
}
.pvlb__visual .pv3d__stage {
  height: 100%;
  width: auto;
  margin: 0;
  perspective: 1300px;
}
.pvlb__visual .pv3d__stage .pv3d,
.pvlb__visual .pv3d__stage img {
  height: 100%;
  width: auto;
}
/* The panel clips the outer shape itself (overflow:hidden, its own 20px
   radius) and there's no gap between the print and the panel's left/top/
   bottom edges — the print's own rounding and drop shadow would just
   double up against that clip, reading muddy right at the seam. */
.pvlb__visual .pv3d__stage .pv3d {
  border-radius: 0;
  box-shadow: none;
}
.pvlb__info {
  flex: 1;
  min-width: 0;
  height: 100%;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0;
  padding: 3rem 3.25rem;
}
.pvlb__eyebrow {
  letter-spacing: 0.16em;
  text-transform: uppercase;
  font-size: 0.74rem;
  color: var(--pv-accent);
  margin: 0 0 0.6rem;
}
.pvlb__title {
  font-size: clamp(1.7rem, 2.6vw, 2.6rem);
  line-height: 1.08;
  font-weight: 800;
  margin: 0 0 1rem;
  color: var(--pv-fg) !important;
}
.pvlb__trust {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  flex-wrap: wrap;
  color: var(--pv-muted);
  font-size: 0.9rem;
  margin: 0 0 1.3rem;
}
.pvlb__desc {
  color: var(--pv-muted);
  line-height: 1.65;
  margin: 0 0 1.4rem;
}
.pvlb__tags {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin: 0 0 1.8rem;
}
.pv-chip {
  font-size: 0.78rem;
  color: var(--pv-muted);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 999px;
  padding: 0.3rem 0.7rem;
}
.pvlb__cta {
  display: inline-block;
  /* The clear single action this whole panel exists to drive — give it
     more weight than a default .pv-btn now that it's the anchor of a much
     larger panel, not a cramped one. */
  padding: 1.05rem 1.8rem;
  font-size: 1.05rem;
}
.pvlb__brandproof {
  display: grid;
  gap: 0.35rem;
  margin-top: 1rem;
  padding: 0.85rem 0;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  color: var(--pv-muted);
  font-size: 0.78rem;
  line-height: 1.4;
}
@media (max-width: 820px) {
  .pvlb {
    padding: 1rem;
  }
  .pvlb__panel {
    flex-direction: column;
    height: auto;
    max-height: 92vh;
    overflow-y: auto;
  }
  .pvlb__visual {
    height: auto;
    max-height: 48vh;
    padding: 1.5rem 0;
  }
  .pvlb__visual .pv3d__stage {
    height: auto;
    width: min(320px, 70vw);
  }
  .pvlb__visual .pv3d__stage .pv3d,
  .pvlb__visual .pv3d__stage img {
    height: auto;
    width: 100%;
  }
  .pvlb__info {
    flex: none;
    height: auto;
    overflow-y: visible;
    justify-content: flex-start;
    padding: 0 1.5rem 1.8rem;
  }
}

/* ---------- Static detail page (/artwork/) ---------- */
body:has(.pv-detail) { background: var(--pv-bg); }
.pv-detail {
  margin: 5rem auto 4rem;
  padding: 0 1.5rem;
}
.pv-detail .pv3d {
  position: relative;
  border-radius: 10px;
  overflow: hidden;
  width: min(420px, 100%);
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.6);
}
.pv-detail .pv3d img {
  display: block;
  width: 100%;
  aspect-ratio: 5 / 7;
  object-fit: cover;
}
.pv-detail__back {
  margin-top: 1.4rem;
}
.pv-detail__back a {
  color: var(--pv-muted);
  text-decoration: none;
}
.pv-detail__back a:hover { color: var(--pv-fg); }

/* ===================== Structure (pvx- namespace, no v1 collision) ===================== */

/* Sticky navbar */
.pvx-nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.9rem 2rem;
  transition: background 0.3s ease, backdrop-filter 0.3s ease, border-color 0.3s ease;
  border-bottom: 1px solid transparent;
}
.pvx-nav.is-solid {
  background: rgba(7, 6, 11, 0.96);
  border-bottom-color: rgba(255, 255, 255, 0.08);
}
.pvx-nav__brand {
  font-weight: 800;
  letter-spacing: 0.16em;
  color: var(--pv-fg);
  text-decoration: none;
  font-size: 0.95rem;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}
.pvx-nav__logo {
  width: 26px;
  height: 26px;
  border-radius: 6px;
  object-fit: cover;
}
/* Wraps .pvx-nav__links + .pvx-nav__actions together so the CTA sits
   immediately after the last link on desktop — identical spacing to when
   the CTA was itself the last child of .pvx-nav__links, before it moved
   out into its own element so mobile can hide/show the links list
   independently of the always-visible CTA. */
.pvx-nav__right {
  display: flex;
  align-items: center;
  gap: 1.6rem;
}
.pvx-nav__links {
  display: flex;
  align-items: center;
  gap: 1.6rem;
}
.pvx-nav__links a {
  color: var(--pv-muted);
  text-decoration: none;
  font-size: 0.92rem;
  transition: color 0.2s ease;
}
.pvx-nav__links a:hover {
  color: var(--pv-fg);
}
.pvx-nav__actions {
  display: flex;
  align-items: center;
  gap: 0.9rem;
}
.pvx-nav__cta {
  background: var(--pv-accent);
  color: #1a1206 !important;
  font-weight: 700;
  padding: 0.55rem 1.1rem;
  border-radius: 999px;
}
/* Duplicate CTA inside the mobile dropdown — hidden everywhere except the
   sub-400px band where the top-bar pill itself gets hidden (see below) so
   narrow screens still have a reachable "Shop" action. */
.pvx-nav__links-cta {
  display: none;
}
/* Hamburger toggle — desktop-hidden; a plain 3-bar icon that morphs into
   an X while the mobile dropdown is open. */
.pvx-nav__toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 34px;
  height: 34px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
}
.pvx-nav__toggle span {
  display: block;
  width: 100%;
  height: 2px;
  border-radius: 2px;
  background: var(--pv-fg);
  transition: transform 0.2s ease, opacity 0.2s ease;
}
.pvx-nav__toggle.is-open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.pvx-nav__toggle.is-open span:nth-child(2) { opacity: 0; }
.pvx-nav__toggle.is-open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

@media (max-width: 640px) {
  .pvx-nav { padding: 0.7rem 1.1rem; }
  .pvx-nav__toggle { display: flex; }
  /* The link list becomes a full-width dropdown panel anchored under the
     bar — `display:none` by default (not just visually hidden) so it's
     unreachable by keyboard/screen reader tab order while closed, matching
     the modal's own show/hide pattern elsewhere on the site. */
  .pvx-nav__links {
    display: none;
    position: fixed;
    top: 56px;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    background: rgba(7, 6, 11, 0.98);
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
    padding: 0.5rem 1.1rem 1rem;
    max-height: calc(100vh - 56px);
    overflow-y: auto;
  }
  .pvx-nav__links.is-open {
    display: flex;
  }
  .pvx-nav__links a {
    padding: 0.9rem 0.2rem;
    font-size: 1.05rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  }
  .pvx-nav__cta {
    padding: 0.5rem 0.85rem;
    font-size: 0.85rem;
  }
}

@media (max-width: 400px) {
  /* Brand text + pill CTA + hamburger no longer fit on one row at this
     width — drop the top-bar pill and surface the same action as the last
     row inside the dropdown instead (added to .pvx-nav__links in JSX). */
  .pvx-nav__cta {
    display: none;
  }
  .pvx-nav__links-cta {
    display: block;
    margin-top: 0.6rem;
    text-align: center;
    background: var(--pv-accent);
    color: #1a1206 !important;
    font-weight: 700;
    padding: 0.8rem 1rem;
    border-radius: 999px;
    border-bottom: none !important;
  }
}

/* Social proof band */
.pvx-proof {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 2.5rem 4rem;
  max-width: 1280px;
  margin: 0 auto;
  padding: 2.5rem 1.5rem;
}
.pvx-proof__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
}
.pvx-proof__n {
  font-size: clamp(1.4rem, 2.4vw, 2rem);
  font-weight: 800;
  color: var(--pv-fg);
}
.pvx-proof__l {
  color: var(--pv-muted);
  font-size: 0.85rem;
  letter-spacing: 0.02em;
}

/* FAQ */
.pvx-faq__list {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  max-width: 820px;
}
.pvx-faq__item {
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.02);
  padding: 0 1.2rem;
}
.pvx-faq__item summary {
  cursor: pointer;
  list-style: none;
  padding: 1.1rem 0;
  font-weight: 600;
  color: var(--pv-fg);
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.pvx-faq__item summary::-webkit-details-marker { display: none; }
.pvx-faq__item summary::after {
  content: "+";
  color: var(--pv-accent);
  font-size: 1.3rem;
  line-height: 1;
}
.pvx-faq__item[open] summary::after { content: "–"; }
.pvx-faq__item p {
  margin: 0 0 1.1rem;
  color: var(--pv-muted);
  line-height: 1.6;
  max-width: 70ch;
}

/* Footer */
.pvx-footer {
  margin-top: 4rem;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  background: rgba(0, 0, 0, 0.3);
}
.pvx-footer__inner {
  max-width: 1100px;
  margin: 0 auto;
  padding: 3rem 1.5rem 2rem;
  display: flex;
  justify-content: space-between;
  gap: 2rem;
  flex-wrap: wrap;
}
.pvx-footer__name {
  font-weight: 800;
  letter-spacing: 0.16em;
  color: var(--pv-fg);
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}
.pvx-footer__logo {
  width: 24px;
  height: 24px;
  border-radius: 6px;
  object-fit: cover;
  vertical-align: middle;
}
.pvx-footer__brand p {
  color: var(--pv-muted);
  max-width: 36ch;
  margin: 0.6rem 0 0;
  line-height: 1.6;
}
.pvx-footer__links {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.pvx-footer__links a {
  color: var(--pv-muted);
  text-decoration: none;
}
.pvx-footer__links a:hover { color: var(--pv-fg); }
.pvx-footer__bottom {
  text-align: center;
  color: var(--pv-muted);
  font-size: 0.8rem;
  padding: 1.5rem;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
}
.pvx-footer__bottom a {
  color: var(--pv-muted);
  text-decoration: none;
}
.pvx-footer__bottom a:hover { color: var(--pv-fg); }
.pvx-footer__sep { margin: 0 0.5rem; opacity: 0.5; }

/* About */
.pvx-about__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 1.2rem;
}
.pvx-about__card {
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 14px;
  padding: 1.6rem;
}
.pvx-about__card h3 {
  margin: 0 0 0.5rem;
  color: var(--pv-fg);
  font-size: 1.05rem;
}
.pvx-about__card p {
  margin: 0;
  color: var(--pv-muted);
  line-height: 1.6;
}

/* Socials */
.pvx-socials-teaser {
  max-width: 640px;
  margin: 0 auto;
  text-align: center;
}
.pvx-socials-teaser__eyebrow {
  letter-spacing: 0.22em;
  text-transform: uppercase;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--pv-accent);
  margin: 0 0 0.8rem;
}
.pvx-socials-teaser .pv-section__title {
  margin-bottom: 0.6rem;
}

/* Newsletter */
.pvx-newsletter {
  max-width: 480px;
  margin: 0 auto;
  text-align: center;
}
.pvx-about__eyebrow {
  letter-spacing: 0.22em;
  text-transform: uppercase;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--pv-accent);
  margin: 0 0 0.8rem;
}
.pvx-newsletter__eyebrow {
  letter-spacing: 0.22em;
  text-transform: uppercase;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--pv-accent);
  margin: 0 0 0.8rem;
}
.pvx-newsletter .pv-section__title {
  margin-bottom: 0.6rem;
}
.pvx-newsletter__form {
  display: flex;
  gap: 0.6rem;
  margin-top: 1.6rem;
}
.pvx-newsletter__input {
  flex: 1;
  min-width: 0;
  padding: 0.75rem 1rem;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(255, 255, 255, 0.04);
  color: var(--pv-fg);
  font-size: 0.95rem;
}
.pvx-newsletter__input:focus {
  outline: none;
  border-color: var(--pv-accent);
}
.pvx-newsletter__success {
  margin-top: 1.6rem;
  color: var(--pv-accent);
  font-weight: 500;
}
.pvx-newsletter__error {
  margin-top: 0.9rem;
  color: var(--pv-muted);
  font-size: 0.88rem;
}
@media (max-width: 520px) {
  .pvx-newsletter__form {
    flex-direction: column;
  }
}

/* Modal trust bullets */
.pvlb__trustlist {
  list-style: none;
  margin: 1.4rem 0 0;
  padding: 1.2rem 0 0;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  display: grid;
  gap: 0.55rem;
}
.pvlb__trustlist li {
  position: relative;
  padding-left: 1.5rem;
  color: var(--pv-muted);
  font-size: 0.9rem;
}
.pvlb__trustlist li::before {
  content: "✓";
  position: absolute;
  left: 0;
  color: var(--pv-accent);
  font-weight: 700;
}
/* Release date — subtle metadata in the panel's own bottom-right corner
   (not the info column's), so it stays put regardless of how tall the
   description/tags/trust-list content above it happens to be. */
.pvlb__release {
  position: absolute;
  right: 1.25rem;
  bottom: 1rem;
  font-size: 0.78rem;
  color: rgba(255, 255, 255, 0.4);
  letter-spacing: 0.01em;
  pointer-events: none;
}
@media (max-width: 820px) {
  /* .pvlb__panel becomes a scrolling column here — an absolute overlay
     could sit on top of the trust list as the user scrolls. Let it flow
     naturally instead, right after the info column (its DOM position). */
  .pvlb__release {
    position: static;
    display: block;
    text-align: right;
    padding: 0 1.5rem 1.25rem;
  }
}

/* ---------- Responsive scale, stepped by breakpoint ----------
   Deliberately not vw/clamp-based for type: vw ties size to the CSS
   viewport width, which shrinks at high browser zoom (the browser fits
   fewer CSS px on screen), fighting the zoom magnification itself and
   producing a size that looks different — sometimes smaller, sometimes
   much bigger — depending on zoom level rather than just viewport width.
   Fixed steps at standard breakpoints stay predictable at any zoom.
   Section containers also cap at a laptop-era 1280-1320px by default,
   which on big monitors leaves huge empty gutters; let the main content
   width step up with the viewport too. */
@media (min-width: 640px) {
  .pv-hero__title { font-size: 3.6rem; }
  .pv-hero__sub { font-size: 1.2rem; }
}
@media (min-width: 1024px) {
  .pv-hero__title { font-size: 5.2rem; }
  .pv-hero__sub { font-size: 1.35rem; }
  .pv-fhero__copy { max-width: 46rem; }
}
@media (min-width: 1440px) {
  .pv-section,
  .pv-collections,
  .pv-gallery,
  .pv-fhero__inner,
  .pvx-proof {
    max-width: 1420px;
  }
  .pv-hero__title { font-size: 6.4rem; }
  .pv-hero__sub { font-size: 1.5rem; }
  .pv-fhero__copy { max-width: 48rem; }
}
@media (min-width: 1600px) {
  .pv-section,
  .pv-collections,
  .pv-gallery,
  .pv-fhero__inner,
  .pvx-proof {
    max-width: 1560px;
  }
  .pv-hero__title { font-size: 7rem; }
  .pv-hero__sub { font-size: 1.6rem; }
  .pv-fhero__copy { max-width: 50rem; }
}
@media (min-width: 2000px) {
  .pv-section,
  .pv-collections,
  .pv-gallery,
  .pv-fhero__inner,
  .pvx-proof {
    max-width: 1800px;
  }
  .pv-hero__title { font-size: 7.8rem; }
  .pv-hero__sub { font-size: 1.7rem; }
  .pv-fhero__copy { max-width: 54rem; }
}

/* ---------- Worldroot Chronicles intro page ---------- */
:root {
  --pv-worldroot-accent: #2f6b4f;
}
.pv-worldroot__hero {
  position: relative;
  overflow: hidden;
  padding: 7rem 1.5rem 4rem;
}
/* Same blurred-macro-drift treatment as the homepage hero (.pv-fhero__bgart),
   reusing its pvHeroArtDrift keyframes rather than defining a second
   near-identical animation. Opacity set via Framer Motion (WorldrootHero.tsx)
   for the crossfade on rotation. */
.pv-worldroot__bgart {
  position: absolute;
  inset: -12%;
  z-index: 0;
  background-size: cover;
  background-position: center;
  filter: blur(56px) saturate(1.2);
  pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
  .pv-worldroot__bgart {
    animation: pvHeroArtDrift 24s ease-in-out infinite alternate;
  }
}
.pv-worldroot__hero-inner {
  position: relative;
  z-index: 1;
  max-width: 920px;
  margin: 0 auto;
  text-align: center;
}
.pv-worldroot__eyebrow {
  letter-spacing: 0.22em;
  text-transform: uppercase;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--pv-worldroot-accent);
  margin: 0 0 1rem;
}
.pv-worldroot__title {
  font-size: clamp(2.2rem, 5vw, 3.6rem);
  font-weight: 800;
  line-height: 1.08;
  margin: 0 0 1.2rem;
  color: var(--pv-fg);
}
.pv-worldroot__sub {
  font-size: 1.1rem;
  color: var(--pv-muted);
  line-height: 1.6;
  margin: 0 auto 2rem;
  max-width: 640px;
}
.pv-worldroot__actions {
  display: flex;
  justify-content: center;
  gap: 1rem;
  flex-wrap: wrap;
}
.pv-worldroot__intro {
  max-width: 720px;
  margin: 0 auto;
  padding: 0 1.5rem 4rem;
  text-align: center;
  color: var(--pv-muted);
  line-height: 1.7;
  font-size: 1.05rem;
}
.pv-worldroot__gallery {
  max-width: 1280px;
  margin: 0 auto;
  padding: 2rem 1.5rem 5rem;
}
.pv-worldroot__cta-repeat {
  display: flex;
  justify-content: center;
  padding: 0 1.5rem 6rem;
}

/* ================================================================
   CINEMATIC ENHANCEMENT LAYER — v2 Premium Art Gallery
   Inspired by: "futuristic platform with immersive 3D product
   visualizations, wireframe transitions, cinematic scroll
   storytelling, floating UI systems and premium aesthetic"
   Adapted for Pixaverse: the metal print IS the hero product.
   2026-06-30
   ================================================================ */

/* Particle canvas layer — absolute fill, between blurred art (z:0) and
   content (z:2). pointer-events:none so the hero's pointer handlers fire. */
.pv-fhero__particles {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}


/* Gold glow on hero Print3D hover — "lit from within" moment.
   The existing tilt transition (0.18s ease-out) is preserved;
   box-shadow gets its own slower transition for a cinematic breathe-in. */
@media (prefers-reduced-motion: no-preference) {
  .pv-fhero__visual .pv3d {
    transition:
      transform 0.18s ease-out,
      box-shadow 0.55s cubic-bezier(0.22, 1, 0.36, 1);
  }
  .pv-fhero__visual .pv3d:hover {
    box-shadow:
      inset 0 0 0 1px rgba(232, 177, 90, 0.32),
      inset 0 -1px 0 rgba(0, 0, 0, 0.32),
      0 40px 90px rgba(0, 0, 0, 0.6),
      0 8px 24px rgba(0, 0, 0, 0.5),
      0 0 52px rgba(232, 177, 90, 0.24),
      0 0 110px rgba(232, 177, 90, 0.09);
  }
}

/* Slow ambient pulse on the radial glow behind the hero print —
   makes it feel alive without flickering. */
@keyframes pvPrintAura {
  0%, 100% { opacity: 0.8; }
  50%       { opacity: 1;   }
}
@media (prefers-reduced-motion: no-preference) {
  .pv-fhero__visual .pv3d__stage::before {
    animation: pvPrintAura 4.5s ease-in-out infinite;
  }
}

/* Glassmorphism on artwork card meta — a frosted dark-glass info
   panel materialises on hover, like a premium gallery label. */
.pv-card__meta {
  transition: background 0.28s ease, border-color 0.28s ease;
  border-top: 1px solid transparent;
}
.pv-card:hover .pv-card__meta {
  background: rgba(8, 7, 14, 0.84);
  backdrop-filter: blur(10px) saturate(1.4);
  -webkit-backdrop-filter: blur(10px) saturate(1.4);
  border-top-color: rgba(232, 177, 90, 0.16);
}
