/* =========================================================================
   Trés Bob — kinetic typography poster (perf-focused build)

   Hard rules (so Chrome stays at 60 fps):
   - Only transform / opacity / translate / scale / rotate per frame.
   - No animated filter, no animated box-shadow, no animated background.
   - No mix-blend-mode on large surfaces.
   - No mask. No backdrop-filter. No SVG noise grain.
   - Static `filter:` and `background:` are fine; animated versions repaint.
   ========================================================================= */

@property --p   { syntax: '<number>'; inherits: true; initial-value: 0; }
@property --hue { syntax: '<number>'; inherits: true; initial-value: 0; }
@property --vel { syntax: '<number>'; inherits: true; initial-value: 0; }

@layer reset, base, stage, bg, ghosts, particles, title, overlay, velocity, a11y;

/* ---------- reset ---------- */
@layer reset {
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
  html, body { height: 100%; }
  body { line-height: 1; -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
  :focus-visible { outline: 2px solid currentColor; outline-offset: 4px; }
}

/* ---------- base ---------- */
@layer base {
  :root {
    --paper:   oklch(96% 0.03 85);
    --paper-2: oklch(92% 0.04 80);
    --ink:     oklch(15% 0.02 280);
    --pink:    oklch(70% 0.25 5);
    --blue:    oklch(58% 0.22 245);
    --mustard: oklch(82% 0.18 85);
    --emerald: oklch(60% 0.18 165);

    --serif: 'Bodoni Moda', 'Didot', Georgia, serif;
    --mono:  'Space Mono', ui-monospace, monospace;

    color-scheme: light dark;
  }

  body {
    background: var(--paper);
    color: var(--ink);
    font-family: var(--serif);
    overflow-x: hidden;
    animation: progress linear both;
    animation-timeline: scroll(root);
  }

  /* Hide the scrollbar but keep the page scrollable (wheel, touch, keys all still work). */
  html { scrollbar-width: none; }              /* Firefox */
  html { -ms-overflow-style: none; }           /* old Edge */
  html::-webkit-scrollbar { width: 0; height: 0; display: none; } /* Chrome / Safari / new Edge */
  @keyframes progress { to { --p: 1; --hue: 360; } }

  @media (prefers-color-scheme: dark) {
    :root {
      --paper:   oklch(14% 0.02 280);
      --paper-2: oklch(20% 0.03 280);
      --ink:     oklch(96% 0.03 85);
    }
  }

  ::selection { background: var(--pink); color: var(--paper); }
}

/* ---------- driver + stage ---------- */
@layer stage {
  .driver { height: 500vh; position: relative; }

  .stage {
    position: sticky;
    top: 0;
    height: 100vh;
    height: 100svh;
    width: 100%;
    overflow: hidden;
    isolation: isolate;
    /* a static, painted gradient mesh replaces the four blurred blobs.
       Painted once when the layer is created, then GPU-composited each
       frame — zero repaint cost. */
    background:
      radial-gradient(50vmax 50vmax at 18% 28%, color-mix(in oklch, var(--pink)    45%, transparent) 0%, transparent 60%),
      radial-gradient(45vmax 45vmax at 82% 22%, color-mix(in oklch, var(--blue)    40%, transparent) 0%, transparent 60%),
      radial-gradient(55vmax 55vmax at 72% 82%, color-mix(in oklch, var(--mustard) 45%, transparent) 0%, transparent 60%),
      radial-gradient(40vmax 40vmax at 12% 78%, color-mix(in oklch, var(--emerald) 35%, transparent) 0%, transparent 60%),
      var(--paper);
  }

  .layer {
    position: absolute;
    inset: 0;
    pointer-events: none;
  }
}

/* ---------- bg rings (border-only, no fill, no blur) ---------- */
@layer bg {
  .layer--bg { z-index: 1; }

  .ring {
    position: absolute;
    top: 50%; left: 50%;
    border-radius: 50%;
    border: 1px solid color-mix(in oklch, var(--ink) 12%, transparent);
    translate: -50% -50%;
    will-change: transform;
    animation: ring-expand linear both;
    animation-timeline: scroll(root);
  }
  .ring--1 { width:  40vmin; aspect-ratio: 1; --grow: 3;   }
  .ring--2 { width:  70vmin; aspect-ratio: 1; --grow: 2.4; }
  .ring--3 { width: 110vmin; aspect-ratio: 1; --grow: 2;   }
  .ring--4 { width: 160vmin; aspect-ratio: 1; --grow: 1.7; }

  @keyframes ring-expand {
    0%   { scale: 0.4;            border-color: color-mix(in oklch, var(--ink)  8%, transparent); }
    50%  { scale: 1;              border-color: color-mix(in oklch, var(--pink) 35%, transparent); }
    100% { scale: var(--grow, 2); border-color: color-mix(in oklch, var(--blue) 50%, transparent); }
  }
}

/* ---------- ghost typography (transform + opacity only) ---------- */
@layer ghosts {
  .layer--ghosts { z-index: 2; }

  .ghost {
    position: absolute;
    top: 50%; left: 50%;
    font-family: var(--serif);
    font-style: italic;
    font-weight: 900;
    font-size: clamp(2.5rem, 9vw, 9rem);
    color: color-mix(in oklch, var(--ink) 70%, transparent);
    white-space: nowrap;
    user-select: none;
    transform-origin: 50% 50%;
    will-change: transform, opacity;
    animation: ghost-sweep linear both;
    animation-timeline: scroll(root);
  }

  .g--1 { --tx0: -55vw; --ty0: -30vh; --tx1:  60vw; --ty1:  35vh; --r0:  -8deg; --r1:  540deg; --s0:.4; --s1:1.3; --o:.32; }
  .g--2 { --tx0:  55vw; --ty0: -25vh; --tx1: -55vw; --ty1:  40vh; --r0:  12deg; --r1: -420deg; --s0:.5; --s1:1.5; --o:.28; }
  .g--3 { --tx0:  -5vw; --ty0:  40vh; --tx1:  10vw; --ty1: -45vh; --r0:  -4deg; --r1:  720deg; --s0:.6; --s1:1.8; --o:.24; }
  .g--4 { --tx0:  30vw; --ty0:  20vh; --tx1: -45vw; --ty1: -20vh; --r0:   6deg; --r1: -540deg; --s0:.7; --s1:2.0; --o:.20; }
  .g--5 { --tx0: -40vw; --ty0:  25vh; --tx1:  50vw; --ty1: -35vh; --r0:  -2deg; --r1:  420deg; --s0:.5; --s1:1.7; --o:.26; }
  .g--6 { --tx0:   0vw; --ty0: -50vh; --tx1:   0vw; --ty1:  55vh; --r0:   0deg; --r1:  900deg; --s0:.3; --s1:1.1; --o:.36; }

  @keyframes ghost-sweep {
    0% {
      opacity: 0;
      translate: calc(-50% + var(--tx0)) calc(-50% + var(--ty0));
      rotate: var(--r0);
      scale: var(--s0);
    }
    50% { opacity: var(--o); }
    100% {
      opacity: 0;
      translate: calc(-50% + var(--tx1)) calc(-50% + var(--ty1));
      rotate: var(--r1);
      scale: var(--s1);
    }
  }
}

/* ---------- particles (transform + opacity only) ---------- */
@layer particles {
  .layer--particles { z-index: 3; }

  .dot {
    position: absolute;
    top: 50%; left: 50%;
    width: var(--sz, 10px); aspect-ratio: 1;
    background: var(--c, var(--ink));
    border-radius: 50%;
    will-change: transform, opacity;
    animation: dot-orbit linear both;
    animation-timeline: scroll(root);
  }

  .d--1 { --sz: 12px; --c: var(--pink);    --tx: -30vw; --ty: -20vh; --rot:  540deg; }
  .d--2 { --sz: 16px; --c: var(--blue);    --tx:  35vw; --ty: -15vh; --rot: -420deg; }
  .d--3 { --sz:  8px; --c: var(--mustard); --tx: -25vw; --ty:  30vh; --rot:  720deg; }
  .d--4 { --sz: 14px; --c: var(--emerald); --tx:  20vw; --ty:  35vh; --rot: -540deg; }
  .d--5 { --sz: 20px; --c: var(--pink);    --tx: -40vw; --ty:  10vh; --rot:  360deg; }
  .d--6 { --sz: 10px; --c: var(--blue);    --tx:  25vw; --ty: -30vh; --rot: -540deg; }

  @keyframes dot-orbit {
    0%   { opacity: 0; translate: -50% -50%; rotate: 0deg; scale: .4; }
    8%   { opacity: 1; }
    92%  { opacity: 1; }
    100% { opacity: 0; translate: calc(-50% + var(--tx)) calc(-50% + var(--ty)); rotate: var(--rot); scale: 0; }
  }
}

/* ---------- title (the anchor) ---------- */
@layer title {
  .kicker {
    position: absolute;
    top: 8%; left: 50%;
    translate: -50% 0;
    font-family: var(--mono);
    font-size: clamp(.65rem, 1.2vw, .9rem);
    letter-spacing: .4em;
    text-transform: uppercase;
    color: color-mix(in oklch, var(--ink) 60%, transparent);
    display: flex; gap: 1rem; align-items: center;
    white-space: nowrap;
    z-index: 6;
    animation: kicker-shift linear both;
    animation-timeline: scroll(root);
  }
  .kicker span { color: var(--pink); }
  @keyframes kicker-shift {
    0%   { opacity: 1; letter-spacing: .4em;  translate: -50% 0; }
    50%  { opacity: .6; letter-spacing: .8em; translate: -50% -2vh; }
    100% { opacity: 0; letter-spacing: 1.5em; translate: -50% -4vh; }
  }

  .title {
    position: absolute;
    inset: 0;
    display: grid;
    place-content: center;
    text-align: center;
    font-weight: 900;
    font-size: clamp(4rem, 18vw, 18rem);
    line-height: 0.82;
    letter-spacing: -0.04em;
    z-index: 5;
    will-change: transform;
    animation: title-arc linear both;
    animation-timeline: scroll(root);
  }
  @keyframes title-arc {
    0%   { scale: 1;    rotate: 0deg;  }
    25%  { scale: 1.15; rotate: -1deg; }
    50%  { scale: .85;  rotate: 2deg;  }
    75%  { scale: 1.6;  rotate: -3deg; letter-spacing: 0.06em; }
    100% { scale: 3.5;  rotate: 6deg;  letter-spacing: 0.25em; }
  }

  /* each word is a stacking-context container; ::before and ::after render
     the same text shifted left (red) and right (blue) — chromatic
     aberration that only costs an opacity tweak per frame. */
  .word {
    display: block;
    position: relative;
    isolation: isolate;
    will-change: transform;
  }
  .word--1 {
    color: var(--ink);
    animation: word1-dance linear both;
    animation-timeline: scroll(root);
  }
  .word--2 {
    font-style: italic;
    color: var(--pink);
    animation: word2-dance linear both;
    animation-timeline: scroll(root);
  }
  @keyframes word1-dance {
    0%   { translate: 0 0;        rotate: 0deg; }
    50%  { translate: -2vw -1vh;  rotate: -2deg; }
    100% { translate: -10vw -6vh; rotate: -12deg; opacity: .3; }
  }
  @keyframes word2-dance {
    0%   { translate: 0 0;        rotate: 0deg; }
    50%  { translate: 3vw 1vh;    rotate: 4deg; }
    100% { translate: 12vw 5vh;   rotate: 18deg; opacity: .3; }
  }

  .subtitle {
    position: absolute;
    bottom: 18%; left: 50%;
    translate: -50% 0;
    display: flex; gap: clamp(.4rem, 1.5vw, 1.5rem);
    align-items: baseline;
    font-size: clamp(1rem, 2.4vw, 2.2rem);
    letter-spacing: .04em;
    white-space: nowrap;
    z-index: 6;
  }
  .sub { display: inline-block; will-change: transform, opacity; }
  .sub--1, .sub--2 { font-style: italic; }
  .sub--amp { color: var(--pink); font-style: italic; font-weight: 700; }

  .sub--1 { animation: sub1-fly linear both; animation-timeline: scroll(root); }
  .sub--2 { animation: sub2-fly linear both; animation-timeline: scroll(root); }
  .sub--amp { animation: amp-spin linear both; animation-timeline: scroll(root); }

  @keyframes sub1-fly {
    0%   { translate: 0 0;       opacity: 1; }
    40%  { translate: -1vw 0;    opacity: 1; }
    70%  { translate: -25vw 0;   opacity: 1; scale: 1.2; }
    100% { translate: -55vw 8vh; opacity: 0; scale: 1.6; rotate: -20deg; }
  }
  @keyframes sub2-fly {
    0%   { translate: 0 0;       opacity: 1; }
    40%  { translate: 1vw 0;     opacity: 1; }
    70%  { translate: 25vw 0;    opacity: 1; scale: 1.2; }
    100% { translate: 55vw -8vh; opacity: 0; scale: 1.6; rotate: 20deg; }
  }
  @keyframes amp-spin {
    0%   { rotate: 0deg;   scale: 1; }
    50%  { rotate: 540deg; scale: 1.8; }
    100% { rotate: 1440deg; scale: 4; opacity: 0; }
  }

  /* slogan — opacity+scale only, no blur (blur is GPU-expensive) */
  .slogan {
    position: absolute;
    top: 50%; left: 50%;
    translate: -50% -50%;
    font-style: italic;
    font-weight: 700;
    font-size: clamp(1.6rem, 5vw, 5rem);
    letter-spacing: -0.01em;
    white-space: nowrap;
    color: var(--ink);
    display: flex;
    gap: clamp(1rem, 4vw, 4rem);
    z-index: 7;
    opacity: 0;
    will-change: transform, opacity;
    animation:
      slogan-reveal linear both,
      slogan-crawl 18s linear infinite;
    animation-timeline: scroll(root), auto;
  }
  .slogan > .phrase { flex: none; }
  @keyframes slogan-reveal {
    0%, 65% { opacity: 0; scale: .6; }
    80%     { opacity: 1; scale: 1; }
    100%    { opacity: 1; scale: 1.1; }
  }
  @keyframes slogan-crawl {
    from { translate: -50% -50%; }
    to   { translate: -120% -50%; }
  }

  /* Each phrase is one grid cell containing two stacked language layers.
     The cell sizes itself to the wider of the two, so the layout never
     shifts when the text swaps. */
  .slogan .phrase {
    display: inline-grid;
    grid-template-areas: "stack";
    align-items: baseline;
  }
  .slogan .lang {
    grid-area: stack;
    will-change: opacity;
  }
  /* step-end keeps opacity at the previous keyframe's value until the next
     keyframe time is hit, then snaps. This produces the hard "blink" feel
     instead of crossfading. */
  .slogan .lang--fr {
    animation: lang-flicker-fr 9s step-end infinite;
  }
  .slogan .lang--en {
    animation: lang-flicker-en 9s step-end infinite;
  }
  /* slight per-phrase phase offsets so the three slogans don't all flip
     in lockstep — feels more like radio interference */
  .slogan .phrase:nth-child(2) .lang { animation-delay: -3.1s; }
  .slogan .phrase:nth-child(3) .lang { animation-delay: -6.4s; }

  /* FR: on (0-45%), flicker-off, EN takes over (50-95%), flicker-on, back to on */
  @keyframes lang-flicker-fr {
    0%    { opacity: 1; }
    45%   { opacity: 1; }
    46%   { opacity: 0; }
    47%   { opacity: 1; }
    48%   { opacity: 0; }
    49%   { opacity: 1; }
    50%   { opacity: 0; }   /* settle off — EN now visible */
    95%   { opacity: 0; }
    96%   { opacity: 1; }   /* flicker back to FR */
    97%   { opacity: 0; }
    98%   { opacity: 1; }
    99%   { opacity: 0; }
    100%  { opacity: 1; }
  }
  /* EN: mirror of FR — off, flicker-on, visible, flicker-off */
  @keyframes lang-flicker-en {
    0%    { opacity: 0; }
    45%   { opacity: 0; }
    46%   { opacity: 1; }
    47%   { opacity: 0; }
    48%   { opacity: 1; }
    49%   { opacity: 0; }
    50%   { opacity: 1; }   /* settle on */
    95%   { opacity: 1; }
    96%   { opacity: 0; }
    97%   { opacity: 1; }
    98%   { opacity: 0; }
    99%   { opacity: 1; }
    100%  { opacity: 0; }
  }
}

/* ---------- overlay (vignette only) ---------- */
@layer overlay {
  .overlay { z-index: 9; }

  /* static radial-gradient; opacity changes only (composite-friendly) */
  .vignette {
    position: absolute; inset: 0;
    background:
      radial-gradient(120% 90% at 50% 50%,
        transparent 50%,
        color-mix(in oklch, var(--ink) 40%, transparent) 100%);
    opacity: 0;
    will-change: opacity;
    animation: vignette-on linear both;
    animation-timeline: scroll(root);
  }
  @keyframes vignette-on {
    0%   { opacity: 0; }
    50%  { opacity: .2; }
    100% { opacity: .9; }
  }

  .hint {
    position: absolute;
    bottom: 4%; left: 50%;
    translate: -50% 0;
    display: flex; gap: .9rem;
    font-family: var(--mono);
    font-size: .72rem;
    letter-spacing: .35em;
    text-transform: uppercase;
    color: color-mix(in oklch, var(--ink) 55%, transparent);
    z-index: 10;
    will-change: opacity, transform;
    animation: hint-fade linear both;
    animation-timeline: scroll(root);
  }
  .hint span:nth-child(2),
  .hint span:nth-child(4) { color: var(--pink); }
  @keyframes hint-fade {
    0%   { opacity: 1; translate: -50% 0; }
    10%  { opacity: 1; }
    25%  { opacity: 0; translate: -50% 2vh; }
    100% { opacity: 0; }
  }
}

/* =========================================================================
   VELOCITY LAYER — reactions to scroll *speed*, not position.
   `--vel` (0 → 1) is set every frame by script.js. Only transform & opacity
   are touched here so the GPU never has to repaint a viewport-sized surface.
   ========================================================================= */
@layer velocity {

  /* Chromatic aberration via pseudo-element clones. Each ::before/::after
     uses content:attr(data-text) to render the same word, offset L/R, with
     opacity tied to --vel. No repaint per frame — opacity & translate are
     both GPU-composited properties. */
  .word::before,
  .word::after {
    content: attr(data-text);
    position: absolute;
    inset: 0;
    z-index: -1;          /* sit behind the real text (.word has isolation) */
    pointer-events: none;
    font: inherit;
    font-style: inherit;
    color: currentColor;
    text-align: inherit;
    opacity: calc(var(--vel, 0) * .85);
    will-change: transform, opacity;
  }
  .word::before {
    color: oklch(70% 0.32 25);
    translate: calc(var(--vel, 0) * -14px) 0;
  }
  .word::after {
    color: oklch(60% 0.30 250);
    translate: calc(var(--vel, 0) * 14px) 0;
  }

  /* slight wind tilt on the ghost layer at speed (composite-only) */
  .layer--ghosts {
    rotate: calc(var(--vel, 0) * 1.2deg);
    scale: calc(1 + var(--vel, 0) * 0.04);
  }

  /* micro-shake on the stage. translate is composited; --vel gates the
     magnitude so at rest the shake collapses to 0 0 (visually nothing). */
  .stage {
    animation: stage-shake .12s steps(8, end) infinite;
  }
  @keyframes stage-shake {
    0%    { translate: 0 0; }
    12.5% { translate: calc(var(--vel, 0) *  2.5px) calc(var(--vel, 0) * -1.5px); }
    25%   { translate: calc(var(--vel, 0) * -2px)   calc(var(--vel, 0) *  2px); }
    37.5% { translate: calc(var(--vel, 0) *  1.5px) calc(var(--vel, 0) *  1.8px); }
    50%   { translate: calc(var(--vel, 0) * -1.8px) calc(var(--vel, 0) * -2.2px); }
    62.5% { translate: calc(var(--vel, 0) *  2.2px) calc(var(--vel, 0) *  1.2px); }
    75%   { translate: calc(var(--vel, 0) * -2.5px) calc(var(--vel, 0) * -1px); }
    87.5% { translate: calc(var(--vel, 0) *  1px)   calc(var(--vel, 0) * -2px); }
    100%  { translate: 0 0; }
  }
}

/* ---------- a11y / fallbacks ---------- */
@layer a11y {
  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
      animation-duration: 0.001ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.001ms !important;
    }
    .ghost, .dot, .ring { display: none; }
    .driver { height: 100vh; }
  }

  @supports not (animation-timeline: scroll()) {
    .driver { height: 100vh; }
    .ghost, .dot { opacity: .15; }
    .title { animation: none; }
  }
}
