/* =====================================================================
   RASA MOTION LAYER — css/motion.css
   Companion stylesheet to js/motion.js. Every rule is namespaced mx-
   and most initial "hidden" states are gated on html.mx (added by the
   engine at boot), so a page with JS disabled — or a stale service-
   worker copy — never hides content.

   Tokens fall back gracefully whether the page loads styles.css
   (--color-accent) or carries its own inline vars (--accent).
   ===================================================================== */

:root {
  --mx-accent: var(--color-accent, var(--accent, #f5a623));
  --mx-gold: var(--color-accent-light, #ffd700);
  --mx-char: #e63946;
  --mx-ease: cubic-bezier(0.16, 1, 0.3, 1);
  --mx-ease-soft: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* ------------------------------------------------------------------
   Shared canvas hosts (simmer wisps, bloom bursts)
------------------------------------------------------------------ */
.mx-fx-host {
  position: relative;
}
.mx-simmer-canvas,
.mx-bloom-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 1;
}
.mx-bloom-canvas {
  z-index: 3;
}

/* ------------------------------------------------------------------
   Directional reveals — [data-mxr] (up | left | right | fade)
   Hidden states apply ONLY once the engine has booted (html.mx).
------------------------------------------------------------------ */
html.mx [data-mxr] {
  opacity: 0;
  transform: translateY(26px);
  transition:
    opacity 0.85s var(--mx-ease),
    transform 0.85s var(--mx-ease);
  will-change: opacity, transform;
}
html.mx [data-mxr='left'] {
  transform: translateX(-64px);
}
html.mx [data-mxr='right'] {
  transform: translateX(64px);
}
html.mx [data-mxr='fade'] {
  transform: none;
}
html.mx [data-mxr].mx-in {
  opacity: 1;
  transform: none;
}
html.mx [data-mxr].mx-settled {
  will-change: auto;
}

/* ------------------------------------------------------------------
   Maillard Ignite — words brown from raw -> toasting -> developed.
   Raw: barely-there. Hot (the freshly lit edge): saffron, like the
   browning front moving through the pan. Lit: white-hot, settled.
   Emphasis words (.mxw-hi) keep the saffron permanently.
------------------------------------------------------------------ */
.mx-ignite .mxw {
  color: rgba(255, 255, 255, 0.16);
  /* unbreakable word unit: glyphs inside are inline-blocks (so they can
     translateY), and inline-blocks would otherwise wrap mid-word */
  display: inline-block;
  white-space: nowrap;
  /* no transition: the ignite engine paints continuous per-frame values;
     a CSS transition here would lag-fight the interpolation and read as
     mushy chunked flips (rebuilt 2026-07-30) */
}
.mx-ignite .mxc {
  display: inline-block;
  position: relative;
  /* The engine animates OPACITY (composite) rather than color (repaint),
     so the settled colour is authored here once and never rewritten. */
  color: rgba(255, 255, 255, 0.94);
  /* Some callers style their <em>/<strong> with a gradient-clipped fill
     (-webkit-text-fill-color: transparent + background-clip: text on the
     PARENT element). That fill property inherits into these glyph spans,
     but the gradient background does not (background never inherits) —
     so the glyph paints with a transparent fill and nothing behind it:
     invisible text (found 2026-08-08, the "It was always ___" bug — the
     word was fully lit in the DOM and completely invisible on screen).
     Reset it here so every glyph is guaranteed an opaque, visible fill. */
  -webkit-text-fill-color: currentColor;
  /* the glyph rises into place — promote once, not per frame */
  will-change: opacity, transform;
}
/* The saffron "browning front", stacked exactly over its glyph and cross-faded
   by opacity alone. Two static colours beat one interpolated one. A pseudo-
   element (not a duplicate span) keeps the sentence single for copy-paste and
   screen readers while still giving us a second, independently-faded layer. */
.mx-ignite .mxc::after {
  content: attr(data-c);
  position: absolute;
  inset: 0;
  color: var(--mx-accent);
  -webkit-text-fill-color: var(--mx-accent);
  opacity: var(--hot, 0);
  pointer-events: none;
}
/* ONE soft glow for the whole paragraph, translated to whichever glyph is
   currently at the front — replaces a per-glyph text-shadow that forced 80
   blur convolutions every frame. */
.mx-ignite {
  position: relative;
}
.mx-torch {
  position: absolute;
  top: 0;
  left: 0;
  width: 2.2em;
  height: 1.2em;
  margin: -0.1em 0 0 -1em;
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    rgba(245, 166, 35, 0.28),
    transparent 70%
  );
  opacity: 0;
  pointer-events: none;
  z-index: -1;
  will-change: transform, opacity;
  transition: opacity 0.25s ease;
}
@media (prefers-reduced-motion: reduce) {
  .mx-torch {
    display: none;
  }
}
.mx-ignite .mxw.lit {
  color: rgba(255, 255, 255, 0.94);
}
.mx-ignite .mxw.lit.hot {
  color: var(--mx-accent);
  text-shadow: 0 0 22px rgba(245, 166, 35, 0.35);
}
.mx-ignite .mxw-hi.lit {
  color: var(--mx-accent);
}

/* ------------------------------------------------------------------
   Reduction counters — the animated number keeps tabular width so
   surrounding copy never shivers while it counts.
------------------------------------------------------------------ */
.mx-count-num {
  /* display:contents lets the number rejoin the surrounding text as one
     inline run — inside flex-layout list items a plain span would become
     its own flex item and pick up the container's gap */
  display: contents;
  /* lining-nums is REQUIRED alongside tabular-nums: this span overrides whatever
     the host element set, and Cormorant Garamond defaults to OLD-STYLE figures —
     so tabular-nums on its own rendered "18 / 8 / 5K" as "I / I / oK". */
  font-variant-numeric: tabular-nums lining-nums;
}

/* ------------------------------------------------------------------
   Rising-heat scroll bar
------------------------------------------------------------------ */
.mx-heatbar {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 3px;
  z-index: 1200;
  background: linear-gradient(
    90deg,
    var(--mx-char) 0%,
    var(--mx-accent) 45%,
    var(--mx-gold) 100%
  );
  transform: scaleX(0);
  transform-origin: left center;
  pointer-events: none;
  box-shadow: 0 0 12px rgba(245, 166, 35, 0.35);
}

/* ------------------------------------------------------------------
   Stacked Kitchens — [data-fx="stack"]
   All visual styling lives with its markup in index.html; the layout
   is pure CSS position:sticky. This layer only contributes the
   clip-fix below (sticky dies inside overflow:hidden ancestors).
------------------------------------------------------------------ */
.mx-clip-fix {
  overflow: clip !important;
}

/* ------------------------------------------------------------------
   The Pan at Midnight — [data-fx="flashlight"]
   .mx-flash__dim: the near-dark base (always visible).
   .mx-flash__lit: the true-colour layer, masked to a small soft circle
   at (--mx, --my). Small, tight radius — restraint beats a big glow.
------------------------------------------------------------------ */
.mx-flash {
  position: relative;
  overflow: hidden;
}
/* anything else inside the stage (headline, copy) rides above the image */
.mx-flash > *:not(.mx-flash__dim):not(.mx-flash__lit):not(.mx-flash__hint) {
  position: relative;
  z-index: 2;
}
/* when a hero becomes a flashlight stage, the mx-flash layers own the
   imagery — retire the section's legacy background and dark overlay */
.mx-flash.cinematic-hero {
  background-image: none !important;
}
.mx-flash.cinematic-hero::after {
  content: none !important;
}
.mx-flash__dim,
.mx-flash__lit {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  background-size: cover;
  background-position: center;
}
.mx-flash__dim {
  filter: brightness(0.22) saturate(0.55);
}
.mx-flash__lit {
  --mx: 50%;
  --my: 55%;
  --mx-flash-r: 170px;
  opacity: 0;
  transition: opacity 0.5s ease;
  -webkit-mask-image: radial-gradient(
    circle var(--mx-flash-r) at var(--mx) var(--my),
    rgba(0, 0, 0, 1) 0%,
    rgba(0, 0, 0, 0.85) 55%,
    rgba(0, 0, 0, 0) 100%
  );
  mask-image: radial-gradient(
    circle var(--mx-flash-r) at var(--mx) var(--my),
    rgba(0, 0, 0, 1) 0%,
    rgba(0, 0, 0, 0.85) 55%,
    rgba(0, 0, 0, 0) 100%
  );
}
.mx-flash__lit.is-ambient {
  opacity: 1;
  --mx-flash-r: 200px;
}
/* reduced motion: show the image plainly, no tricks */
.mx-flash--static .mx-flash__dim {
  filter: brightness(0.7) saturate(0.9);
}
.mx-flash--static .mx-flash__lit {
  display: none;
}
.mx-flash__hint {
  position: absolute;
  left: 50%;
  bottom: 1.4rem;
  transform: translateX(-50%);
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.62rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.4);
  pointer-events: none;
  z-index: 2;
}
.mx-flash--static .mx-flash__hint {
  display: none;
}

/* ------------------------------------------------------------------
   Mise en Place timeline — [data-fx="mise"]
   A horizontal track (vertical on mobile) that fills with saffron as
   --jp goes 0 -> 1; an ember rides the tip; nodes ignite; cards rise.
------------------------------------------------------------------ */
.mx-mise__stepper {
  --jp: 0;
  --jp-dot: 0;
  position: relative;
}
.mx-mise__step {
  position: relative;
  padding-top: 2.4rem;
}
.mx-mise__step .mx-mise__node {
  position: absolute;
  top: -7px;
  left: 50%;
  transform: translateX(-50%);
}
.mx-mise__track {
  position: absolute;
  top: 0;
  left: 4%;
  right: 4%;
  height: 2px;
  background: rgba(255, 255, 255, 0.08);
  overflow: visible;
}
@media (max-width: 820px) {
  /* stacked cards: the horizontal line no longer connects anything */
  .mx-mise__track {
    display: none;
  }
  .mx-mise__step .mx-mise__node {
    display: none;
  }
  .mx-mise__step {
    padding-top: 0;
  }
}
.mx-mise__fill {
  position: absolute;
  inset: 0;
  transform-origin: left center;
  transform: scaleX(var(--jp));
  background: linear-gradient(90deg, var(--mx-accent), var(--mx-gold));
  box-shadow: 0 0 10px rgba(245, 166, 35, 0.5);
}
/* the ember riding the fill tip */
.mx-mise__ember {
  position: absolute;
  top: 50%;
  left: calc(var(--jp) * 100%);
  width: 10px;
  height: 10px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: var(--mx-gold);
  box-shadow:
    0 0 12px rgba(255, 215, 0, 0.9),
    0 0 30px rgba(245, 166, 35, 0.5);
  opacity: var(--jp-dot);
  transition: opacity 0.3s ease;
}
.mx-mise__step {
  opacity: 0;
  transform: translateY(24px);
  transition:
    opacity 0.7s var(--mx-ease),
    transform 0.7s var(--mx-ease);
}
.mx-mise__step.in {
  opacity: 1;
  transform: none;
}
.mx-mise__node {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 1.5px solid rgba(245, 166, 35, 0.45);
  background: #080c14;
  transition:
    background 0.45s var(--mx-ease),
    box-shadow 0.45s var(--mx-ease);
}
.mx-mise__node.lit {
  background: var(--mx-accent);
  box-shadow: 0 0 14px rgba(245, 166, 35, 0.65);
}
.mx-mise--static .mx-mise__step {
  opacity: 1;
  transform: none;
}

/* ------------------------------------------------------------------
   Self-drawing SVG line art — [data-fx-draw]
   The engine sets pathLength="1" on every shape; armed shapes start
   fully offset and draw to zero when .mx-drawn lands.
------------------------------------------------------------------ */
svg.mx-draw-armed path,
svg.mx-draw-armed circle,
svg.mx-draw-armed ellipse,
svg.mx-draw-armed line,
svg.mx-draw-armed polyline,
svg.mx-draw-armed rect {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  fill-opacity: 0;
  transition:
    stroke-dashoffset 1.1s var(--mx-ease-soft),
    fill-opacity 0.8s ease 0.7s;
}
svg.mx-drawn path,
svg.mx-drawn circle,
svg.mx-drawn ellipse,
svg.mx-drawn line,
svg.mx-drawn polyline,
svg.mx-drawn rect {
  stroke-dashoffset: 0;
  fill-opacity: 1;
}

/* ------------------------------------------------------------------
   Progressive edge blur — frosted dissolve at the viewport's bottom
------------------------------------------------------------------ */
.mx-edgeblur {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  height: 120px;
  z-index: 1100;
  pointer-events: none;
  opacity: 1;
  transition: opacity 0.45s ease;
}
.mx-edgeblur.is-hidden {
  opacity: 0;
}
.mx-edgeblur__layer {
  position: absolute;
  inset: 0;
}

/* ------------------------------------------------------------------
   Reduced motion — neutralize everything this layer animates.
   (Each JS effect also branches to a static path; this is the belt
   to that suspenders.)
------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  html.mx [data-mxr] {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .mx-ignite .mxw {
    color: rgba(255, 255, 255, 0.94);
    transition: none;
  }
  .mx-ignite .mxw-hi {
    color: var(--mx-accent);
  }
  .mx-heatbar,
  .mx-mise__ember {
    animation: none;
    transition: none;
  }
  svg.mx-draw-armed path,
  svg.mx-draw-armed circle,
  svg.mx-draw-armed ellipse,
  svg.mx-draw-armed line,
  svg.mx-draw-armed polyline,
  svg.mx-draw-armed rect {
    stroke-dashoffset: 0;
    fill-opacity: 1;
    transition: none;
  }
  .mx-edgeblur {
    transition: none;
  }
}
