/*
  ✨ The animations and fun moments (web/fx.js, window.ChoreFX; DESIGN.md "Phase 9, part 2"): the
  sparkles on a ticked task, "+25 XP" flying into the ⭐ chip, confetti, the ⚡ zap, the level-up
  moment, achievement flips, the streak 🔥, rows sliding in, the schedule's settle, the weather
  chip's icon, the thinking shimmer, the empty-state scenes, and a gentle press on buttons.

  Only transform and opacity move (the host's own `transform` is left alone: effects on elements
  that aren't ours use the separate `scale`, `translate` and `rotate` properties). fx.js sets
  <html data-fx="full | subtle | off"> from Settings → Animations and prefers-reduced-motion:
  full = everything, subtle = short fades only (no particles, no shakes, nothing looping), off =
  nothing moves. The reduced-motion block at the end stops everything again, whatever fx.js says.

  WIRING: <link rel="stylesheet" href="fx.css"> in web/index.html after help.css (last, so the
  press and lift sit on top of choreboy.css's .btn). Tokens: choreboy.css's (--accent, --gold…).
*/

:root {
  --fx-bronze: #b0703a;
  --fx-silver: #8a94a6;
  --fx-gold: #e8a30c;
  --fx-platinum: #7c5cff;
}
@media (prefers-color-scheme: dark) {
  :root {
    --fx-bronze: #e0a26b;
    --fx-silver: #cfd6e0;
    --fx-gold: #fdb022;
    --fx-platinum: #b692f6;
  }
}

/* ---------- the particle layer: one fixed sheet over everything that never takes a click ---------- */

.fx-layer { position: fixed; inset: 0; z-index: 520; pointer-events: none; overflow: hidden; contain: strict; }
.fx-p { position: absolute; left: 0; top: 0; pointer-events: none; will-change: transform, opacity; }

/* Sparkles: little diamonds and dots flying out from a ticked check, in the category's color. */
.fx-spark { width: 7px; height: 7px; margin: -3.5px 0 0 -3.5px; border-radius: 1.5px; background: var(--fx-color, var(--accent)); animation: fx-spark var(--fx-dur, .55s) cubic-bezier(.2, .7, .3, 1) var(--fx-delay, 0s) both; }
.fx-spark.round { width: 5px; height: 5px; margin: -2.5px 0 0 -2.5px; border-radius: 50%; }
@keyframes fx-spark {
  0% { opacity: 0; transform: translate(0, 0) rotate(45deg) scale(.4); }
  18% { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--fx-dx, 0), var(--fx-dy, 0)) rotate(calc(45deg + var(--fx-rot, 90deg))) scale(.25); }
}
/* A glyph burst (❄️ or ☀️ round a weather task that just arrived). */
.fx-glyph { font-size: 13px; line-height: 1; width: 1em; height: 1em; margin: -.5em 0 0 -.5em; text-align: center; animation: fx-glyph var(--fx-dur, .7s) cubic-bezier(.2, .7, .3, 1) var(--fx-delay, 0s) both; }
@keyframes fx-glyph {
  0% { opacity: 0; transform: translate(0, 0) scale(.5) rotate(0deg); }
  20% { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--fx-dx, 0), var(--fx-dy, 0)) scale(.9) rotate(var(--fx-rot, 60deg)); }
}

/* "+25 XP" on its way to the ⭐ chip (fx.js moves it with the Web Animations API). */
.fx-xp { font: 800 .9rem var(--font); color: var(--gold); text-shadow: 0 1px 0 var(--surface), 0 0 6px var(--surface); white-space: nowrap; transform: translate(-50%, -50%); }
.fx-xp.fx-xp-rise { animation: fx-xp-rise .95s ease-out both; }
.fx-xp.fx-xp-fade { animation: fx-xp-fade .9s ease-out both; }
@keyframes fx-xp-rise { 0% { opacity: 0; transform: translate(-50%, -30%) scale(.8); } 15% { opacity: 1; transform: translate(-50%, -90%) scale(1.1); } 100% { opacity: 0; transform: translate(-50%, -260%) scale(1); } }
@keyframes fx-xp-fade { 0% { opacity: 0; transform: translate(-50%, -110%); } 20%, 65% { opacity: 1; transform: translate(-50%, -110%); } 100% { opacity: 0; transform: translate(-50%, -110%); } }

/* Confetti for the day's last task. */
.fx-confetti { width: 8px; height: 12px; margin-left: -4px; border-radius: 2px; background: var(--accent); animation: fx-fall var(--fx-dur, 2s) cubic-bezier(.25, .55, .45, 1) var(--fx-delay, 0s) both; }
.fx-confetti.wide { width: 11px; height: 6px; }
.fx-confetti.round { width: 7px; height: 7px; border-radius: 50%; }
.fx-c0 { background: var(--accent); }
.fx-c1 { background: var(--gold); }
.fx-c2 { background: var(--purple); }
.fx-c3 { background: var(--info); }
.fx-c4 { background: var(--bad); }
@keyframes fx-fall {
  0% { opacity: 0; transform: translate(0, -16px) rotate3d(1, .4, 0, 0deg); }
  6% { opacity: 1; }
  82% { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--fx-dx, 0), var(--fx-fall, 100vh)) rotate3d(1, .4, 0, var(--fx-rot, 720deg)); }
}

/* ⚡ a Quickie done. */
.fx-zap { font-size: 18px; line-height: 1; width: 1em; height: 1em; margin: -.5em 0 0 -.5em; text-align: center; animation: fx-zap .6s ease-out both; }
.fx-zap.fx-zap-fade { animation: fx-zap-fade .6s ease-out both; }
@keyframes fx-zap {
  0% { opacity: 0; transform: scale(.3) rotate(0deg); }
  28% { opacity: 1; transform: scale(1.35) rotate(-10deg); }
  42% { transform: scale(1.1) rotate(9deg); }
  56% { transform: scale(1.15) rotate(-5deg); }
  100% { opacity: 0; transform: translateY(-16px) scale(.9) rotate(0deg); }
}
@keyframes fx-zap-fade { 0% { opacity: 0; } 25%, 60% { opacity: 1; } 100% { opacity: 0; } }

/* ---------- one-shot effects on the app's own elements (classes fx.js adds and takes off) ---------- */

/* The check pops (ticked), a chip bumps (XP arrived), a row shakes (⚡), a block settles (schedule). */
.fx-pop { animation: fx-pop .3s ease-out; }
@keyframes fx-pop { 0% { scale: .85; } 55% { scale: 1.2; } 100% { scale: 1; } }
.fx-bump { animation: fx-bump .42s cubic-bezier(.3, 1.6, .5, 1); }
@keyframes fx-bump { 0%, 100% { scale: 1; } 40% { scale: 1.14; } }
.fx-shake { animation: fx-shake .32s ease-in-out; }
@keyframes fx-shake { 0%, 100% { translate: 0 0; } 20% { translate: -2px 0; } 45% { translate: 2px 0; } 70% { translate: -1px 0; } }
.fx-settle { animation: fx-settle .34s cubic-bezier(.3, 1.4, .5, 1); }
@keyframes fx-settle { 0% { scale: 1.035; } 55% { scale: .985; } 100% { scale: 1; } }
/* Subtle's stand-in for all of those: a short fade. */
.fx-blink { animation: fx-blink .26s ease-out; }
@keyframes fx-blink { 0% { opacity: .45; } 100% { opacity: 1; } }

/* A schedule block being dragged: tilted a little, with a deeper shadow. */
.fx-lifted.fx-lifted { box-shadow: var(--shadow-lg); }
html[data-fx="full"] .fx-lifted { rotate: -1.2deg; scale: 1.02; transition: rotate .12s ease, scale .12s ease; }

/* Lists: rows slide in when a list opens (staggered, fast), a new task drops in, a deleted one
   slides out. Subtle: they only fade. `backwards` keeps a row hidden during its short delay. */
html[data-fx="full"] .fx-in { animation: fx-in .24s cubic-bezier(.2, .8, .3, 1) calc(var(--fx-i, 0) * 22ms) backwards; }
html[data-fx="subtle"] .fx-in { animation: fx-fade-in .16s ease-out backwards; }
@keyframes fx-in { from { opacity: 0; translate: 0 8px; } }
@keyframes fx-fade-in { from { opacity: 0; } }
html[data-fx="full"] .fx-drop { animation: fx-drop .3s cubic-bezier(.2, 1.2, .4, 1) backwards; }
html[data-fx="subtle"] .fx-drop { animation: fx-fade-in .18s ease-out backwards; }
@keyframes fx-drop { from { opacity: 0; translate: 0 -10px; scale: .98; } }
html[data-fx="full"] .fx-out { animation: fx-out .26s ease-in forwards; pointer-events: none; }
html[data-fx="subtle"] .fx-out { animation: fx-fade-out .18s ease-in forwards; pointer-events: none; }
@keyframes fx-out { to { opacity: 0; translate: 1.5rem 0; } }
@keyframes fx-fade-out { to { opacity: 0; } }

/* A gentle, steady pulse (the Upcoming "today" item). Full only. */
html[data-fx="full"] .fx-pulsing { animation: fx-breathe 2.6s ease-in-out infinite; }
@keyframes fx-breathe { 0%, 100% { scale: 1; opacity: 1; } 50% { scale: 1.02; opacity: .88; } }

/* ---------- achievement: the badge flips in with a shine in its tier's color ---------- */

.fx-shining { position: relative; overflow: hidden; }
.fx-ach.fx-ach { animation: fx-flip .62s cubic-bezier(.2, 1.25, .4, 1) backwards; }
@keyframes fx-flip {
  0% { opacity: 0; transform: perspective(520px) rotateY(-95deg) scale(.9); }
  45% { opacity: 1; }
  100% { opacity: 1; transform: perspective(520px) rotateY(0deg) scale(1); }
}
.fx-shine { position: absolute; inset: 0; border-radius: inherit; pointer-events: none; background: linear-gradient(105deg, transparent 30%, color-mix(in srgb, var(--fx-tier, var(--fx-gold)) 55%, #fff) 48%, color-mix(in srgb, var(--fx-tier, var(--fx-gold)) 30%, transparent) 54%, transparent 70%); opacity: .85; mix-blend-mode: screen; transform: translateX(-110%); animation: fx-shine .9s ease-in-out .32s forwards; }
@media (prefers-color-scheme: light) { .fx-shine { mix-blend-mode: normal; opacity: .55; } }
@keyframes fx-shine { to { transform: translateX(110%); } }
.fx-tier-ring { box-shadow: 0 0 0 2px var(--fx-tier, var(--fx-gold)), var(--shadow-lg); }

/* ---------- the level-up moment (full screen, tap to close) ---------- */

.fx-lvl { position: fixed; inset: 0; z-index: 510; display: grid; place-items: center; padding: 1rem; background: rgba(16, 24, 40, .55); cursor: pointer; animation: fx-fade-in .2s ease-out; transition: opacity .2s ease; -webkit-tap-highlight-color: transparent; }
.fx-lvl.out { opacity: 0; }
.fx-lvl-card { position: relative; overflow: hidden; isolation: isolate; width: min(21rem, 100%); padding: 1.6rem 1.3rem 1.2rem; text-align: center; background: var(--surface); color: var(--text); border: 1px solid color-mix(in srgb, var(--gold) 45%, var(--border)); border-radius: 20px; box-shadow: var(--shadow-lg); cursor: default; }
.fx-lvl-rays { position: absolute; z-index: -1; left: 50%; top: 4.2rem; width: 26rem; height: 26rem; margin: -13rem 0 0 -13rem; border-radius: 50%; background: repeating-conic-gradient(from 0deg, color-mix(in srgb, var(--gold) 15%, transparent) 0deg 9deg, transparent 9deg 22.5deg); -webkit-mask-image: radial-gradient(circle, #000 18%, transparent 62%); mask-image: radial-gradient(circle, #000 18%, transparent 62%); opacity: .9; pointer-events: none; }
.fx-lvl-char { width: 96px; height: 96px; margin: 0 auto .5rem; display: grid; place-items: center; font-size: 3.6rem; line-height: 1; }
.fx-lvl-char svg, .fx-lvl-char img { width: 100%; height: 100%; image-rendering: pixelated; }
.fx-lvl-kicker { margin: 0; font: 800 .78rem var(--font); letter-spacing: .14em; text-transform: uppercase; color: var(--accent-text); }
.fx-lvl-num { margin: .15rem 0 0; font: 800 3.3rem/1 var(--font); letter-spacing: -.03em; color: var(--gold); font-variant-numeric: tabular-nums; }
.fx-lvl-num small { display: block; margin-bottom: .1rem; font-size: .95rem; font-weight: 700; letter-spacing: 0; color: var(--text-2); }
.fx-lvl-title { margin: .45rem 0 1.1rem; font-size: 1.05rem; font-weight: 650; overflow-wrap: anywhere; }
.fx-lvl-title:empty { display: none; }
.fx-lvl-ok { min-width: 8rem; }
.fx-lvl-hint { margin: .7rem 0 0; font-size: .78rem; color: var(--text-3); }
html[data-fx="full"] .fx-lvl-card { animation: fx-card-in .5s cubic-bezier(.2, 1.35, .4, 1) both; }
html[data-fx="full"] .fx-lvl-char > * { animation: fx-jump .75s cubic-bezier(.25, 1.5, .45, 1) .18s both; }
html[data-fx="full"] .fx-lvl-rays { animation: fx-spin 18s linear infinite; }
html[data-fx="subtle"] .fx-lvl-card { animation: fx-fade-in .2s ease-out both; }
@keyframes fx-card-in { 0% { opacity: 0; transform: scale(.82) translateY(14px); } 100% { opacity: 1; transform: none; } }
@keyframes fx-jump { 0% { opacity: 0; transform: translateY(46px) scale(.55); } 55% { opacity: 1; transform: translateY(-14px) scale(1.06); } 78% { transform: translateY(3px) scale(.98); } 100% { opacity: 1; transform: none; } }
@keyframes fx-spin { to { transform: rotate(360deg); } }

/* ---------- 🔥 the streak flame on the level chip (bigger at 7, 30 and 100 days) ---------- */

.fx-flame { display: inline-block; line-height: 1; font-size: .8em; transform-origin: 50% 85%; }
.fx-flame[data-size="1"] { font-size: .95em; }
.fx-flame[data-size="2"] { font-size: 1.1em; }
.fx-flame[data-size="3"] { font-size: 1.25em; }
html[data-fx="full"] .fx-flame { animation: fx-flicker 1.7s ease-in-out infinite; }
html[data-fx="full"] .fx-flame[data-size="2"] { animation-duration: 1.35s; }
html[data-fx="full"] .fx-flame[data-size="3"] { animation-duration: 1.05s; }
@keyframes fx-flicker {
  0%, 100% { transform: scale(1, 1) rotate(-2deg); opacity: 1; }
  30% { transform: scale(1.06, .95) rotate(2deg); opacity: .9; }
  62% { transform: scale(.96, 1.07) rotate(-1deg); opacity: 1; }
}

/* ---------- the weather chip's icon: falling snow, rain, turning sun, drifting cloud ---------- */

.fx-wx { position: relative; display: inline-block; }
.fx-wx-fx { display: none; position: absolute; left: -.15em; right: -.15em; top: 60%; height: .9em; pointer-events: none; overflow: hidden; }
.fx-wx-fx i { position: absolute; top: 0; width: 3px; height: 3px; border-radius: 50%; opacity: 0; background: color-mix(in srgb, var(--info) 70%, #fff); }
.fx-wx-fx i:nth-child(1) { left: 12%; }
.fx-wx-fx i:nth-child(2) { left: 48%; }
.fx-wx-fx i:nth-child(3) { left: 80%; }
.fx-wx-rain .fx-wx-fx i { width: 1.5px; height: 5px; border-radius: 1px; background: var(--info); }
html[data-fx="full"] .fx-wx-snow .fx-wx-fx, html[data-fx="full"] .fx-wx-rain .fx-wx-fx { display: block; }
html[data-fx="full"] .fx-wx-snow .fx-wx-fx i { animation: fx-snow 2.4s linear infinite; }
html[data-fx="full"] .fx-wx-rain .fx-wx-fx i { animation: fx-rain .8s linear infinite; }
html[data-fx="full"] .fx-wx-snow .fx-wx-fx i:nth-child(2) { animation-delay: -.9s; }
html[data-fx="full"] .fx-wx-snow .fx-wx-fx i:nth-child(3) { animation-delay: -1.6s; }
html[data-fx="full"] .fx-wx-rain .fx-wx-fx i:nth-child(2) { animation-delay: -.3s; }
html[data-fx="full"] .fx-wx-rain .fx-wx-fx i:nth-child(3) { animation-delay: -.55s; }
@keyframes fx-snow { 0% { opacity: 0; transform: translate(0, -2px); } 20% { opacity: .95; } 50% { transform: translate(2px, 6px); } 85% { opacity: .8; } 100% { opacity: 0; transform: translate(-1px, 13px); } }
@keyframes fx-rain { 0% { opacity: 0; transform: translateY(-3px); } 25% { opacity: .9; } 100% { opacity: 0; transform: translateY(12px); } }
html[data-fx="full"] .fx-wx-sun { animation: fx-spin 14s linear infinite; }
html[data-fx="full"] .fx-wx-cloud { animation: fx-drift 3.6s ease-in-out infinite alternate; }
@keyframes fx-drift { from { transform: translateX(-1.5px); } to { transform: translateX(1.5px); } }

/* ---------- ✨ Ask Claude while it's thinking: a soft light sweeping across ---------- */

.fx-shimmer { position: relative; overflow: hidden; isolation: isolate; }
.fx-shimmer::after { content: ''; position: absolute; inset: 0; z-index: 1; pointer-events: none; opacity: 0; background: linear-gradient(100deg, transparent 25%, color-mix(in srgb, currentColor 16%, transparent) 50%, transparent 75%); transform: translateX(-100%); }
html[data-fx="full"] .fx-shimmer::after { opacity: 1; animation: fx-sweep 1.3s ease-in-out infinite; }
html[data-fx="subtle"] .fx-shimmer { animation: fx-dim 1.6s ease-in-out infinite; }
@keyframes fx-sweep { to { transform: translateX(100%); } }
@keyframes fx-dim { 0%, 100% { opacity: 1; } 50% { opacity: .7; } }

/* ---------- empty states: a broom sweeping (empty Today), a sleeping moon (empty Future) ---------- */

.fx-empty { position: relative; display: block; width: 5rem; height: 3.6rem; margin: 0 auto .5rem; font-size: 2.3rem; line-height: 1; }
.fx-empty-ico { position: absolute; left: 50%; bottom: .15rem; width: 1em; margin-left: -.5em; text-align: center; transform-origin: 50% 100%; }
.fx-dust, .fx-z { display: none; position: absolute; inset: 0; pointer-events: none; }
.fx-dust i { position: absolute; bottom: .2rem; left: 22%; width: .4rem; height: .4rem; border-radius: 50%; background: var(--border-strong); opacity: 0; }
.fx-dust i:nth-child(2) { left: 30%; bottom: .45rem; width: .3rem; height: .3rem; }
.fx-dust i:nth-child(3) { left: 16%; bottom: .35rem; width: .25rem; height: .25rem; }
.fx-z i { position: absolute; left: 66%; top: .5rem; font: 800 .8rem var(--font); font-style: normal; color: var(--text-3); opacity: 0; }
html[data-fx="full"] .fx-dust, html[data-fx="full"] .fx-z { display: block; }
html[data-fx="full"] .fx-empty-broom .fx-empty-ico { animation: fx-broom 2.2s ease-in-out infinite; }
html[data-fx="full"] .fx-dust i { animation: fx-puff 2.2s ease-out infinite; }
html[data-fx="full"] .fx-dust i:nth-child(2) { animation-delay: .12s; }
html[data-fx="full"] .fx-dust i:nth-child(3) { animation-delay: .24s; }
html[data-fx="full"] .fx-empty-moon .fx-empty-ico { animation: fx-bob 3.8s ease-in-out infinite; }
html[data-fx="full"] .fx-z i { animation: fx-zzz 3.6s ease-out infinite; }
html[data-fx="full"] .fx-z i:nth-child(2) { animation-delay: 1.2s; }
html[data-fx="full"] .fx-z i:nth-child(3) { animation-delay: 2.4s; }
@keyframes fx-broom { 0%, 100% { transform: translateX(6px) rotate(10deg); } 45% { transform: translateX(-7px) rotate(-16deg); } 55% { transform: translateX(-7px) rotate(-16deg); } }
@keyframes fx-puff { 0%, 40% { opacity: 0; transform: translate(0, 0) scale(.3); } 50% { opacity: .75; } 100% { opacity: 0; transform: translate(-14px, -8px) scale(1.5); } }
@keyframes fx-bob { 0%, 100% { transform: translateY(0) rotate(-6deg); } 50% { transform: translateY(-4px) rotate(4deg); } }
@keyframes fx-zzz { 0% { opacity: 0; transform: translate(0, 0) scale(.7); } 25% { opacity: .9; } 100% { opacity: 0; transform: translate(14px, -20px) scale(1.15); } }

/* ---------- buttons and chips: a gentle press, and a small lift on hover (full only) ---------- */

html[data-fx="full"] :is(.btn, .chip-btn, .nav-btn, .seg button, .emoji-idea, .wd) { transition: scale .12s ease, translate .15s ease; }
html[data-fx="full"] :is(.btn, .chip-btn, .nav-btn, .seg button, .emoji-idea, .wd):active:not(:disabled) { scale: .97; }
@media (hover: hover) {
  html[data-fx="full"] :is(.btn:not(.tiny):not(.ghost), .chip-btn):hover:not(:disabled):not(:active) { translate: 0 -1px; }
}

/* ---------- reduced motion: nothing moves, whatever fx.js was told ---------- */

@media (prefers-reduced-motion: reduce) {
  .fx-layer, .fx-wx-fx, .fx-dust, .fx-z { display: none !important; }
  .fx-pop, .fx-bump, .fx-shake, .fx-settle, .fx-blink, .fx-in, .fx-drop, .fx-pulsing, .fx-ach.fx-ach, .fx-shine,
  .fx-lvl, .fx-lvl-card, .fx-lvl-char > *, .fx-lvl-rays, .fx-flame, .fx-wx-sun, .fx-wx-cloud, .fx-shimmer,
  .fx-shimmer::after, .fx-empty-ico { animation: none !important; transition: none !important; }
  .fx-out { animation: none !important; opacity: 0; }
  .fx-lifted, :is(.btn, .chip-btn, .nav-btn, .seg button, .emoji-idea, .wd) { rotate: none !important; scale: none !important; translate: none !important; }
}

/* ⚙️ Settings → 🔊 Sounds & reminders (app.js draws it): the volume slider and one row per sound. */
.snd-volume { display: block; width: 100%; max-width: 320px; accent-color: var(--accent); margin: 2px 0 8px; }
.snd-list { display: grid; gap: 2px 16px; grid-template-columns: repeat(auto-fill, minmax(min(100%, 240px), 1fr)); }
.snd-row .btn-row { flex-wrap: nowrap; }
.snd-row .sel { min-width: 0; flex: 1 1 auto; }
