/* ==========================================================================
   Text reveals — used on EVERY page (moved out of pages/case-content.css,
   2026-11-06, so that file can load only where the media builder is used).
   main.js adds the classes: .has-glint on <html>, .rv on split headings,
   .ln / .is-in on line-revealed paragraphs. Without JS nothing is parked
   invisible.
   ========================================================================== */

/* -- Scroll glint on a heading ----------------------------------------------
   Same machinery as the About accordion's sweep, driven by scroll instead of a
   click: a background-clip:text gradient slides across the glyphs so the words
   are wiped in by a diagonal shine. 345deg + sliding from the bottom end of the
   canvas to the top end gives the SAME front as the accordion, top left to
   bottom right - keep the two in step if either is ever re-aimed.

   Gated on .has-glint, which main.js puts on <html>: without JS the heading
   must stay plain and visible, never parked transparent. Reduced motion gets
   the plain heading too. The canvas is 3x the element height, so parked at
   100% only the transparent end covers the text and at 0% only the solid end
   does. One pass, then main.js stops watching. */
.has-glint .glint {
  background-image: linear-gradient(345deg,
    transparent 44%,
    color-mix(in srgb, var(--text-title) 40%, #fff) 50%,
    var(--text-title) 56%);
  background-size: 100% 300%;
  background-position: 50% 100%;
  background-repeat: no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  transition: background-position 1.1s cubic-bezier(0.33, 1, 0.5, 1);
}
.has-glint .glint.is-lit { background-position: 50% 0%; }
@media (prefers-reduced-motion: reduce) {
  .has-glint .glint {
    background-image: none;
    -webkit-background-clip: border-box;
    background-clip: border-box;
    -webkit-text-fill-color: currentColor;
    color: var(--text-title);
    transition: none;
  }
}


/* -- Staggered per-letter reveal (14islands-style) --------------------------
   Each letter gets its own --d delay, so the paragraph lights up left to right
   like a wipe rather than fading as one block. Taken from 14islands.com, who
   split the text into words and then characters and stagger a --delay per
   character; what you catch mid-flight is a band of still-dim letters.

   We animate COLOUR, not opacity. Theirs parks the letters at opacity 0, which
   means a failed script leaves the text invisible; a dim colour is always
   readable, whatever happens. --rv-dim is mixed from currentColor, so the
   same rule works on a light ground and a dark one without a second variant.

   .rv is added by main.js after it has split the text — the dim state must
   never apply to markup that nothing is going to light up. */
/* 14islands' own recipe, read off their stylesheet rather than guessed at:
   words are inline-block so one never breaks across lines, and each CHARACTER
   is an inline-block that starts invisible and, on wide screens, slightly
   squashed and tilted. --d is the per-character stagger, and it sits in the
   DELAY slot of the shorthand (three values = duration, then a time is read as
   the delay), which is exactly how theirs is written. */
.rv .rv__w { display: inline-block; }
.rv .rv__c { display: inline-block; opacity: 0; }
.rv.is-lit .rv__c {
  transition: opacity 1.2s var(--d, 0s);
  opacity: 1;
}

/* The tilt is desktop-only in theirs too. transform-origin: 0 0 is what makes
   the letters pivot from their own top-left, so the line looks hand-set rather
   than uniformly scaled. */
@media (min-width: 900px) {
  .rv .rv__c {
    transform: scaleY(0.92) rotate(2deg) skew(-2deg);
    transform-origin: 0 0;
  }
  /* Layers only while near/animating (see main.js rvNear/rvLight); settled = released. */
  .rv.is-near .rv__c { will-change: opacity, transform; }
  .rv.is-settled .rv__c { will-change: auto; }
  .rv.is-lit .rv__c {
    transition: opacity 1.2s var(--d, 0s), transform 0.5s var(--d, 0s);
    transform: none;
  }
}

/* The parked state is invisible, so it may only ever exist while JS is there
   to light it: .rv is added after the split. Reduced motion skips it all. */
@media (prefers-reduced-motion: reduce) {
  .rv .rv__c { opacity: 1; transform: none; transition: none; }
}


/* -- Line-by-line reveal --------------------------------------------------
   Each LINE sits in its own overflow: hidden box and rises a full line height
   into view, one after the next (Kajsa 2026-10-06).

   14islands does NOT split lines - checked their page, the paragraph has no
   child elements at all and the clip sits on a wrapper the full height of the
   block. What makes theirs read line by line is that their LIST rows are
   single-line items, each its own masked item (measured: 26px tall boxes on a
   19px line height). Masking per line is the way to get that reading on a
   multi-line paragraph, so that is what this does.

   translateY(100%) of the inner block = exactly one line down, so the line is
   completely behind its own mask before it rises. The lines are rebuilt from
   the measured line breaks, so this has to re-run whenever the text rewraps.
   .is-in is added by JS; without it nothing is ever parked invisible. */
.ln { display: block; overflow: hidden; }
.ln__in {
  display: block;
  transform: translateY(100%);
  opacity: 0;
}
.is-in .ln__in {
  transform: translateY(0);
  opacity: 1;
  transition: transform 0.8s cubic-bezier(0.22, 1, 0.36, 1) var(--d, 0s),
              opacity 0.8s cubic-bezier(0.22, 1, 0.36, 1) var(--d, 0s);
}
@media (prefers-reduced-motion: reduce) {
  .ln__in { transform: none; opacity: 1; transition: none; }
}
