/* ============================================================
   WCP §30 EFFECTS KIT · fx.css · v1 (2026-10-13)
   The portable motion + special-effects layer for every site Yu-kai builds. Distilled from
   the yukaichou.com Boneo Motion System (theme-rebuild/yukai-boneo/assets/css/motion.css,
   Yu-kai's locked picks of 2026-07-10) and the octalysisgroup.com motion layer
   (design/tog-brand/octalysisgroup-redesign/site/assets/js/tog-*.js).

   Contract (read WCP §30 before changing anything):
   - Every rule is scoped under html.fx-motion. fx.js adds that class ONLY when the visitor
     has not asked for reduced motion. No JS or reduced motion = the finished, static page.
   - Nothing is ever pre-hidden by CSS alone. Transform and opacity only. Zero layout shift.
   - Per-site look: override the --fx-* properties in the site's own stylesheet.
   ============================================================ */
:root {
  --fx-eo: cubic-bezier(.16, 1, .3, 1);         /* ease-out-expo: entrances, settles   */
  --fx-spring: cubic-bezier(.34, 1.56, .64, 1);  /* spring: pops and presses            */
  --fx-accent: #38C6F2;                          /* orbit spark                         */
  --fx-accent-glow: rgba(56, 198, 242, .75);
  --fx-sweep: rgba(255, 255, 255, .55);          /* hero light sweep; ~.12 on dark heroes */
  --fx-tilt-shadow: 0 18px 40px rgba(43, 166, 232, .28);
  --fx-glint: rgba(255, 255, 255, .22);
}

@keyframes fx-sweep { from { transform: translateX(-160%) skewX(-14deg); } to { transform: translateX(340%) skewX(-14deg); } }
@keyframes fx-spin  { to { transform: rotate(360deg); } }
@keyframes fx-path  { from { offset-distance: 0%; } to { offset-distance: 100%; } }
@keyframes fx-rise  { from { opacity: 0; transform: translateY(26px); } to { opacity: 1; transform: none; } }
@keyframes fx-pass  { 0% { opacity: 1; transform: translateX(-120%) skewX(-14deg); } 100% { opacity: 0; transform: translateX(420%) skewX(-14deg); } }
@keyframes fx-cf8   { 0% { opacity: 1; transform: translate(0, 0) rotate(0) scale(1); } 70% { opacity: 1; }
                      100% { opacity: 0; transform: translate(var(--dx), var(--dy)) rotate(var(--rot)) scale(.7); } }

/* Screen-reader-only copy (used by the count-up). */
.fx-sr { position: absolute !important; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; }

/* 1 · SWEEP — one specular light pass over a finished hero (Boneo 1b). The hero paints once,
   complete; the band only decorates it, so LCP is untouched (lesson of 2026-10-07). */
.fx-sweep { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 3; }
.fx-sweep > i { position: absolute; top: 0; bottom: 0; left: 0; width: 26%; opacity: 0;
  background: linear-gradient(90deg, transparent, var(--fx-sweep), transparent); }
.fx-motion .fx-play > .fx-sweep > i { opacity: 1; animation: fx-sweep 1.05s ease-in-out .2s both; }

/* 2 · ORBIT — a spark circles an emblem (Boneo 1e). "octagon" mode traces the frame itself. */
.fx-orbit { position: absolute; pointer-events: none; z-index: 2; }
.fx-orbit > i { position: absolute; width: 10px; height: 10px; border-radius: 50%;
  background: var(--fx-accent); box-shadow: 0 0 12px 3px var(--fx-accent-glow); }
.fx-motion .fx-orbit--path > i { left: 0; top: 0; offset-anchor: 50% 50%; offset-rotate: 0deg;
  animation: fx-path var(--fx-orbit-s, 9s) linear infinite; }
.fx-motion .fx-orbit--circle { animation: fx-spin var(--fx-orbit-s, 7s) linear infinite; }
.fx-motion .fx-orbit--circle > i { left: 50%; top: 0; margin: -5px 0 0 -5px; }

@media (hover: hover) and (pointer: fine) {
  /* 3 · TILT — levitates toward the cursor on hover (Boneo 1i). The host must not carry its
     own transform. Angles come from fx.js; the static values are the no-pointer fallback. */
  .fx-motion [data-fx~="tilt"] { transition: transform .22s var(--fx-eo), box-shadow .22s var(--fx-eo); }
  .fx-motion [data-fx~="tilt"]:hover {
    transform: perspective(700px) rotateX(var(--fx-rx, 2.5deg)) rotateY(var(--fx-ry, -3deg)) translateY(-3px);
    box-shadow: var(--fx-tilt-shadow); }

  /* 7 · GLINT — a soft highlight follows the cursor over a hero emblem (octalysisgroup #8).
     Uses the host's ::after; the host needs position (fx.js sets relative when static). */
  .fx-motion [data-fx~="glint"]::after { content: ""; position: absolute; inset: 0; pointer-events: none;
    border-radius: inherit; opacity: 0; transition: opacity .3s var(--fx-eo);
    background: radial-gradient(260px circle at var(--fx-gx, 50%) var(--fx-gy, 50%), var(--fx-glint), transparent 62%); }
  .fx-motion [data-fx~="glint"].fx-glint-on::after { opacity: 1; }
}

/* 5 · REVEAL — sections below the fold rise once (Boneo 1l); data-fx-pass adds the light
   pass (1n). fx.js arms ONLY elements still below the viewport at load, pins the end state
   after the animation window, and has a scroll fallback, so nothing can stay blank. */
.fx-motion .fx-armed { opacity: 0; }
.fx-motion .fx-armed.fx-in { opacity: 1; animation: fx-rise .65s var(--fx-eo) both; }
.fx-motion .fx-armed.fx-in[data-fx-i="1"] { animation-delay: .12s; }
.fx-motion .fx-armed.fx-in[data-fx-i="2"] { animation-delay: .24s; }
.fx-motion .fx-armed.fx-done { opacity: 1; animation: none; }
.fx-pass { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
.fx-pass > i { position: absolute; top: 0; bottom: 0; left: 0; width: 22%; opacity: 0;
  background: linear-gradient(90deg, transparent, var(--fx-sweep), transparent); }
.fx-motion .fx-in > .fx-pass > i { animation: fx-pass 1s ease-out .25s both; }

/* 6 · KINETIC — giant lines that move only while the reader scrolls (Boneo 1r, TOG kinetic). */
.fx-motion [data-fx~="kinetic"] { overflow: hidden; }
.fx-motion [data-fx~="kinetic"] [data-fx-dir] { will-change: transform; white-space: nowrap; }

/* 8 · BURST — octagon confetti, only for a real completion (window.fxBurst). */
.fx-cf8 { position: fixed; width: 11px; height: 11px; pointer-events: none; opacity: 0; z-index: 9999;
  clip-path: polygon(29.29% 0, 70.71% 0, 100% 29.29%, 100% 70.71%, 70.71% 100%, 29.29% 100%, 0 70.71%, 0 29.29%);
  animation: fx-cf8 var(--dur, 1.3s) var(--fx-eo) var(--del, 0s) forwards; }
