
/* ==========================================================================
   v3 fx (Ken, Oct 14): celebratory animations. Driven by public/js/v3-fx.js
   (plus a few pure-CSS ones below). Short, transform/opacity only, and off
   for "reduce motion" (block at the end).
   ========================================================================== */
.v3-fx-dot { position: fixed; z-index: 2050; border-radius: 50%; pointer-events: none; }
.v3-fx-dot--star { border-radius: 2px; clip-path: polygon(50% 0, 62% 38%, 100% 50%, 62% 62%, 50% 100%, 38% 62%, 0 50%, 38% 38%); transform: scale(1.4); }
.v3-fx-confetti { position: fixed; inset: 0; width: 100%; height: 100%; z-index: 2100; pointer-events: none; }

/* generic pop / tap bounce */
.v3-fx-pop { animation: v3-fx-pop .45s cubic-bezier(.2,.8,.2,1); }
@keyframes v3-fx-pop { 0% { transform: scale(1); } 35% { transform: scale(1.28); } 65% { transform: scale(.92); } 100% { transform: scale(1); } }
.v3-fx-tap { animation: v3-fx-tap .42s cubic-bezier(.3,1.6,.5,1); }
@keyframes v3-fx-tap { 0% { transform: scale(1); } 30% { transform: scale(.82) translateY(2px); } 70% { transform: scale(1.12) translateY(-3px); } 100% { transform: none; } }

/* save: a filled bookmark rises from the bottom over the outline */
.v3-fx-saving > .v3-icon:not(.v3-fx-savefill) { fill: none !important; }
.v3-fx-savefill { position: absolute; fill: currentColor; pointer-events: none; animation: v3-fx-savefill .45s cubic-bezier(.2,.8,.2,1) forwards; }
@keyframes v3-fx-savefill { from { clip-path: inset(100% 0 0 0); } to { clip-path: inset(0 0 0 0); } }

/* repost: the arrows spin once */
.v3-fx-spin .v3-icon { animation: v3-fx-spin .6s cubic-bezier(.3,.9,.4,1); }
@keyframes v3-fx-spin { from { transform: rotate(0); } to { transform: rotate(360deg); } }

/* follow: the button turns into a check, then "Following" fades in */
.v3-fx-follow { justify-content: center; }
.v3-fx-check { width: 18px; height: 18px; animation: v3-fx-check .5s cubic-bezier(.3,1.6,.5,1) both; }
@keyframes v3-fx-check { from { transform: scale(0) rotate(-45deg); opacity: 0; } to { transform: none; opacity: 1; } }
.v3-fx-textin { animation: v3-fx-textin .3s ease-out; }
@keyframes v3-fx-textin { from { opacity: .2; } to { opacity: 1; } }

/* notifications: the bell wiggles and the count pops */
.v3-fx-ring .v3-icon { transform-origin: 50% 10%; animation: v3-fx-ring .8s ease-in-out; }
@keyframes v3-fx-ring { 0%, 100% { transform: rotate(0); } 15% { transform: rotate(16deg); } 30% { transform: rotate(-14deg); } 45% { transform: rotate(10deg); } 60% { transform: rotate(-6deg); } 75% { transform: rotate(3deg); } }
.v3-fx-countpop { animation: v3-fx-pop .45s cubic-bezier(.3,1.6,.5,1); }

/* phone tab bar: + turns into × while the Create sheet is open */
.v3-tab__create .v3-icon { transition: transform .28s cubic-bezier(.3,1.4,.5,1); }
body:has(#v3CreateSheet:not([hidden])) .v3-tab__create .v3-icon { transform: rotate(135deg); }

/* new post: slides in (v3-feed.js) with a short brand glow */
.v3-fx-fresh { animation: v3-fx-fresh 1.6s ease-out; }
@keyframes v3-fx-fresh { 0% { box-shadow: 0 0 0 2px color-mix(in srgb, var(--v3-brand) 70%, transparent), 0 0 28px color-mix(in srgb, var(--v3-brand) 40%, transparent); } 100% { box-shadow: 0 0 0 0 transparent, 0 0 0 transparent; } }
/* "new posts" pill drops in from the top */
.v3-newpill:not([hidden]) { animation: v3-fx-drop .42s cubic-bezier(.3,1.5,.5,1); }
@keyframes v3-fx-drop { from { opacity: 0; transform: translateY(-24px) scale(.9); } to { opacity: 1; transform: none; } }

/* sidebar XP card glows while the bar fills */
.v3-fx-glow { animation: v3-fx-glow 1.6s ease-out; }
@keyframes v3-fx-glow { 0%, 100% { box-shadow: var(--v3-shadow-card, none); } 40% { box-shadow: 0 0 0 2px color-mix(in srgb, var(--v3-brand) 55%, transparent), 0 0 30px color-mix(in srgb, var(--v3-brand) 35%, transparent); } }

/* level up: the badge stamps in (the number rolls up in v3.js) */
.v3-lvlup.is-on .v3-lvlup__badge { animation: v3-fx-stamp .6s cubic-bezier(.3,1.5,.5,1) .12s both; }
@keyframes v3-fx-stamp { 0% { transform: scale(1.9) rotate(-12deg); opacity: 0; } 60% { opacity: 1; } 100% { transform: none; opacity: 1; } }
.v3-lvlup__badge b.is-rolling { animation: v3-fx-roll .14s ease-out; }
@keyframes v3-fx-roll { from { transform: translateY(40%); opacity: .3; } to { transform: none; opacity: 1; } }

/* badge unlocked: flip in, foil sweep */
.v3-fx-reveal__back { position: fixed; inset: 0; z-index: 400; background: rgba(15, 10, 25, .55); opacity: 0; transition: opacity .2s; }
.v3-fx-reveal__back.is-on { opacity: 1; }
.v3-fx-reveal { position: fixed; z-index: 401; left: 50%; top: 50%; width: min(320px, calc(100vw - 32px)); padding: 26px 22px 22px; border-radius: var(--v3-r-xl); background: var(--v3-surface); border: 1px solid var(--v3-border); box-shadow: var(--v3-shadow-pop); text-align: center; display: flex; flex-direction: column; align-items: center; gap: 12px; transform: translate(-50%, -46%) scale(.94); opacity: 0; transition: transform .25s cubic-bezier(.2,.9,.3,1.3), opacity .2s; perspective: 600px; }
.v3-fx-reveal.is-on { opacity: 1; transform: translate(-50%, -50%); }
.v3-fx-reveal__kicker { margin: 0; font-size: 11px; font-weight: 800; letter-spacing: .12em; text-transform: uppercase; color: var(--v3-brand); }
.v3-fx-reveal__name { margin: 0; font-family: var(--v3-font-display); font-size: 22px; color: var(--v3-text); }
.v3-fx-reveal__badge { display: inline-grid; position: relative; overflow: hidden; transform: scale(2.6); margin: 46px 0 40px; border-radius: 50%; animation: v3-fx-flip .9s cubic-bezier(.3,1.2,.5,1) .1s both; }
@keyframes v3-fx-flip { 0% { transform: scale(.6) rotateY(-540deg); opacity: 0; } 60% { opacity: 1; } 100% { transform: scale(2.6) rotateY(0); opacity: 1; } }
.v3-fx-reveal__badge::after, .v3-fx-foil::after { content: ''; position: absolute; inset: -20%; background: linear-gradient(115deg, transparent 35%, rgba(255,255,255,.75) 50%, transparent 65%); transform: translateX(-120%); animation: v3-fx-foil 1.1s ease-in-out .9s; pointer-events: none; }
.v3-fx-foil { position: relative; overflow: hidden; border-radius: 50%; }
.v3-fx-foil::after { animation-delay: .2s; }
@keyframes v3-fx-foil { to { transform: translateX(120%); } }

/* boost live: the rocket takes off (v3-fx.js), its spot pops back */
.v3-fx-rocket { position: fixed; z-index: 2060; pointer-events: none; color: var(--v3-ok); }
.v3-fx-launched svg { opacity: 0; }

/* tip sent: coins */
.v3-fx-coin { position: fixed; top: 0; z-index: 2060; width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center; pointer-events: none; font: 800 14px/1 var(--v3-font); color: #8A5A00; background: radial-gradient(circle at 35% 30%, #FFE7A3, #F5B83D 55%, #D18B0B); box-shadow: inset 0 -2px 0 rgba(0,0,0,.18), 0 3px 8px rgba(0,0,0,.2); }

/* battles: result bars fill, the crown drops, the winner lifts */
.v3-match__bar::before { animation: v3-fx-bar 1s cubic-bezier(.2,.8,.2,1) .2s both; }
@keyframes v3-fx-bar { from { width: 0; } }
.v3-battle__champ { animation: v3-fx-lift .6s cubic-bezier(.2,.8,.2,1) both; }
@keyframes v3-fx-lift { from { opacity: 0; transform: translateY(14px) scale(.98); } to { opacity: 1; transform: none; } }
.v3-battle__crown .v3-icon, .v3-capcard.is-win .v3-capcard__votes .v3-icon { animation: v3-fx-crown .75s cubic-bezier(.3,1.6,.5,1) .45s both; }
@keyframes v3-fx-crown { from { transform: translateY(-22px) rotate(-25deg); opacity: 0; } to { transform: none; opacity: 1; } }

/* leaderboard: the podium rises in (2nd, 1st, 3rd), the crown drops */
.v3-podium__spot { animation: v3-fx-rise .6s cubic-bezier(.2,.9,.3,1.2) both; }
.v3-podium__spot.is-2 { animation-delay: .05s; }
.v3-podium__spot.is-1 { animation-delay: .2s; }
.v3-podium__spot.is-3 { animation-delay: .35s; }
@keyframes v3-fx-rise { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: none; } }
.v3-podium__spot.is-1 .v3-podium__medal .v3-icon { animation: v3-fx-crown .75s cubic-bezier(.3,1.6,.5,1) .6s both; }

/* empty pages: the hat floats and tips */
.v3-empty__hat { display: block; width: 74px; height: auto; margin: 0 auto 14px; transform-origin: 50% 85%; filter: drop-shadow(0 8px 14px color-mix(in srgb, var(--v3-brand) 30%, transparent)); animation: v3-fx-float 3.2s ease-in-out infinite; }
@keyframes v3-fx-float { 0%, 100% { transform: translateY(0) rotate(0); } 40% { transform: translateY(-6px) rotate(-5deg); } 70% { transform: translateY(-2px) rotate(3deg); } }

/* pull to refresh (installed app): the hat follows your finger, then spins */
.v3-ptr { position: fixed; left: 50%; top: calc(var(--v3-topbar-h, 64px) + env(safe-area-inset-top, 0px)); z-index: 39; width: 44px; height: 44px; margin-top: 6px; border-radius: 50%; display: grid; place-items: center; background: var(--v3-surface); border: 1px solid var(--v3-border); box-shadow: var(--v3-shadow-pop); transform: translate(-50%, -60px); opacity: 0; pointer-events: none; transition: transform .2s, opacity .2s; }
.v3-ptr img { width: 30px; height: auto; }
.v3-ptr.is-ready { border-color: var(--v3-brand); }
.v3-ptr.is-spinning { opacity: 1; transform: translate(-50%, 30px); }
.v3-ptr.is-spinning img { animation: v3-fx-spin .7s linear infinite; }

/* smooth page changes (Chrome's cross-page transitions; others just load) */
@view-transition { navigation: auto; }
::view-transition-old(root), ::view-transition-new(root) { animation-duration: .2s; }
.v3-topbar { view-transition-name: v3-topbar; }
.v3-tabbar { view-transition-name: v3-tabbar; }
.v3-post--detail .v3-post__media picture img { view-transition-name: v3-hero; }
::view-transition-group(v3-hero) { animation-duration: .32s; animation-timing-function: cubic-bezier(.2,.8,.2,1); }

@media (prefers-reduced-motion: reduce) {
    @view-transition { navigation: none; }
    .v3-fx-pop, .v3-fx-tap, .v3-fx-spin .v3-icon, .v3-fx-ring .v3-icon, .v3-fx-countpop, .v3-fx-fresh, .v3-newpill:not([hidden]), .v3-fx-glow,
    .v3-lvlup.is-on .v3-lvlup__badge, .v3-match__bar::before, .v3-battle__champ, .v3-battle__crown .v3-icon, .v3-capcard.is-win .v3-capcard__votes .v3-icon,
    .v3-podium__spot, .v3-podium__spot.is-1 .v3-podium__medal .v3-icon, .v3-empty__hat, .v3-fx-foil::after { animation: none !important; }
    .v3-tab__create .v3-icon { transition: none; }
}

/* Leaderboard rank movement since yesterday (Ken, Oct 14) */
.v3-rmove { display: inline-flex; align-items: center; gap: 1px; margin-left: 4px; padding: 1px 5px; border-radius: 999px; font-size: 10.5px; font-weight: 800; line-height: 1.4; vertical-align: middle; font-variant-numeric: tabular-nums; animation: v3-fx-rmove .5s cubic-bezier(.3,1.6,.5,1) .5s both; }
.v3-rmove > span[aria-hidden] { font-size: 8px; margin-right: 1px; }
.v3-rmove.is-up { color: var(--v3-ok); background: var(--v3-ok-bg); }
.v3-rmove.is-down { color: var(--v3-hot); background: var(--v3-hot-bg); }
.v3-rmove.is-new { color: var(--v3-brand); background: var(--v3-tint); text-transform: uppercase; letter-spacing: .04em; }
.v3-lbl__rank:has(.v3-rmove) { display: flex; flex-direction: column; align-items: center; gap: 2px; line-height: 1.1; }
.v3-lbl__rank .v3-rmove { margin: 0; padding: 0 4px; font-size: 9.5px; white-space: nowrap; }
@keyframes v3-fx-rmove { from { opacity: 0; transform: translateY(6px) scale(.7); } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .v3-rmove { animation: none !important; } }
