/*! fx.css v1.0.0 · 2026-08-11 · fc-brands @fcv */
/* ============================================================
   TEMPER — hover / ambient FX  (temperfest.netlify.app/shared/fx.css)
   Add a class to any <img> (or element). Works on a bare <img> —
   no wrapper, no pseudo-elements. Respects prefers-reduced-motion.

   .glitch-hover     -> quick ONE-SHOT glitch on hover (re-hover replays)
   .glitch-img-reg   -> auto glitch every 10s (no hover). Image stays fully
                        visible — a fast, punchy RGB-split shake, no slicing.
   ============================================================ */

/* ---- 1) glitch-hover : full-intensity, on hover, one shot ----
   No clip-path: clip-path clips the element's HIT region (not just pixels),
   which blocks clicks on links mid-animation. Transform + filter keep it
   fully clickable while still reading as a punchy glitch. */
/* triggered by fx.js (adds .gh-go on hover, removes on animationend) so the one-shot
   always PLAYS THROUGH even if you move the mouse away mid-glitch. */
.glitch-hover.gh-go{ animation:tmpGlitchHover .42s linear var(--glitch-delay,0s) 1; will-change:transform,filter; }
@keyframes tmpGlitchHover{
  0%   { transform:translate(0,0) skewX(0);          filter:none; }
  8%   { transform:translate(-5px,1px) skewX(-7deg);
         filter:drop-shadow(6px 0 rgba(255,0,72,.7)) drop-shadow(-6px 0 rgba(0,214,255,.7)); }
  18%  { transform:translate(5px,-1px) skewX(5deg);
         filter:drop-shadow(-5px 0 rgba(255,0,72,.65)) drop-shadow(5px 0 rgba(0,214,255,.65)); }
  30%  { transform:translate(-4px,1px) skewX(-3deg);
         filter:drop-shadow(6px 0 rgba(255,0,72,.6)) drop-shadow(-6px 0 rgba(0,214,255,.6)); }
  42%  { transform:translate(4px,0) skewX(4deg);
         filter:drop-shadow(-5px 0 rgba(255,0,72,.6)) drop-shadow(5px 0 rgba(0,214,255,.6)); }
  56%  { transform:translate(-3px,-1px) skewX(-2deg);
         filter:drop-shadow(4px 0 rgba(255,0,72,.5)) drop-shadow(-4px 0 rgba(0,214,255,.5)); }
  70%  { transform:translate(3px,1px);
         filter:drop-shadow(-3px 0 rgba(255,0,72,.45)) drop-shadow(3px 0 rgba(0,214,255,.45)); }
  84%  { transform:translate(-1px,0) skewX(1deg);    filter:none; }
  100% { transform:translate(0,0) skewX(0);          filter:none; }
}

/* ---- 1b) glitch-link : slice glitch on an IMAGE inside a link/frame ----
   Keeps the hard slice look AND stays clickable. Put .glitch-link on the
   link/frame and .glitch-link-img on the image inside it. clip-path clips only
   the image's own hit region — the link's hit box is untouched — and
   pointer-events:none on the image routes every click/hover to the link.
   Triggered by the FRAME's hover so it can't cut out when the image jitters. */
.glitch-link-img{ pointer-events:none; }
.glitch-link.gh-go .glitch-link-img{ animation:tmpGlitchSlice .42s linear var(--glitch-delay,0s) 1; will-change:transform,clip-path,filter; }
@keyframes tmpGlitchSlice{
  0%  { transform:translate(0,0) skewX(0);          clip-path:inset(0 0 0 0);   filter:none; }
  10% { transform:translate(-4px,1px) skewX(-6deg); clip-path:inset(8% 0 62% 0);
        filter:drop-shadow(4px 0 rgba(255,0,72,.65)) drop-shadow(-4px 0 rgba(0,214,255,.65)); }
  22% { transform:translate(4px,-1px) skewX(4deg);  clip-path:inset(54% 0 20% 0);
        filter:drop-shadow(-3px 0 rgba(255,0,72,.6)) drop-shadow(3px 0 rgba(0,214,255,.6)); }
  34% { transform:translate(-3px,1px);              clip-path:inset(26% 0 44% 0);
        filter:drop-shadow(5px 0 rgba(255,0,72,.6)) drop-shadow(-5px 0 rgba(0,214,255,.6)); }
  46% { transform:translate(3px,0) skewX(-3deg);    clip-path:inset(72% 0 6% 0);  filter:none; }
  58% { transform:translate(-2px,-1px);             clip-path:inset(12% 0 70% 0);
        filter:drop-shadow(-4px 0 rgba(255,0,72,.5)) drop-shadow(4px 0 rgba(0,214,255,.5)); }
  70% { transform:translate(2px,1px);               clip-path:inset(40% 0 30% 0); filter:none; }
  84% { transform:translate(-1px,0);                clip-path:inset(0 0 0 0);      filter:none; }
  100%{ transform:translate(0,0);                   clip-path:inset(0 0 0 0);      filter:none; }
}

/* ---- 1c) glitch-hover_2 / _3 : slice-style siblings with different sequences ----
   Same character as the original clip-path glitch, re-choreographed so two/three
   next to each other don't animate in sync. NOTE: these use clip-path, which
   clips the hit region — apply them to non-clickable inner elements (e.g. an
   <img>), or trigger from a wrapper while the link stays separate. */
.glitch-hover_2.gh-go{ animation:tmpGlitchHover2 .40s linear var(--glitch-delay,0s) 1; will-change:transform,clip-path,filter; }
@keyframes tmpGlitchHover2{
  0%  { transform:translate(0,0) skewX(0);          clip-path:inset(0 0 0 0);    filter:none; }
  12% { transform:translate(5px,-1px) skewX(5deg);  clip-path:inset(60% 0 14% 0);
        filter:drop-shadow(-5px 0 rgba(255,0,72,.6)) drop-shadow(5px 0 rgba(0,214,255,.6)); }
  26% { transform:translate(-4px,1px) skewX(-5deg); clip-path:inset(18% 0 58% 0);
        filter:drop-shadow(4px 0 rgba(255,0,72,.65)) drop-shadow(-4px 0 rgba(0,214,255,.65)); }
  40% { transform:translate(3px,-1px);              clip-path:inset(44% 0 30% 0);
        filter:drop-shadow(-5px 0 rgba(255,0,72,.55)) drop-shadow(5px 0 rgba(0,214,255,.55)); }
  52% { transform:translate(-3px,1px) skewX(3deg);  clip-path:inset(6% 0 78% 0);  filter:none; }
  66% { transform:translate(2px,0);                 clip-path:inset(70% 0 8% 0);
        filter:drop-shadow(4px 0 rgba(255,0,72,.5)) drop-shadow(-4px 0 rgba(0,214,255,.5)); }
  80% { transform:translate(-1px,-1px);             clip-path:inset(30% 0 40% 0); filter:none; }
  90% { transform:translate(1px,0);                 clip-path:inset(0 0 0 0);      filter:none; }
  100%{ transform:translate(0,0);                   clip-path:inset(0 0 0 0);      filter:none; }
}
.glitch-hover_3.gh-go{ animation:tmpGlitchHover3 .45s linear var(--glitch-delay,0s) 1; will-change:transform,clip-path,filter; }
@keyframes tmpGlitchHover3{
  0%  { transform:translate(0,0) skewX(0);          clip-path:inset(0 0 0 0);    filter:none; }
  9%  { transform:translate(-3px,2px) skewX(-4deg); clip-path:inset(34% 0 34% 0);
        filter:drop-shadow(5px 0 rgba(255,0,72,.6)) drop-shadow(-5px 0 rgba(0,214,255,.6)); }
  20% { transform:translate(4px,-2px) skewX(6deg);  clip-path:inset(4% 0 70% 0);
        filter:drop-shadow(-4px 0 rgba(255,0,72,.65)) drop-shadow(4px 0 rgba(0,214,255,.65)); }
  32% { transform:translate(-2px,1px);              clip-path:inset(64% 0 12% 0);
        filter:drop-shadow(6px 0 rgba(255,0,72,.55)) drop-shadow(-6px 0 rgba(0,214,255,.55)); }
  46% { transform:translate(3px,-1px) skewX(-2deg); clip-path:inset(22% 0 50% 0); filter:none; }
  60% { transform:translate(-3px,2px);              clip-path:inset(48% 0 22% 0);
        filter:drop-shadow(-5px 0 rgba(255,0,72,.5)) drop-shadow(5px 0 rgba(0,214,255,.5)); }
  74% { transform:translate(2px,-1px) skewX(3deg);  clip-path:inset(10% 0 66% 0); filter:none; }
  88% { transform:translate(-1px,0);                clip-path:inset(0 0 0 0);      filter:none; }
  100%{ transform:translate(0,0);                   clip-path:inset(0 0 0 0);      filter:none; }
}

/* ---- 2) glitch-img-reg : punchy RGB-split shake fired every ~10s ----
   Driven by JS (fx.js toggles .gi-go for one shot). It is intentionally NOT a
   persistent animation: a permanently filtered/composited image gets clamped
   from wide-gamut (P3) to sRGB by the browser -> washed-out, lifted blacks.
   With .gi-go off the image renders clean & full-color. Needs fx.js to glitch;
   without it, images are simply static (and correct). */
.glitch-img-reg.gi-go{ animation:tmpGlitchReg .34s linear 1; }
@keyframes tmpGlitchReg{
  0%   { transform:translate(0,0) skewX(0); filter:none; }
  12%  { transform:translate(-4px,0) skewX(-4deg);
         filter:drop-shadow(5px 0 rgba(255,0,72,.7)) drop-shadow(-5px 0 rgba(0,214,255,.7)); }
  27%  { transform:translate(4px,-1px) skewX(3deg);
         filter:drop-shadow(-5px 0 rgba(255,0,72,.65)) drop-shadow(5px 0 rgba(0,214,255,.65)); }
  42%  { transform:translate(-3px,1px) skewX(-2deg);
         filter:drop-shadow(4px 0 rgba(255,0,72,.6)) drop-shadow(-4px 0 rgba(0,214,255,.6)); }
  57%  { transform:translate(3px,0) skewX(4deg);
         filter:drop-shadow(-4px 0 rgba(255,0,72,.6)) drop-shadow(4px 0 rgba(0,214,255,.6)); }
  72%  { transform:translate(-2px,-1px);
         filter:drop-shadow(3px 0 rgba(255,0,72,.5)) drop-shadow(-3px 0 rgba(0,214,255,.5)); }
  87%  { transform:translate(2px,0);
         filter:drop-shadow(-2px 0 rgba(255,0,72,.45)) drop-shadow(2px 0 rgba(0,214,255,.45)); }
  100% { transform:translate(0,0) skewX(0); filter:none; }
}

/* ---- fix: card images render full-width on load (lazy + AOS can collapse them
   until a repaint; the glitch animation was the only thing forcing that repaint) ---- */
.glitch-hover .ct-image, .glitch-hover img{ display:block; width:100%; height:auto; }

@media (prefers-reduced-motion:reduce){
  .glitch-hover.gh-go,.glitch-hover_2.gh-go,.glitch-hover_3.gh-go,.glitch-link.gh-go .glitch-link-img{ animation:none; }
  .glitch-img-reg.gi-go{ animation:none; }
}

/* ============================================================
   fc-uline — attribute-driven hover underline system.
   Put class "fc-uline" on a div + data-uline="fill|refresh|trace".
   Optional tuning attrs (fx.js maps them to CSS vars):
     data-uline-color   data-uline-width   data-uline-dur
     data-uline-in / data-uline-out       (left | right)
     data-uline-ease-in / data-uline-ease-out
   (trace style handled by fx.js — see uline controller.)
   ============================================================ */
.fc-uline{
  position:relative;
  --ul-color:#6BCEE8; --ul-width:3px; --ul-dur:.4s; --ul-gap:2px;
  --ul-in:left; --ul-out:right;
  --ul-ease-in:cubic-bezier(.22,1,.36,1);    /* ease-out entrance */
  --ul-ease-out:cubic-bezier(.55,0,1,.45);   /* ease-in exit      */
}
.fc-uline::after{
  content:""; position:absolute; left:0; right:0; bottom:calc(-1 * var(--ul-gap));
  height:var(--ul-width); background:var(--ul-color); pointer-events:none;
}
/* 1) fill — empty; fills in on hover (stays), recedes on leave */
.fc-uline[data-uline="fill"]::after{
  transform:scaleX(0); transform-origin:var(--ul-out) center;
  transition:transform var(--ul-dur) var(--ul-ease-out);
}
.fc-uline[data-uline="fill"]:hover::after{
  transform:scaleX(1); transform-origin:var(--ul-in) center;
  transition:transform var(--ul-dur) var(--ul-ease-in);
}
/* 2) refresh — starts underlined; wipes out then back in on hover */
.fc-uline[data-uline="refresh"]::after{ transform:scaleX(1); }
.fc-uline[data-uline="refresh"]:hover::after{ animation:ulRefresh var(--ul-dur) 1; }
@keyframes ulRefresh{
  0%   { transform:scaleX(1); transform-origin:var(--ul-out) center; animation-timing-function:var(--ul-ease-out); }
  49%  { transform:scaleX(0); transform-origin:var(--ul-out) center; }
  50%  { transform:scaleX(0); transform-origin:var(--ul-in) center;  animation-timing-function:var(--ul-ease-in); }
  100% { transform:scaleX(1); transform-origin:var(--ul-in) center; }
}
@media (prefers-reduced-motion:reduce){
  .fc-uline[data-uline="fill"]:hover::after{ transition:none; }
  .fc-uline[data-uline="refresh"]:hover::after{ animation:none; }
}

:root{--fcv-fx-css:"1.0.0";}
