/* Motion + cursor-glow base styles for Remi-web.
 *
 * Warm-light cursor glow that fits the Signal Quarry cream palette.
 *
 * Visibility model on a LIGHT cream background:
 *   The cream paper (#f3eedf, L≈92%) is already bright. To make the glow
 *   readable to the human eye, we must DARKEN the paper where the cursor
 *   sits, not add light. We use `mix-blend-mode: multiply` with a warmer,
 *   more saturated amber (lower lightness, higher chroma than the page).
 *   Multiplying the cream paper by an amber tone produces a warm parchment
 *   pool — paper-under-candlelight done correctly: the area near the cursor
 *   becomes more saturated and slightly darker, the way real ambient light
 *   warms paper rather than blasting it.
 *
 * Color choice rationale:
 *   - Outer halo: oklch(82% 0.10 75) — warm amber, hue 75° (yellow-amber),
 *     chroma 0.10 (visible but not neon), 10 percentage points darker than
 *     the paper so the multiply has measurable luminance work to do.
 *   Hue 75° stays inside the 60–95° amber band — explicitly NOT
 *   teal/cyan/purple/pink (banned per CONTEXT.md).
 *
 * Size rationale (2026-05-24 softening, per user feedback "take out the harsh
 * glow and shrink the lighter glow"): the dense amber-gold inner core layer
 * was removed entirely — it was reading as a harsh spotlight against the
 * editorial cream paper. Only the outer warm halo remains, and its radius
 * dropped 200px → 135px so the single softer layer feels like ambient
 * parchment-warmth, not a flashlight. Opacity held at 0.85 — single-layer
 * output is naturally lighter than the prior two-layer stack, so keeping
 * opacity steady preserves perceptual presence without re-introducing harshness.
 * Outer gradient stops gently re-weighted (75% center → 50% @ 32% → 22% @ 62%
 * → transparent @ 90%) so the falloff stays smooth without the core anchoring
 * the middle.
 *
 * Disabled on touch + reduced-motion via JS (the layer is not even created).
 * CSS here also has belt-and-braces media queries below.
 */

/* ---------- Cursor glow field ---------- */

#remi-cursor-glow {
  position: fixed;
  inset: 0;
  pointer-events: none;
  /* Above page content, below the mobile-nav overlay (z 1000+) and any modal. */
  z-index: 30;

  /* Pointer coords set per-frame from JS. */
  --mx: 50%;
  --my: 50%;
  --glow-intensity: 1;
  --glow-warm: oklch(82% 0.10 75);
  --glow-radius: calc(135px * var(--glow-intensity));

  /* Single soft warm halo. Multi-stop progression keeps the falloff smooth and
     eliminates the ring-edge that a two-stop gradient would create at the
     halo boundary. The dense amber inner core that lived here previously was
     removed on 2026-05-24 — it read as a harsh spotlight on cream. */
  background:
    radial-gradient(
      circle var(--glow-radius) at var(--mx) var(--my),
      color-mix(in oklch, var(--glow-warm) 75%, transparent),
      color-mix(in oklch, var(--glow-warm) 50%, transparent) 32%,
      color-mix(in oklch, var(--glow-warm) 22%, transparent) 62%,
      transparent 90%
    );

  /* Multiply darkens + saturates the cream paper underneath, producing a
     warm parchment pool. soft-light produced no visible change because the
     glow lightness matched the paper lightness. multiply works because the
     glow is substantially darker (L=82) than the paper (L≈92). */
  mix-blend-mode: multiply;
  opacity: 0.85;

  transition: opacity 240ms cubic-bezier(0.16, 1, 0.3, 1);
}

/* Defensive: if reduced-motion is requested after the layer somehow exists,
   hide it without removing the node. */
@media (prefers-reduced-motion: reduce) {
  #remi-cursor-glow {
    display: none !important;
  }
}

/* Hide on coarse pointer too — JS will normally skip creating the layer,
   but this keeps the fallback consistent if the layer was created on a
   hybrid device that later switched into touch mode. */
@media (pointer: coarse) {
  #remi-cursor-glow {
    display: none !important;
  }
}

/* ---------- Base reveal hidden state ---------- */
/* Per-page motion modules apply `data-reveal=""` SYNCHRONOUSLY at module body
   to all elements they will animate, BEFORE the next paint. That suppresses
   the "HTML paints visible → JS snaps blank → animates" FOUC that would
   otherwise occur because module scripts execute deferred. The per-page
   selector lists intentionally EXCLUDE the hero h1 on every page so the LCP
   element paints at first paint with no opacity/transform animation. */

[data-reveal] {
  opacity: 0;
  transform: translate3d(0, 18px, 0);
  will-change: opacity, transform;
}

/* Hero h1 is the LCP element on every page.
   Even if someone accidentally tags it (data-reveal or .sr-word inside it),
   CSS guarantees it paints immediately.
   - .hero h1            → pre-launch landing / methodology / library / library/47
   - .email-hero h1      → pre-launch /weekly/
   - h1.block-heading,
     .tile-row, .tile    → coming-soon / (LCP candidate is the LinkedIn tile
                                          row; the small h1.block-heading sits
                                          inside .linkedin-block). Statement
                                          paragraph also stays paint-immediate. */
.hero h1,
.hero h1[data-reveal],
.hero h1 .sr-word,
.hero h1 .sr-char,
.email-hero h1,
.email-hero h1[data-reveal],
.email-hero h1 .sr-word,
.email-hero h1 .sr-char {
  opacity: 1 !important;
  transform: none !important;
  will-change: auto !important;
}
h1.block-heading,
h1.block-heading[data-reveal],
.linkedin-block,
.linkedin-block[data-reveal],
.tile-row,
.tile-row[data-reveal],
.tile,
.tile[data-reveal],
.statement,
.statement[data-reveal] {
  opacity: 1 !important;
  will-change: auto;
}

/* If the user requests reduced motion, never hide content — show it at rest.
   Reduced-motion is also why tagRevealTargets() bails early: it would not
   add the attribute in the first place, but this CSS belt-and-braces the
   guarantee in case the attribute is present from a different code path. */
@media (prefers-reduced-motion: reduce) {
  [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
    will-change: auto !important;
  }
  .sr-word,
  .sr-char {
    opacity: 1 !important;
    transform: none !important;
    will-change: auto !important;
  }
}

/* Safety net: if the motion module fails to load, content still appears
   after a beat. This `noscript` fallback handles the JS-disabled case. */
.no-motion [data-reveal] {
  opacity: 1 !important;
  transform: none !important;
  will-change: auto !important;
}

/* ---------- Magnetic targets ---------- */
/* Elements wrapped by `magnetic()` should keep their visual identity
   but get a smooth transition origin. We avoid changing pre-existing
   transforms — the utility composes via translate3d on inline style. */
[data-magnetic] {
  transform-origin: center center;
}

/* ---------- Split-text reveal helpers ---------- */
.sr-word,
.sr-char {
  /* Keep selectable text and respect normal line-breaking. */
  display: inline-block;
}

/* ---------- Scroll-position progress bar (library brief) ---------- */
/* Uses transform: scaleX(var(--progress)) — compositor-only, never animates
   the layout-bound `width` property. JS writes --progress as a 0..1 scalar. */
#remi-reading-progress {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: transparent;
  z-index: 60;
  pointer-events: none;
}
#remi-reading-progress::before {
  content: '';
  display: block;
  height: 100%;
  width: 100%;
  transform: scaleX(var(--progress, 0));
  transform-origin: left center;
  background: #2d4a3a;
  transition: transform 80ms linear;
  will-change: transform;
}

@media (prefers-reduced-motion: reduce) {
  #remi-reading-progress::before {
    transition: none;
  }
}

/* ---------- Showcase score bars (landing) ---------- */
/* Bars use scaleX(0 → 1) on enter — JS sets transform-origin: left center
   inline. This rule mirrors the inline setting in CSS so the visual remains
   correct even if JS executes a frame late. */
.showcase-vis .bar-row .f {
  transform-origin: left center;
}

/* ---------- Scroll-pinned sections (landing proof moment) ---------- */
/* Wrapper that contains the pinned visual + scrollable text column.
   The JS scrollLink keeps the visual fixed-ish while text passes by. */
.pin-stack {
  position: relative;
}
.pin-stack > .pin-sticky {
  position: sticky;
  top: 14vh;
  will-change: transform, opacity;
}

@media (max-width: 767px) {
  .pin-stack > .pin-sticky {
    position: static;
  }
}
