/* ==========================================================================
   Heaven Furniture Mart — "experience" layer
   Cohesive motion + presentation system added on top of the base design
   language. Everything here shares the same easing tokens and the Oxblood &
   Bone palette so the loader, cinematic section, fluid text, 3D stage and AR
   controls read as one design, not bolted-on patches.
   ========================================================================== */

/* ==========================================================================
   1. FIRST-VISIT LOADING SCREEN
   Full-screen, brand-consistent, shown only until the initial heavy assets
   (fonts + the correct hero video's first frames) are ready. Gated by
   sessionStorage so intra-session navigation never shows it again.
   Fails OPEN: js/loader.js force-dismisses after a hard timeout, and if JS
   never runs the overlay is display:none by default (class added by JS).
   ========================================================================== */
/* THE LOADER STAYS DARK, DELIBERATELY. Everything else in Phase 4 moved to
   paper, so this rule needs its reason recorded or a later editor will "finish
   the job" and regress it.

   It only ever appears on the homepage, whose first section is the #111219
   hero. A paper loader would flash white and then hand over to a dark band —
   two hard luminance jumps in the first second on the slowest connections,
   which is exactly the audience this overlay exists for. Ink-on-ink is one
   continuous surface: the overlay lifts and the hero is already there.

   What DID change is the token names. Every colour here was written in the
   retired oxblood vocabulary — --bone, --clay, and a warm rgba(245,239,231)
   track. Those are legacy aliases now (--bone → --paper, --clay →
   --on-dark-mute), so the file rendered correctly while describing a palette
   that no longer exists. Renamed to the honest on-dark roles, and the track's
   warm cream replaced with plain white at the same alpha so nothing in the
   loader carries an oxblood value any more.

   The wordmark also picks up the display law from base.css: weight 200 with
   positive tracking, not 800 at -.02em. It was the last piece of retired
   typography still shipping. */
#loader {
  position: fixed; inset: 0; z-index: 200;
  display: grid; place-items: center;
  background: var(--ink);
  /* revealed only when JS confirms a first visit */
}
body:not(.loading) #loader { display: none; }

.loader-inner {
  display: grid; justify-items: center; gap: 1.75rem;
  padding: 2rem; text-align: center;
  transform: translateY(0);
  transition: opacity var(--dur-lg) var(--ease-out);
}
.loader-mark {
  font-family: var(--display); font-weight: 200;
  font-size: clamp(1.6rem, 5vw, 2.4rem); letter-spacing: .045em;
  text-transform: uppercase;
  color: var(--on-dark); line-height: 1;
}
.loader-mark span {
  display: block; font-family: var(--sans); font-weight: 600;
  font-size: .7rem; letter-spacing: .32em; text-transform: uppercase;
  color: var(--on-dark-mute); margin-top: .7rem;
}
/* A thin progress rule that fills left→right, echoing the fluid text-fill. */
.loader-bar {
  width: min(64vw, 340px); height: 2px;
  background: rgba(255, 255, 255, .16); overflow: hidden;
}
/* The fill is the signal red: it is the only moving element on screen and the
   only place the brand's one accent can appear without competing with anything.
   At 2px tall this is non-text decoration, so 4.28:1 on ink is not a bar it has
   to clear — the percentage readout beside it carries the accessible value. */
.loader-bar i {
  display: block; height: 100%; width: 100%;
  background: var(--primary);
  transform: scaleX(var(--p, 0)); transform-origin: left;
  transition: transform .4s var(--ease-out);
}
.loader-pct {
  font-family: var(--sans); font-variant-numeric: tabular-nums;
  font-size: .8125rem; font-weight: 600; letter-spacing: .04em;
  color: var(--on-dark-mute);
}
/* Exit: the whole overlay lifts and fades as one deliberate move. */
#loader.is-done {
  opacity: 0; visibility: hidden;
  transform: translateY(-2%);
  transition: opacity var(--dur-xl) var(--ease-out),
              transform var(--dur-xl) var(--ease-out),
              visibility 0s linear var(--dur-xl);
}
/* Content underneath eases up into place once the loader clears. */
body.loading { overflow: hidden; }
body.intro main { opacity: 0; transform: translateY(14px); }
body.intro-run main {
  opacity: 1; transform: none;
  transition: opacity var(--dur-xl) var(--ease-out),
              transform var(--dur-xl) var(--ease-out);
}
@media (prefers-reduced-motion: reduce) {
  .loader-bar i, #loader.is-done, body.intro-run main { transition: none !important; }
}

/* ==========================================================================
   2. CINEMATIC INDEX SECTION  ("why-ar")
   Oversized display lines, hairline dividers, a slow left→right fluid fill,
   and per-line reveal of hidden detail copy on hover (desktop) / tap (mobile).

   THIS SECTION IS NOW ON PAPER, and that inversion is the single highest-value
   change in the whole re-skin: run from #E9E9E9 to #191919 on white, the fill
   IS the reference's ghost-typography language, arrived at with the motion
   system that was already here. js/cine.js is untouched — scroll-driven fill,
   the IntersectionObserver headline trigger, hover/tap rows and the accordion
   all behave exactly as before. Only colours and borders moved.
   ========================================================================== */
.cine {
  background: var(--paper); color: var(--on-light);
  padding-block: clamp(3.5rem, 9vw, 7rem);
  overflow: hidden;
}
/* NOT --clay (#A4888B): that is the muted text colour for DARK surfaces and
   would land at 3.31:1 here, under the 4.5:1 a 13px/700 eyebrow owes. */
.cine .eyebrow { color: var(--on-light); }

.cine-list {
  list-style: none; margin: 2rem 0 0; padding: 0;
  border-top: var(--edge-dk);
}
.cine-row {
  position: relative;
  border-bottom: var(--edge-dk);
}
.cine-row > button {
  all: unset; box-sizing: border-box;
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  width: 100%; cursor: pointer;
  padding-block: clamp(.4rem, 1.6vw, 1rem);
}
.cine-row:focus-within { outline: none; }
.cine-row > button:focus-visible { outline: 3px solid var(--ar-signal); outline-offset: -3px; }

/* The oversized word. Two stacked layers give the fluid fill:
   - base layer: the ghost grey the whole re-skin uses for "not yet there"
   - fill layer: ink, clipped by a moving inset driven by scroll position */
.cine-word {
  position: relative;
  font-family: var(--grotesque);
  /* Was 400 with a note that "Anton is single-weight, already heavy". Anton is
     retired; Jost is variable, so the display voice can be what the reference
     actually is — thin and wide. 300 rather than 200 because at #E9E9E9 the
     unfilled base has to still be a legible ghost of a word. */
  font-weight: 300;
  font-size: clamp(2.6rem, 12.5vw, 11rem);
  line-height: .92; letter-spacing: .01em;
  text-transform: uppercase;
  white-space: nowrap;
}
.cine-word .base { color: var(--ghost); }
.cine-word .fill {
  position: absolute; inset: 0;
  color: var(--on-light);
  clip-path: inset(0 var(--fill-inset, 100%) 0 0);        /* dynamically driven by scroll in js/cine.js */
  will-change: clip-path;
}
@media (prefers-reduced-motion: reduce) {
  .cine-word .fill { clip-path: inset(0 0 0 0) !important; }
}

/* Marker dot on the right — the reference's accent dot, in the signal red. */
.cine-dot {
  flex: none; width: clamp(10px, 1.4vw, 16px); height: clamp(10px, 1.4vw, 16px);
  border-radius: 50%; background: var(--primary);
  opacity: .6; transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.cine-row[data-open="true"] .cine-dot { opacity: 1; transform: scale(1.25); }

/* Hidden detail copy: revealed by clip + fade + slight rise. Never a hard
   on/off. Height animates so surrounding rows move smoothly. */
.cine-detail {
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows var(--dur-lg) var(--ease-out);
}
.cine-detail > div { overflow: hidden; }
.cine-detail p {
  margin: 0; max-width: 52ch;
  padding: 0 0 clamp(1rem, 2.5vw, 1.6rem);
  color: var(--on-light-mute); font-size: var(--fs-lead); line-height: 1.5;
  opacity: 0; transform: translateY(10px);
  transition: opacity var(--dur-lg) var(--ease-out), transform var(--dur-lg) var(--ease-out);
  transition-delay: .06s;
}
.cine-row[data-open="true"] .cine-detail { grid-template-rows: 1fr; }
.cine-row[data-open="true"] .cine-detail p { opacity: 1; transform: none; }

/* Desktop: hover drives it, so the open row's ghost also darkens a step —
   a hint that the word is live, without pre-empting the scroll fill. */
@media (hover: hover) and (min-width: 1025px) {
  .cine-row .cine-word .base { transition: color var(--dur-lg) var(--ease-out); }
  .cine-row[data-open="true"] .cine-word .base { color: var(--hair); }
}

/* ==========================================================================
   3. STANDALONE FLUID-FILL HEADLINE  (.fluid-head)
   The section's own title uses the same liquid fill, driven by scroll.
   Light-first now, matching the page: the dark pairing is kept as an opt-in
   for any .section-dark that wants it later.
   ========================================================================== */
.fluid-head {
  position: relative; display: inline-block;
  font-family: var(--display);
  /* Was 800/-.025em. That was the old type law; the new display voice is thin
     and wide, and this rule sits after base.css so it would otherwise be the
     one headline on the site still set heavy and tight. */
  font-weight: 200;
  font-size: var(--fs-h2); line-height: 1.02; letter-spacing: .045em;
}
.fluid-head .base { color: var(--ghost); }
.fluid-head .fill {
  position: absolute; inset: 0; color: var(--on-light);
  clip-path: inset(0 var(--fill-inset, 100%) 0 0);
  will-change: clip-path;
}
.section-dark .fluid-head .base { color: var(--ink-3); }
.section-dark .fluid-head .fill { color: var(--on-dark); }
@media (prefers-reduced-motion: reduce) {
  .fluid-head .fill { clip-path: inset(0 0 0 0) !important; }
}

/* ==========================================================================
   4. 3D STAGE — real swap, not an overlay
   The poster image and the model-viewer are separate layers. Activating 3D
   fades/scales the poster OUT (and sets it inert) before the model fades IN;
   Exit 3D reverses it. No stacking, no flashing.
   ========================================================================== */
.stage { position: relative; }
.stage-poster,
.stage model-viewer {
  transition: opacity var(--dur-xl) var(--ease-out),
              transform var(--dur-xl) var(--ease-out);
}
/* Poster resting state */
.stage-poster { opacity: 1; transform: scale(1); }
/* When 3D is live: poster is faded, scaled back, and made inert. */
.stage[data-mode="3d"] .stage-poster {
  opacity: 0; transform: scale(1.03);
  pointer-events: none; visibility: hidden;
  transition: opacity var(--dur-lg) var(--ease-out),
              transform var(--dur-lg) var(--ease-out),
              visibility 0s linear var(--dur-lg);
}
/* model-viewer enters from a slightly scaled-down, transparent state. */
.stage model-viewer {
  display: block;           /* custom elements default to inline in some browsers */
  position: absolute;       /* layer alongside the poster — fills the stage */
  inset: 0;
  width: 100%; height: 100%;
  opacity: 0; transform: scale(1.02);
}
.stage[data-mode="3d"] model-viewer.is-in {
  opacity: 1; transform: scale(1);
}

/* Exit-3D control — quiet, structural, never uses the AR signal colour.
   It floats ON the stage, over a product image of unknown lightness, so it
   carries its own ink chip rather than borrowing the page's ground. Retinted
   from the oxblood rgba(23,16,15,.82) to --ink's value at the same alpha;
   white-on-that is comfortably past 4.5:1 whatever the model is doing behind
   it. The border is a white hairline (--edge) precisely because the chip is
   dark — that one is correct here and wrong on paper. */
.stage-exit {
  position: absolute; top: 12px; right: 12px; z-index: 3;
  display: inline-flex; align-items: center; gap: .45rem;
  min-height: 44px; padding: 9px 15px;
  background: rgba(17, 18, 25, .82); color: var(--on-dark);
  border: var(--edge); border-radius: 0; cursor: pointer;
  font-family: var(--sans); font-size: var(--fs-small); font-weight: 700;
  opacity: 0; transform: translateY(-6px); pointer-events: none;
  transition: opacity var(--dur) var(--ease-out), transform var(--dur) var(--ease-out),
              background var(--dur) var(--ease);
}
@supports (backdrop-filter: blur(6px)) { .stage-exit { backdrop-filter: blur(6px); } }
.stage[data-mode="3d"] .stage-exit { opacity: 1; transform: none; pointer-events: auto; }
/* Hover DARKENS to the deep red, not the bright one: the label is 15px/700,
   under WCAG's large-text cutoff, so white on it owes 4.5:1 — #ED1C2E gives
   4.37:1 and fails, #D41929 gives 5.29:1. */
.stage-exit:hover { background: var(--primary-deep); }
.stage-exit svg { width: 16px; height: 16px; }

/* ==========================================================================
   5. AR SIZE / SCALE CONTROLS
   Shown once a model is loaded: a slider (44px target) plus a reset.

   Migrated to paper with the rest of the piece page. The old comment said "ink
   surface, oxblood fill on the track", which describes the retired skin — the
   panel is now a hairline-ruled block on the page's own ground, and the filled
   part of the track is the signal red.

   The track is the interesting accessibility case: a slider's filled portion is
   a non-text UI indicator, so it owes 3:1 against the unfilled part, not
   against the page. Red on #E9E9E9 clears that; red on white would too.
   ========================================================================== */
.scale-ctl {
  display: grid; gap: .55rem;
  margin-top: 1rem; padding: 1rem 1.1rem;
  background: var(--paper-2); border: var(--edge-dk);
  opacity: 0; transform: translateY(8px);
  transition: opacity var(--dur-lg) var(--ease-out), transform var(--dur-lg) var(--ease-out);
}
.scale-ctl[hidden] { display: none; }
.stage-wrap[data-model="ready"] .scale-ctl { opacity: 1; transform: none; }
.scale-ctl .scale-top {
  display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
}
.scale-ctl label {
  font-size: var(--fs-eyebrow); font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: var(--on-light-fade);
}
.scale-ctl output {
  font-variant-numeric: tabular-nums; font-weight: 700; color: var(--on-light);
  font-size: var(--fs-small);
}
.scale-row { display: flex; align-items: center; gap: .75rem; }
.scale-range {
  -webkit-appearance: none; appearance: none;
  flex: 1; height: 44px; background: transparent; cursor: pointer;
}
/* --fillpct is written by js/piece.js:111 on every input event. */
.scale-range::-webkit-slider-runnable-track {
  height: 4px; background: linear-gradient(var(--primary), var(--primary)) no-repeat,
                            var(--ghost);
  background-size: var(--fillpct, 50%) 100%;
}
.scale-range::-moz-range-track { height: 4px; background: var(--ghost); }
.scale-range::-moz-range-progress { height: 4px; background: var(--primary); }
/* The thumb is the control's handle, so its edge owes 3:1 (WCAG 1.4.11): an ink
   disc on paper is 17.4:1 and needs no shadow to be found. */
.scale-range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 22px; height: 22px; margin-top: -9px;
  background: var(--on-light); border: 0; border-radius: 50%;
}
.scale-range::-moz-range-thumb {
  width: 22px; height: 22px; background: var(--on-light); border: 0; border-radius: 50%;
}
.scale-range:focus-visible { outline: 3px solid var(--ar-signal); outline-offset: 4px; }
.scale-reset {
  min-height: 44px; padding: 8px 14px; white-space: nowrap;
  background: transparent; border: 1px solid var(--hair-strong); color: var(--on-light-mute);
  font-family: var(--sans); font-size: var(--fs-small); font-weight: 600; cursor: pointer;
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.scale-reset:hover { border-color: var(--primary); color: var(--on-light); }
.scale-hint { margin: 0; font-size: .8125rem; color: var(--on-light-mute); }

/* ==========================================================================
   6. CONSISTENT PRODUCT GROUND
   Every product surface — grid cells, the piece-page stage, the room board,
   the media splits — shares ONE ground, so eighteen separately-shot pieces
   read as a single catalogue rather than eighteen unrelated photographs.

   RE-SKINNED FROM OXBLOOD TO PAPER. The old value was a dark vertical gradient
   (--ink-3 → --ink-2 → --ink) with a warm vignette. That was right when the
   whole site was #170000 AND the product images were opaque RGB baked onto the
   same oxblood ground: photo and page met invisibly. Both halves of that are
   now false — the page is paper and the artwork is a cut-out with real alpha —
   so the dark version puts a heavy rectangle behind a floating silhouette,
   which is the one thing the reference never does.

   The tint is #E9E9E9 (--ghost) at 55%, compositing to ≈#F4F4F4 on white.
   That is enough to read as a swept studio floor under the cut-out's own baked
   ground shadow, and not enough to become a card. Written as a literal rgba
   rather than color-mix() so it needs no @supports fallback.

   Hairlines are deliberately NOT set here: each component owns its own edge in
   css/pages.css, so this rule does exactly one job and can be reasoned about
   on its own.

   THE TWO !important FLAGS ARE GONE. They existed to defeat per-component
   backgrounds in pages.css, but load order already does that: the cascade runs
   tokens → base → pages → experience → nav, and equal-specificity rules
   resolve by file order, so this block wins unaided. Keeping them would leave
   every one of these four components permanently unstyleable from the file
   that actually owns it — which is how a re-skin becomes a rewrite.
   ========================================================================== */
:root {
  --product-bg:
    radial-gradient(120% 90% at 50% 46%,
      rgba(233, 233, 233, .55) 0%, rgba(255, 255, 255, 0) 68%);
}
.piece-figure,
.stage,
.room-item img,
.media-split img {
  background: var(--product-bg);
  background-color: var(--paper);
}


/* ==========================================================================
   THE CORRIDOR — product card stream (js/corridor.js builds the cards).
   Fixed band height so the section can't shift layout while cards spawn.
   ========================================================================== */
#corridor {
  position: relative;
  height: 70vh;
  min-height: 480px;
  overflow: hidden;
  background: var(--ink);
  border-top: var(--edge);
  border-bottom: var(--edge);
  /* cqw units inside the canvas resolve against this container. */
  container-type: inline-size;
}
#corridor-canvas { position: absolute; inset: 0; }

/* Copy floats over the moving cards, kept legible by an ink scrim —
   same treatment as the hero. */
.corridor-body {
  position: relative;
  z-index: 2;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--section) 1.25rem;
  background:
    radial-gradient(90% 70% at 50% 50%, rgba(17,18,25,.82), rgba(17,18,25,.34) 70%, rgba(17,18,25,.0) 100%);
  /* Tinted with --ink, not the old rgba(23,0,0) oxblood. Over blue-black cards
     a warm scrim greyed the artwork's cool cast instead of deepening it, and
     left a visible warm halo where the gradient met the section background. */
}
.corridor-body .eyebrow { color: var(--primary-lite); }  /* 5.04:1 on the ink
   corridor. --primary is 4.33:1, and at --fs-eyebrow (13px) this is body-copy
   size however bold it is, so it owes the full 4.5:1. This rule also has to
   out-specify .section-dark .eyebrow, which #corridor now carries. */
.corridor-body h2 {
  font-family: var(--grotesque);
  /* Matches the display law: thin and wide, not the old 400 + tight tracking. */
  font-weight: 200;
  text-transform: uppercase;
  letter-spacing: .045em;
  font-size: var(--fs-h2);
  margin: .6rem 0 .9rem;
  max-width: 22ch;
}

/* Save-data / 2G fallback: a quiet static strip of three pieces. */
.corridor-strip {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  gap: var(--gap);
  padding: 3rem;
}
.corridor-strip img {
  width: min(30%, 300px); aspect-ratio: 4/3; object-fit: cover;
  border: var(--edge); border-radius: 4px;
}
@media (max-width: 700px) {
  #corridor { height: 62vh; min-height: 420px; }
  .corridor-strip { flex-direction: column; }
  .corridor-strip img { width: min(70%, 320px); }
}


/* AR buttons keep their reserved lime — hover uses the deep lime, so the AR
   colour law holds (every lime-family pixel stays lime-family). */
.btn-ar:hover, .btn-ar:focus-visible { background: var(--ar-signal-dp); }
