/* ============================================================================
   forge.css — the page as a forge that heats up as you descend.

   WHY THIS EXISTS, in one line: Garett asked for two things — "the site needs to
   use colors to indicate the path towards buying" and "as the user scrolls down
   the background animates". Those are the same thing, so they get one mechanism.
   Scroll progress drives a heat ramp along the EXISTING --path-1..4 tokens:
   cold blueprint carbon at the top where you are still reading, lit gold by the
   checkout. The background is not decoration; it is the buying path rendered as
   atmosphere.

   PERFORMANCE SPLIT — this is the whole reason it can run full-page without
   wrecking a three-year-old Android. The expensive part (large soft gradients
   covering the viewport) is CSS, cross-fading on the compositor with nothing per
   frame in JS. The cheap part (a few dozen embers) is canvas. The naive version
   of this effect redraws viewport-sized gradients in canvas every frame and
   melts a phone; here JS writes four custom properties per scroll frame and the
   GPU does the rest.

   NO-JS / NO-MOTION SAFETY: every layer's resting opacity below is its COLD
   state, so with JS dead the page still paints a finished dark workshop rather
   than a black void. The reveal states are gated behind html.js-reveal, added
   by script, so content is never left invisible by a failed fetch.
   ========================================================================== */

:root{
  /* Written by forge.js on every scroll frame. These defaults ARE the top of the
     page, so the first paint is correct before a single frame has run. */
  --heat:0;
  --heat-cold:1;
  --heat-warm:0;
  --heat-hot:0;
  --forge-rise:0px;
}

/* ---------------------------------------------------------------- the stack */

.forge{
  position:fixed; inset:0; z-index:-1;   /* above the plate (-2), under content */
  pointer-events:none;
  overflow:hidden;
  /* contain paints so a 120%-wide drifting grid child cannot create a scrollbar */
  contain:strict;
}
.forge-l{ position:absolute; inset:0; }

/* Content rides above it. The hero already carried z-index:1; header, main and
   footer need it too or the fixed layer paints over them. */
body > header,
body > main,
body > footer,
.site-header, .site-footer{ position:relative; z-index:1; }

/* ------------------------------------------------------- light, not ground

   NOTHING HERE PAINTS A BACKGROUND. The ground is already built in theme.css
   and it is the thing this file must not touch: body::before is the diamond
   plate (tread.svg, masked along the lamp axis) and body::after is the
   worklamp. My first version added an opaque .forge-base gradient at z-index 0
   and buried both -- Garett's "need the background diamond metal like we had"
   was exactly that, me covering his metal with a flat gradient.

   So the whole forge now sits at z-index:-1: ABOVE the plate at -2, so the
   heat is light falling ON the metal, and below all content. Every layer is
   translucent. There is also no blueprint grid any more -- the plate is the
   texture, and two textures in one background fight each other. */

/* Cold cyan wash at the top of the page — --classify, the token the catalog
   already uses for "this is what you are looking at". Reading is cold. */
.forge-cold{
  opacity:var(--heat-cold);
  background:
    radial-gradient(120% 70% at 72% -12%,
      rgba(91,200,245,0.17) 0%,
      rgba(91,200,245,0.065) 40%,
      transparent 74%);
  transition:opacity 420ms linear;
}

/* --------------------------------------------------------------- the heat */

/* Two blooms cross-fading, rather than one bloom with an interpolated color.
   color-mix() would be tidier but this is bulletproof everywhere and both
   layers composite on the GPU, so the "cheaper" version would also be the
   fragile one. --forge-rise lifts the glow to meet you as you descend. */
.forge-warm{
  opacity:var(--heat-warm);
  transform:translate3d(0, var(--forge-rise), 0);
  background:
    radial-gradient(125% 92% at 50% 120%,
      rgba(184,144,14,0.390) 0%,
      rgba(184,144,14,0.158) 36%,
      transparent 74%),
    radial-gradient(90% 46% at 50% -8%,
      rgba(184,144,14,0.075) 0%,
      transparent 68%);
  will-change:opacity, transform;
}
.forge-hot{
  opacity:var(--heat-hot);
  transform:translate3d(0, var(--forge-rise), 0);
  background:
    radial-gradient(100% 64% at 50% 112%,
      rgba(255,216,90,0.345) 0%,
      rgba(244,197,33,0.180) 32%,
      transparent 70%),
    radial-gradient(160% 115% at 50% 135%,
      rgba(244,197,33,0.098) 0%,
      transparent 62%);
  will-change:opacity, transform;
}

/* The middle of the page was a dead zone: the blooms live at the bottom edge, so
   between the cold top and the lit checkout there was nothing but carbon. These
   are the forge walls — two soft side lights that peak with --heat-warm and fade
   as the mouth below takes over, so the page is never unlit anywhere. */
.forge-walls{
  opacity:calc(var(--heat-warm) * (1 - var(--heat-hot) * 0.45));
  background:
    radial-gradient(42% 70% at -6% 48%, rgba(227,193,105,0.098) 0%, transparent 70%),
    radial-gradient(42% 70% at 106% 44%, rgba(227,193,105,0.083) 0%, transparent 70%);
}

/* Heat haze — only present at real heat, so it never muddies the reading half of
   the page. A slow vertical shimmer; one composited transform, no repaint. */
.forge-haze{
  inset:auto 0 -10% 0; height:46%;
  opacity:calc(var(--heat-hot) * 0.5);
  background:repeating-linear-gradient(
    180deg,
    rgba(255,216,90,0.030) 0px,
    transparent 3px,
    transparent 7px);
  animation:forge-haze 7.5s ease-in-out infinite alternate;
  will-change:transform, opacity;
}
@keyframes forge-haze{
  from{ transform:translate3d(0,0,0) scaleY(1); }
  to{   transform:translate3d(0,-8px,0) scaleY(1.04); }
}

/* Embers canvas sits between the atmosphere and the vignette. */
#forgeEmbers{ width:100%; height:100%; }

/* A vignette that tightens as the page heats, so the lit bottom still frames the
   text instead of washing it out. Pure overlay, no motion. */
.forge-vignette{
  background:radial-gradient(120% 95% at 50% 45%,
    transparent 42%,
    rgba(5,6,7,calc(0.10 + var(--heat) * 0.14)) 100%);
}

/* ------------------------------------------------------- the progress rail */

/* Literally "the path towards buying": a hairline on the left edge that fills
   along --path-1 -> --path-4 as you descend. It is the same ramp as the
   background, so the rail explains the atmosphere without a word of copy. */
.forge-rail{
  position:fixed; top:0; left:0; bottom:0; width:2px; z-index:40;
  pointer-events:none;
  background:rgba(255,255,255,0.05);
}
.forge-rail i{
  display:block; width:100%; height:100%;
  transform:scaleY(var(--heat)); transform-origin:top;
  background:linear-gradient(180deg,
    var(--path-1) 0%,
    var(--path-2) 42%,
    var(--path-3) 78%,
    var(--path-4) 100%);
  box-shadow:0 0 10px var(--path-glow);
  will-change:transform;
}
/* On a phone 2px of left edge is where a back-swipe lives, so the rail goes to
   the top where it reads as progress rather than fighting the gesture. */
@media (max-width:760px){
  .forge-rail{ top:0; left:0; right:0; bottom:auto; width:auto; height:2px; }
  .forge-rail i{
    transform:scaleX(var(--heat)); transform-origin:left;
    background:linear-gradient(90deg,
      var(--path-1) 0%, var(--path-2) 42%, var(--path-3) 78%, var(--path-4) 100%);
  }
}

/* ------------------------------------------------------------- reveals */

/* STEP ONE is to undo theme.css. It sets .reveal{opacity:0} unconditionally at
   equal specificity; because forge.css loads after it, this plain reset wins and
   the hidden state stops being the default. That is the entire bug fix — a
   failed app.js fetch can no longer leave 37 elements invisible. */
.reveal{ opacity:1; transform:none; }

/* ROBUST BY DEFAULT, which the previous version was not: theme.css set
   .reveal{opacity:0} unconditionally, so a failed app.js fetch rendered 37
   blank elements on this page. Here the hidden state only exists once script
   has run and declared itself able to undo it. */
html.js-reveal .reveal{
  opacity:0; transform:translateY(22px);
  transition:opacity 760ms cubic-bezier(0.22,0.68,0.24,1),
             transform 760ms cubic-bezier(0.22,0.68,0.24,1);
}
html.js-reveal .reveal.in{ opacity:1; transform:none; }
html.js-reveal .reveal.d1{transition-delay:0.07s;}
html.js-reveal .reveal.d2{transition-delay:0.14s;}
html.js-reveal .reveal.d3{transition-delay:0.21s;}
html.js-reveal .reveal.d4{transition-delay:0.28s;}

/* A section's heading gets its underline drawn rather than faded, which reads as
   deliberate rather than as a page that forgot to load. */
html.js-reveal .page-head{ position:relative; }
html.js-reveal .page-head::after{
  content:""; position:absolute; left:0; bottom:-14px; height:2px; width:64px;
  background:linear-gradient(90deg, var(--path-3), transparent);
  transform:scaleX(0); transform-origin:left;
  transition:transform 900ms cubic-bezier(0.22,0.68,0.24,1) 120ms;
}
html.js-reveal .page-head.in::after{ transform:scaleX(1); }

/* ------------------------------------------------------- reduced motion */

/* Honor the preference completely: no drift, no haze, no reveal offset. The heat
   ramp stays, because it is color rather than movement and it is the thing that
   communicates where you are on the path. */
@media (prefers-reduced-motion:reduce){
  .forge-haze{ animation:none; }
  .forge-warm, .forge-hot{ transform:none; }
  html.js-reveal .reveal{ opacity:1; transform:none; transition:none; }
  html.js-reveal .page-head::after{ transform:scaleX(1); transition:none; }
}
