/* fx.css: shared motion layer. Only transform/opacity are animated (no layout shift). Content is never hidden without JS. */
@view-transition { navigation: auto; }
::view-transition-old(root),::view-transition-new(root){animation-duration:.22s}
:root{--spring:cubic-bezier(.34,1.56,.64,1);--ease:cubic-bezier(.2,.7,.2,1)}
/* scroll reveal: fx.js adds .rv only to elements that start BELOW the fold, then .in when they enter */
.rv{opacity:0;transform:translateY(18px)}
.rv.in{opacity:1;transform:none;transition:opacity .6s var(--ease),transform .6s var(--ease)}
/* animated gradient backgrounds */
.bg-anim{position:relative;isolation:isolate;overflow:hidden}
.bg-anim::before{content:"";position:absolute;inset:-40%;z-index:-1;background:radial-gradient(40% 40% at 20% 30%,var(--g1,#22c55e33),transparent 70%),radial-gradient(35% 35% at 80% 20%,var(--g2,#0ea5e933),transparent 70%),radial-gradient(45% 45% at 60% 80%,var(--g3,#a855f722),transparent 70%);animation:drift 18s ease-in-out infinite alternate;will-change:transform}
@keyframes drift{to{transform:translate3d(6%,-4%,0) rotate(8deg) scale(1.08)}}
/* tap feedback + ripple */
.btn,.chip{position:relative;overflow:hidden;transition:transform .15s var(--spring),background-color .2s,box-shadow .2s;-webkit-tap-highlight-color:transparent}
.btn:active,.chip:active{transform:scale(.94)}
.ripple{position:absolute;border-radius:50%;pointer-events:none;background:currentColor;opacity:.25;transform:scale(0);animation:ripple .55s var(--ease) forwards}
@keyframes ripple{to{transform:scale(1);opacity:0}}
/* bars fill with spring easing (pure CSS, works without JS) */
.bar>span,.fill{transform-origin:left center;animation:fillIn 1.1s var(--spring) both}
@keyframes fillIn{from{transform:scaleX(0)}}
/* skeleton loader (reserves exact space, so no CLS) */
.skel{position:relative;background:linear-gradient(90deg,rgba(127,127,127,.08) 25%,rgba(127,127,127,.18) 37%,rgba(127,127,127,.08) 63%);background-size:400% 100%;animation:shimmer 1.4s ease infinite;border-radius:12px}
.skel.done{animation:none;background:none}
@keyframes shimmer{0%{background-position:100% 50%}100%{background-position:0 50%}}
/* pop / pulse / shake helpers */
.pop{animation:pop .45s var(--spring)}
@keyframes pop{0%{transform:scale(.85)}60%{transform:scale(1.08)}100%{transform:scale(1)}}
.float{animation:float 6s ease-in-out infinite}
@keyframes float{50%{transform:translateY(-8px)}}
.pulse-dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:#22c55e;box-shadow:0 0 0 0 #22c55e88;animation:pd 1.6s infinite}
@keyframes pd{70%{box-shadow:0 0 0 10px #22c55e00}100%{box-shadow:0 0 0 0 #22c55e00}}
/* toast */
.toast{position:fixed;left:50%;bottom:84px;transform:translate(-50%,20px);opacity:0;background:var(--ink,#21172C);color:var(--paper,#FFF8EF);padding:10px 16px;border-radius:12px;font-size:14.5px;font-weight:600;z-index:50;transition:transform .35s var(--spring),opacity .25s;pointer-events:none;display:flex;gap:8px;align-items:center;max-width:min(92vw,460px)}
.toast svg{width:26px;height:26px;flex:none;--ho:#21172C;--hf:#FFD76A;--hio:1}
.toast.show{transform:translate(-50%,0);opacity:1}
/* theme toggle */
.theme-toggle{font-size:18px}
.theme-toggle svg{transition:transform .5s var(--spring)}
.theme-toggle:hover svg{transform:rotate(25deg)}
.ui-dark .theme-toggle svg{transform:rotate(180deg)}
/* swipe rows */
.swipe{touch-action:pan-y;transition:transform .25s var(--ease),opacity .25s}
.swipe.dragging{transition:none}
.swipe.gone{transform:translateX(-110%)!important;opacity:0}
/* confetti canvas */
#fx-canvas{position:fixed;inset:0;pointer-events:none;z-index:60}
/* draw-in SVG strokes */
.draw{stroke-dasharray:var(--len,1000);stroke-dashoffset:var(--len,1000);animation:draw 1.4s var(--ease) forwards}
@keyframes draw{to{stroke-dashoffset:0}}
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important;scroll-behavior:auto!important}
  .rv{opacity:1;transform:none}
  @view-transition{navigation:none}
}
