/* 13-animation.css — Animations-Feinschliff + Android-Flacker-Schutz (html.andr). Teil der geordneten Kaskade: in index.html in Nummern-Reihenfolge geladen; Regeln NICHT zwischen Dateien verschieben, ohne die Reihenfolge zu bedenken. */
/* ═══════════════════════════════════════════════════════════════════════════
   ANIMATION-POLISH (Apple/iOS Spring) — subtile Micro-Interactions
   Alle Animationen werden via existing @media(prefers-reduced-motion)
   automatisch deaktiviert.
   ═══════════════════════════════════════════════════════════════════════ */

/* 1) Shop-Item Pop-In bei Add (slide-down + scale + fade) */
.shop-item--new {
  animation: shopItemPopIn 420ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
  transform-origin: top center;
}
@keyframes shopItemPopIn {
  0%   { opacity: 0; transform: translateY(-6px) scale(0.94); }
  60%  { opacity: 1; }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}

/* 2) Shop-Check Pulse bei Done-Toggle */
.shop-check {
  transition:
    background 0.28s cubic-bezier(0.34, 1.56, 0.64, 1),
    border-color 0.28s ease,
    box-shadow 0.28s ease,
    transform 0.18s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.shop-item.done .shop-check {
  animation: shopCheckPulse 0.42s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
@keyframes shopCheckPulse {
  0%   { transform: scale(1); }
  45%  { transform: scale(1.18); }
  100% { transform: scale(1); }
}

/* 3) Stat-Counter Swap im Shop-Header (Number-Change) */
#shopStatText { display: inline-block; will-change: transform, opacity; }
#shopStatText.is-swap {
  animation: statSwap 280ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
@keyframes statSwap {
  0%   { opacity: 0; transform: translateY(6px); }
  100% { opacity: 1; transform: translateY(0); }
}

/* 4) Folder-Tab smooth active-Wechsel (Spring auf Klick) */
.shop-folder-tab {
  transition:
    background 320ms cubic-bezier(0.22, 1, 0.36, 1),
    border-color 260ms cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 280ms cubic-bezier(0.22, 1, 0.36, 1),
    color 220ms ease-out,
    transform 380ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
.shop-folder-tab.active {
  animation: folderTabSpring 380ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes folderTabSpring {
  0%   { transform: scale(0.97); }
  60%  { transform: scale(1.015); }
  100% { transform: scale(1); }
}

/* 5) Shop-Item-Image Fade-In beim Laden (via [src]-Attribut) */
.shop-item-img {
  opacity: 0;
  transform: scale(1.03);
  transition: opacity 0.35s ease-out, transform 0.35s ease-out;
}
.shop-item-img[src]:not([src=""]) {
  opacity: 1;
  transform: scale(1);
}

/* 6) Page-Transition (Calendar ↔ Shop ↔ Profile ↔ Stats ↔ Dev-Monatskalender):
   KEINE Bewegung/Größenänderung (translateY wirkte wie „Aufploppen") und KEIN
   Seiten-Weißblitz mehr (wieder entfernt, User-Wunsch) — nur ein weicher
   Opacity-Fade. Das kurze Aufleuchten liegt stattdessen INNEN auf der
   gleitenden Nav-Pill (navPuckGlow, Definition bei der Bottom-Nav). */
#calendarPage, #shopPage, #profilePage, #statsPage, #monthCalPage {
  opacity: 0;
  transition: opacity 200ms cubic-bezier(0.22, 0.61, 0.36, 1);
  will-change: opacity;
}
#calendarPage.page-enter, #shopPage.page-enter,
#profilePage.page-enter, #statsPage.page-enter,
#monthCalPage.page-enter {
  opacity: 1;
}
/* ANDROID (html.andr): Seitenwechsel INSTANT statt Fade. Der Fade zeigte beim Wechsel
   ~200ms den dunklen Body-Hintergrund durch die opacity-0-Seite („wird kurz dunkel und
   dann wieder normal", OnePlus-Restflackern nach v497) — und opacity-über-Blur sowie das
   none↔blur-Wiederherstellen des Guards sind obendrein Adreno-Blitz-Quellen. Ohne Fade
   entfällt die komplette Kette; will-change weg = weniger Layer-Druck bei DPR 3.5.
   iPhone behält den weichen Fade unverändert. */
html.andr #calendarPage, html.andr #shopPage, html.andr #profilePage,
html.andr #statsPage, html.andr #monthCalPage {
  opacity: 1;
  transition: none;
  will-change: auto;
}
/* Flacker beim Seitenwechsel: backdrop-filter in einem Element, dessen Vorfahr gerade
   opacity animiert (die page-enter-Einblendung), flackert in Chromium auf High-DPR
   zuverlässig. Während des Wechsels (html.switching, ~220ms via showPage) den Header-Blur
   abschalten — der Header ist während des Fades ohnehin fast durchsichtig; danach kommt der
   Blur EINMALIG sauber zurück (kein opacity-Overlap = kein Flackern). Fade bleibt erhalten. */
/* :not(.andr): Android wechselt Seiten INSTANT (kein Fade, s. page-enter-Block) → dort
   animiert nichts über dem Blur, der Guard wäre selbst ein none↔blur-Re-Raster-Blitz. */
html.switching:not(.andr) .header,
html.switching:not(.andr) .shop-header,
html.switching:not(.andr) .profile-header,
html.switching:not(.andr) .stats-header,
html.switching:not(.andr) .monthcal-header,
html.switching:not(.andr) .stats-card {   /* Stats-Karten fadeten mit LIVE-Blur unter #statsPage → gleicher Bug, war ungeschützt */
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
}

/* ── ANDROID/Adreno-Flacker-Guards (html.andr, gesetzt im Boot-Script) ─────────
   Chromium auf Adreno rastert das Backdrop eines backdrop-filter-Elements bei JEDER
   Transform/Opacity-Animation des Elements (oder eines Vorfahren) pro Frame neu →
   1-Frame-Schwarz/Weiß-Blitz. WebKit snapshottet das Backdrop einmal → iPhone sauber.
   Daher NUR auf Android: Blur runter von animierten Karten/Sheets (alle haben ohnehin
   ~0.9-opake Verlaufs-Hintergründe; eine opake --card-Basis drunter = no-glass-Rezept,
   Optik fast identisch, Blitz weg). Toast = häufigster Fall (nach jedem Add/Abhaken). */
html.andr .toast,
html.andr .shiftplan-box,
html.andr .voice-modal-card,
html.andr .shop-photo-sheet-card,
html.andr .pass-box,
html.andr .td-briefing-card {
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
  background-color: var(--card);
}
/* Dauer-Repaint im Glas-Header stoppen: der 14s-Glow-Drift (background-position,
   nicht kompositierbar) hält die Header-Kachel permanent „dirty" — direkt neben der
   Blur-Ebene koppelt das auf Adreno in Re-Raster. Glow bleibt sichtbar, steht nur still
   (gleiches Muster wie html.anims-off, erprobt). */
html.andr .header::before,
html.andr .shop-header::before,
html.andr .profile-header::before,
html.andr .stats-header::before,
html.andr .monthcal-header::before {
  animation: none;
  background-position: 50% 50%;
}

/* 7) Bottom-Nav active-Wechsel: gleitende Pill (#navPuck, Definition oben bei
   der Nav). Transition-Liste identisch zur Basis-Regel der Nav — das frühere
   Tab-Aufleuchten (navFlashPulse) ist durch den Innen-Glow der Pill ersetzt. */
.nav-item {
  transition:
    flex-grow 0.55s cubic-bezier(0.34, 1.56, 0.64, 1),
    padding 0.55s cubic-bezier(0.34, 1.56, 0.64, 1),
    color 0.35s ease,
    opacity 0.30s cubic-bezier(0.32, 0.72, 0, 1);
}
.nav-item.active .nav-svg { transform: scale(1.05); }

/* 8) Toast – Premium Spring-Entry mit Blur-Easing */
.toast {
  filter: blur(4px);
  transition:
    transform 0.55s cubic-bezier(0.34, 1.56, 0.64, 1),
    opacity   0.35s ease-out,
    filter    0.35s ease-out;
}
.toast.show {
  filter: blur(0);
  transition:
    transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1),
    opacity   0.25s ease-out,
    filter    0.25s ease-out;
}

/* 9) Live-Dot Sync-Glimmer bei Remote-Update */
.live-status-dot.live-status-dot--inline.is-syncing {
  animation: liveDotSyncGlimmer 420ms cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes liveDotSyncGlimmer {
  0%   { filter: brightness(1)   drop-shadow(0 0 0   currentColor); transform: scale(1); }
  40%  { filter: brightness(1.6) drop-shadow(0 0 4px currentColor); transform: scale(1.45); }
  100% { filter: brightness(1)   drop-shadow(0 0 0   currentColor); transform: scale(1); }
}

/* 10) Avatar-Mini-Animationen — jede Variante hat ihren eigenen Charakter
   (Klassen setzt getAvatarHTML in app.js; sichtbar im Header + Profil).
   Kind-Elemente brauchen transform-box:fill-box, damit scale/rotate um die
   eigene Mitte statt um den SVG-Ursprung laufen. Lange Zyklen + kleine
   Amplituden → lebendig, ohne im 40px-Header zu nerven. Reduced-Motion
   deaktiviert alles über die globale Regel oben. */
.avatar-anim svg { overflow: visible; }
.avatar-anim svg circle, .avatar-anim svg path, .avatar-anim svg rect,
.avatar-anim svg ellipse, .avatar-anim svg polygon, .avatar-anim svg line {
  transform-box: fill-box;
  transform-origin: center;
}
/* 😊 blinzelt alle paar Sekunden */
.avatar-anim--smile svg circle:nth-of-type(2),
.avatar-anim--smile svg circle:nth-of-type(3) { animation: avBlink 3.4s ease-in-out infinite; }
@keyframes avBlink {
  0%, 90%, 100% { transform: scaleY(1); }
  93%, 96%      { transform: scaleY(0.12); }
}
/* 😎 rückt sich lässig die Brille zurecht */
.avatar-anim--cool svg rect,
.avatar-anim--cool svg line { animation: avCoolGlasses 4.2s ease-in-out infinite; }
@keyframes avCoolGlasses {
  0%, 76%, 100% { transform: translateY(0); }
  84%, 90%      { transform: translateY(1.6px); }
}
/* 🧑‍🍳 wippt mit der Kochmütze */
.avatar-anim--chef svg ellipse,
.avatar-anim--chef svg rect { animation: avChefHat 3.2s ease-in-out infinite; }
@keyframes avChefHat {
  0%, 100% { transform: translateY(0) rotate(0); }
  35%      { transform: translateY(-1.4px) rotate(-3deg); }
  65%      { transform: translateY(-0.6px) rotate(2deg); }
}
/* 👩‍🍳 wiegt sich sanft hin und her */
.avatar-anim--chefin svg { animation: avSway 3.8s ease-in-out infinite; }
@keyframes avSway {
  0%, 100% { transform: rotate(-2.5deg); }
  50%      { transform: rotate(2.5deg); }
}
/* ⭐ funkelt (Doppel-Twinkle mit Pause) */
.avatar-anim--star svg polygon { animation: avTwinkle 3.0s ease-in-out infinite; }
@keyframes avTwinkle {
  0%, 55%, 100% { transform: scale(1) rotate(0); }
  64%           { transform: scale(1.14) rotate(8deg); }
  72%           { transform: scale(0.98) rotate(-3deg); }
  80%           { transform: scale(1.08) rotate(4deg); }
}
/* ❤️ schlägt im Doppel-Takt (lub-dub) */
.avatar-anim--heart svg path { animation: avHeartbeat 2.4s ease-in-out infinite; }
@keyframes avHeartbeat {
  0%, 36%, 100% { transform: scale(1); }
  10%           { transform: scale(1.16); }
  18%           { transform: scale(1); }
  26%           { transform: scale(1.10); }
}
/* ⚡ blitzt kurz auf */
.avatar-anim--bolt svg polygon { animation: avBoltFlash 3.2s linear infinite; }
@keyframes avBoltFlash {
  0%, 84%, 100% { opacity: 1; filter: none; }
  88%           { opacity: 0.35; }
  92%           { opacity: 1; filter: drop-shadow(0 0 3px currentColor); }
  96%           { opacity: 0.7; }
}
/* 🔥 flackert dauerhaft (Origin unten: Flamme züngelt nach oben) */
.avatar-anim--fire svg path { transform-origin: 50% 100%; animation: avFlicker 1.7s ease-in-out infinite; }
@keyframes avFlicker {
  0%, 100% { transform: scaleY(1)    scaleX(1); }
  25%      { transform: scaleY(1.06) scaleX(0.96); }
  55%      { transform: scaleY(0.95) scaleX(1.04); }
  80%      { transform: scaleY(1.04) scaleX(0.98); }
}

/* 11) Konfetti-Burst beim Abhaken im Einkauf (uiFx.burstAt setzt --dx/--dy) */
.fx-burst {
  position: fixed;
  width: 0; height: 0;
  z-index: 500;
  pointer-events: none;
}
.fx-burst span {
  position: absolute;
  left: -2.5px; top: -2.5px;
  width: 5px; height: 5px;
  border-radius: 50%;
  background: rgb(var(--cell-glow-rgb));
  box-shadow: 0 0 6px rgba(var(--cell-glow-rgb), 0.8);
  animation: fxBurst 0.45s ease-out both;
}
@keyframes fxBurst {
  0%   { transform: translate(0, 0) scale(1); opacity: 1; }
  100% { transform: translate(var(--dx), var(--dy)) scale(0.2); opacity: 0; }
}

/* 12) Kalender-Karten-Stagger beim Tab-Wechsel (Klasse setzt showPage kurz) */
.cal-stagger .week-row {
  animation: weekRowIn 0.30s cubic-bezier(0.22, 0.61, 0.36, 1) both;
  animation-delay: calc(var(--row-i, 0) * 28ms);
}
@keyframes weekRowIn {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* 13) Stats: Balken wachsen beim Rendern von 0 auf ihren Wert
   (kein to-Frame → Ziel ist die Inline-Breite; Zahlen zählt statsView hoch) */
.stat-bar span, .store-bar span {
  animation: statBarGrow 0.6s cubic-bezier(0.22, 0.61, 0.36, 1) both;
}
@keyframes statBarGrow { from { width: 0; } }

/* 14) Wochenplan: Reload-Dreh + Zeilen blenden nacheinander neu ein */
.weekplan-reroll.spin svg { animation: rerollSpin 0.5s cubic-bezier(0.32, 0.72, 0, 1); }
@keyframes rerollSpin { to { transform: rotate(360deg); } }
.wp-row {
  animation: wpRowIn 0.32s cubic-bezier(0.22, 0.61, 0.36, 1) both;
  animation-delay: calc(var(--i, 0) * 45ms);
}
@keyframes wpRowIn {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

