/* ═══════════════════════════════════════════════════════════════════════
   franticc-type.css — CANONICAL TYPE SYSTEM
   Source of truth for FRANticc's figure font, colour ramps and text/box
   treatments. Approved by the CDO on 2026-08-05.

   Prose lives in docs/BRAND-SYSTEM.md — read that for WHERE each treatment
   is allowed. This file is WHAT they are; the doc is when to use them.

   Several treatments need per-character measurement and cannot work from
   CSS alone. franticc-type.js is their runtime — load it wherever you use
   .ftc-wave, .ftc-grad or .ftc-comet.

   ── THE SMOOTHNESS RULE ────────────────────────────────────────────────
   Never animate font-variation-settings, and never animate a conic-gradient
   angle. Both force a per-frame glyph re-rasterisation or a full gradient
   repaint, and both visibly judder — this was measured, not assumed. Only
   opacity, background-position and stroke-dashoffset are animated below.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── FIGURES ────────────────────────────────────────────────────────────
   Every numeral, ₹, %, decimal, comma and en-dash renders in Manrope.

   The mechanism is deliberate: rather than editing hundreds of font-family
   declarations (which lose to more specific page rules anyway), Manrope is
   registered as an EXTRA FACE of each family already in use, scoped by
   unicode-range to figures only. Faces of the same family compose, so any
   element set in these families draws its digits from Manrope automatically
   — zero markup changes, and letters are untouched because no letter is in
   the range.

   Chosen over Inter because Inter is the BODY font on Logotile and
   compare-tool; Inter figures there would be the same glyphs as the
   surrounding prose, so numbers would stop announcing themselves as data.

   Subset is 5.0 KB, variable 400–800. Declare the weight range honestly —
   claiming 100–900 makes the browser synthesise weights the file does not
   contain instead of clamping to the real ones.

   ⚠ ADDING A FAMILY? If a surface introduces a new text font, add a face
   for it here too, or that surface's numerals will silently keep the old
   font while everything else has switched. That is exactly how the metric
   cards (JetBrains Mono) were missed on the first pass.
   ──────────────────────────────────────────────────────────────────────── */
@font-face{font-family:'DM Sans';font-style:normal;font-weight:400 800;font-display:swap;
  src:url(https://fonts.gstatic.com/l/font?kit=xn7gYHE41ni1AdIRsgW7TdXcTs2rSmAFZv327YnPTv756DciI4os&skey=c397ded15edc0853&v=v20) format('woff2');
  unicode-range:U+25,U+2c,U+2e,U+30-39,U+2013,U+20b9;}
@font-face{font-family:'Urbanist';font-style:normal;font-weight:400 800;font-display:swap;
  src:url(https://fonts.gstatic.com/l/font?kit=xn7gYHE41ni1AdIRsgW7TdXcTs2rSmAFZv327YnPTv756DciI4os&skey=c397ded15edc0853&v=v20) format('woff2');
  unicode-range:U+25,U+2c,U+2e,U+30-39,U+2013,U+20b9;}
@font-face{font-family:'Inter';font-style:normal;font-weight:400 800;font-display:swap;
  src:url(https://fonts.gstatic.com/l/font?kit=xn7gYHE41ni1AdIRsgW7TdXcTs2rSmAFZv327YnPTv756DciI4os&skey=c397ded15edc0853&v=v20) format('woff2');
  unicode-range:U+25,U+2c,U+2e,U+30-39,U+2013,U+20b9;}
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:400 800;font-display:swap;
  src:url(https://fonts.gstatic.com/l/font?kit=xn7gYHE41ni1AdIRsgW7TdXcTs2rSmAFZv327YnPTv756DciI4os&skey=c397ded15edc0853&v=v20) format('woff2');
  unicode-range:U+25,U+2c,U+2e,U+30-39,U+2013,U+20b9;}

/* ── TOKENS ─────────────────────────────────────────────────────────── */
:root{
  /* Canonical green — titles. Deep → emerald → mint. */
  --ftc-g1:#047857; --ftc-g2:#10b981; --ftc-g3:#6ee7b7;
  /* Light green — section headings. A lighter sibling of the title ramp. */
  --ftc-l1:#10b981; --ftc-l2:#34d399; --ftc-l3:#a7f3d0;
  /* Blue — premium / Pro surfaces only. */
  --ftc-b1:#1d4ed8; --ftc-b2:#3b82f6; --ftc-b3:#93c5fd;
  /* Chromatic aberration channels. */
  --ftc-chrom-a:#059669; --ftc-chrom-b:#e11d48;
  /* Motion */
  --ftc-wave-dur:3.8s; --ftc-grad-dur:9s; --ftc-comet-dur:4.2s;
}

/* ── T4 · SMOOTH WEIGHT WAVE ────────────────────────────────────────────
   Per-character weight cascade. A pre-rendered bold copy is stacked over
   the normal one and only OPACITY cross-fades, so the compositor handles
   it — animating the weight axis directly is what juddered.
   Character widths are frozen at REST weight by the JS, so the bold copy
   swells inside a fixed box: zero CLS, and the line never reflows.
   ──────────────────────────────────────────────────────────────────────── */
.ftc-wave .ftc-w{display:inline-block;white-space:nowrap}
.ftc-wave .ftc-c{position:relative;display:inline-block;text-align:center;
  font-variation-settings:"wght" var(--ftc-w0,600)}
.ftc-wave .ftc-c::after{content:attr(data-c);position:absolute;left:50%;top:0;
  transform:translateX(-50%);font-variation-settings:"wght" var(--ftc-w1,900);
  opacity:0;will-change:opacity;
  animation:ftcWave var(--ftc-wave-dur) ease-in-out infinite;
  animation-delay:calc(var(--i) * 44ms)}
@keyframes ftcWave{0%,68%,100%{opacity:0}30%{opacity:1}}

/* ── GRADIENT RAMPS ON SPLIT TEXT ───────────────────────────────────────
   Two traps are handled here, both of which produced invisible text on the
   first attempt:

   1. A .ftc-c is position:relative, so it paints ABOVE the parent's
      background-clip:text background — the gradient vanished entirely.
      Fix: every character carries its OWN copy of the ramp, sized to the
      whole string and offset by that character's position, so the slices
      reassemble into one continuous gradient. --ftc-bgw/--ftc-bgx are
      measured in JS against the TEXT extent, not the element box (an h1 is
      full-bleed, so measuring the element squeezes the ramp into its left
      quarter and the title renders almost solid).

   2. Seamless looping. The tile is 2× the text width and starts AND ends
      on c1, so it repeats without a seam, and the animation translates by
      exactly ONE FULL TILE — landing pixel-identical to frame one. An
      earlier version shifted half a tile and visibly jumped.
   ──────────────────────────────────────────────────────────────────────── */
.ftc-grad .ftc-c,
.ftc-grad .ftc-c::after{
  background-image:linear-gradient(96deg,var(--c1),var(--c2) 25%,var(--c3) 50%,var(--c2) 75%,var(--c1));
  background-size:calc(var(--ftc-bgw,100%) * 2) 100%;
  background-position:var(--ftc-bgx,0) 0;
  background-repeat:repeat-x;
  -webkit-background-clip:text;background-clip:text;
  color:transparent;-webkit-text-fill-color:transparent;
  will-change:background-position;
  animation:ftcGradSlide var(--ftc-grad-dur) linear infinite}
@keyframes ftcGradSlide{
  from{background-position:var(--ftc-bgx,0) 0}
  to{background-position:calc(var(--ftc-bgx,0) - (var(--ftc-bgw,100%) * 2)) 0}}
/* The bold overlay runs BOTH animations. Durations MUST match the base rule
   or the swell's ramp drifts out of step with the glyph beneath it. */
.ftc-grad.ftc-wave .ftc-c::after{
  animation:ftcWave var(--ftc-wave-dur) ease-in-out infinite,
            ftcGradSlide var(--ftc-grad-dur) linear infinite;
  animation-delay:calc(var(--i) * 44ms), 0s}

.ftc-grad-green{--c1:var(--ftc-g1);--c2:var(--ftc-g2);--c3:var(--ftc-g3)}
.ftc-grad-light{--c1:var(--ftc-l1);--c2:var(--ftc-l2);--c3:var(--ftc-l3)}
.ftc-grad-blue {--c1:var(--ftc-b1);--c2:var(--ftc-b2);--c3:var(--ftc-b3)}

/* ── P2 · CHROMATIC ABERRATION ──────────────────────────────────────────
   Colour channels drift apart like print misregistration. Needs data-t on
   the element (the JS sets it).
   Calibration matters: at ±1.6px and full opacity the copies read as extra
   words and the line is unreadable for seconds. Half-opacity, <1px, and a
   cycle that rests in register ~76% of the time is the usable setting.
   Blend mode is surface-dependent — multiply on cream, screen on dark.
   ──────────────────────────────────────────────────────────────────────── */
.ftc-chrom{position:relative;display:inline-block}
.ftc-chrom::before,.ftc-chrom::after{content:attr(data-t);position:absolute;
  left:0;top:0;pointer-events:none;white-space:nowrap;opacity:.5}
.ftc-chrom::before{color:var(--ftc-chrom-a);mix-blend-mode:multiply;
  animation:ftcChL 6s ease-in-out infinite}
.ftc-chrom::after{color:var(--ftc-chrom-b);mix-blend-mode:multiply;
  animation:ftcChR 6s ease-in-out infinite}
.ftc-dark .ftc-chrom::before,.ftc-dark .ftc-chrom::after{mix-blend-mode:screen}
@keyframes ftcChL{0%,38%{transform:none}50%{transform:translate(-.9px,-.35px)}62%,100%{transform:none}}
@keyframes ftcChR{0%,38%{transform:none}50%{transform:translate(.9px,.35px)}62%,100%{transform:none}}

/* ── P3 · TERMINAL HIGHLIGHT ────────────────────────────────────────────
   A solid marker block sweeps through and inverts what it covers. Hard
   edges read as machinery rather than ambience. Bloomberg register.
   ──────────────────────────────────────────────────────────────────────── */
.ftc-term{position:relative;display:inline-block;overflow:hidden}
.ftc-term::after{content:'';position:absolute;top:-.08em;bottom:-.08em;width:2.6em;
  background:var(--ftc-g2);mix-blend-mode:difference;
  animation:ftcTerm 6s cubic-bezier(.5,0,.5,1) infinite}
@keyframes ftcTerm{0%{left:-3em}100%{left:100%}}

/* ── P8 · PRISM EDGE ────────────────────────────────────────────────────
   Glyph bodies stay neutral and legible; colour lives only in the outline.
   The only treatment safe on long body copy. Needs data-t.
   ──────────────────────────────────────────────────────────────────────── */
.ftc-prism{position:relative;display:inline-block;color:transparent;
  -webkit-text-stroke:.6px var(--ftc-g1)}
.ftc-prism::after{content:attr(data-t);position:absolute;left:0;top:0;
  white-space:nowrap;-webkit-text-stroke:0;color:#0f172a;opacity:.88}
.ftc-dark .ftc-prism::after{color:#f8fafc}
.ftc-prism::before{content:attr(data-t);position:absolute;left:0;top:0;
  white-space:nowrap;-webkit-text-stroke:1.4px transparent;
  background:linear-gradient(96deg,var(--ftc-g3),#38bdf8 40%,var(--ftc-g2) 70%,var(--ftc-g3));
  background-size:220% 100%;-webkit-background-clip:text;background-clip:text;
  color:transparent;animation:ftcPrism 8s ease-in-out infinite}
@keyframes ftcPrism{0%,100%{background-position:0 0}50%{background-position:100% 0}}

/* ── T6 · AURORA DRIFT ──────────────────────────────────────────────────
   A highlight travels through a green ramp. Deliberately avoids the deep
   #047857 stop: at subtitle size the dark end reads as near-black on cream,
   which made the first version look like ordinary dark text.
   ──────────────────────────────────────────────────────────────────────── */
.ftc-aurora{background:linear-gradient(96deg,#0f766e,#10b981 38%,#5eead4 50%,#10b981 62%,#0f766e);
  background-size:280% 100%;-webkit-background-clip:text;background-clip:text;
  color:transparent;-webkit-text-fill-color:transparent;
  will-change:background-position;animation:ftcAurora 7s ease-in-out infinite}
@keyframes ftcAurora{0%{background-position:100% 0}100%{background-position:0% 0}}

/* ── T5 · ODOMETER ──────────────────────────────────────────────────────
   Digits physically roll in a masked window. Tabular figures pin the width
   so nothing reflows as values change. Built by the JS.
   ──────────────────────────────────────────────────────────────────────── */
.ftc-odo{display:inline-flex;gap:1px;font-variant-numeric:tabular-nums}
.ftc-odo .ftc-odo-col{height:1.1em;overflow:hidden;line-height:1.1}
.ftc-odo .ftc-odo-strip{display:flex;flex-direction:column;
  transition:transform 1.6s cubic-bezier(.22,1,.36,1)}
.ftc-odo .ftc-odo-strip i{font-style:normal;height:1.1em}

/* ── T8 · SPOTLIGHT ─────────────────────────────────────────────────────
   Text rests dim; the pointer is the reading light. Adds no layout box, so
   it never changes size.
   ⚠ DESKTOP ONLY — pointer-driven, does nothing on touch. Never let it
   carry meaning, and do not put it on headline figures: dimming the number
   an investor came for fights readability (tried on metric cards, removed).
   ──────────────────────────────────────────────────────────────────────── */
.ftc-spot{position:relative;color:rgba(15,23,42,.26)}
.ftc-spot::after{content:attr(data-t);position:absolute;left:0;top:0;
  white-space:nowrap;color:#0f172a;pointer-events:none;
  -webkit-mask-image:radial-gradient(circle 90px at var(--mx,-999px) var(--my,-999px),#000 0,transparent 70%);
  mask-image:radial-gradient(circle 90px at var(--mx,-999px) var(--my,-999px),#000 0,transparent 70%)}

/* ── B2 · COMET BORDER ──────────────────────────────────────────────────
   An SVG rect with the box's own border-radius, animated via
   stroke-dashoffset, so the light traces the REAL rounded outline at
   constant speed.
   Do NOT rebuild this with a conic-gradient: a conic sweeps by ANGLE from
   the box centre, so on a wide rounded box the light cuts across corners
   and changes speed — it visibly traces a square path around a rounded
   box, and repainting the gradient every frame judders.
   pathLength="100" normalises speed across box sizes. The JS sizes the
   viewBox to the real pixel box so the corner radius stays circular.
   ──────────────────────────────────────────────────────────────────────── */
.ftc-comet{position:relative}
.ftc-comet > svg.ftc-comet-svg{position:absolute;inset:0;width:100%;height:100%;
  overflow:visible;pointer-events:none;z-index:3}
.ftc-comet-svg rect.ftc-trk{fill:none;stroke:rgba(15,23,42,.13);stroke-width:1.4}
.ftc-dark .ftc-comet-svg rect.ftc-trk{stroke:rgba(255,255,255,.12)}
.ftc-comet-svg rect.ftc-tail{fill:none;stroke:var(--ftc-g2);stroke-width:1.6;
  stroke-linecap:round;opacity:.34;stroke-dasharray:14 86;
  animation:ftcDash var(--ftc-comet-dur) linear infinite}
.ftc-comet-svg rect.ftc-head{fill:none;stroke:var(--ftc-g3);stroke-width:1.9;
  stroke-linecap:round;stroke-dasharray:4 96;
  filter:drop-shadow(0 0 5px var(--ftc-g2));
  animation:ftcDash var(--ftc-comet-dur) linear infinite}
@keyframes ftcDash{to{stroke-dashoffset:-100}}

/* ── OFF-SCREEN PAUSE ───────────────────────────────────────────────────
   The JS toggles .ftc-paused via IntersectionObserver so nothing burns CPU
   while scrolled out of view. */
.ftc-paused .ftc-c::after,
.ftc-paused.ftc-aurora,
.ftc-paused .ftc-comet-svg rect,
.ftc-paused.ftc-chrom::before,.ftc-paused.ftc-chrom::after,
.ftc-paused.ftc-term::after,
.ftc-paused.ftc-prism::before{animation-play-state:paused}
.ftc-grad.ftc-paused .ftc-c{animation-play-state:paused}

/* ── REDUCED MOTION ─────────────────────────────────────────────────────
   Every treatment degrades to a legible static state. Gradients keep their
   colour; only movement stops. */
@media (prefers-reduced-motion:reduce){
  .ftc-wave .ftc-c::after,.ftc-grad .ftc-c,.ftc-grad .ftc-c::after,
  .ftc-aurora,.ftc-chrom::before,.ftc-chrom::after,.ftc-term::after,
  .ftc-prism::before,.ftc-comet-svg rect,.ftc-odo .ftc-odo-strip{
    animation:none!important;transition:none!important}
  .ftc-chrom::before,.ftc-chrom::after{opacity:0}
}
