/* ============================================================
   Dilemma — motion.css
   Premium motion layer. Loads AFTER style.css so it can refine.
   Rules:
     - Boldness spent on ONE signature moment: odds tick.
     - Everything else: quiet, disciplined, sub-400ms.
     - prefers-reduced-motion: reduce -> all decorative motion off.
   ============================================================ */

:root{
    --dur-instant:120ms;
    --dur-fast:220ms;
    --dur-base:360ms;
    --dur-slow:600ms;

    --ease-standard:cubic-bezier(.4,0,.2,1);
    --ease-out-quint:cubic-bezier(.22,1,.36,1);
    --ease-spring:cubic-bezier(.34,1.56,.64,1);

    --tick-flash-yes:rgba(2,162,254,.22);
    --tick-flash-no:rgba(253,37,112,.22);
    --tick-glow-yes:0 0 0 1px rgba(2,162,254,.45), 0 6px 18px rgba(2,162,254,.28);
    --tick-glow-no:0 0 0 1px rgba(253,37,112,.45), 0 6px 18px rgba(253,37,112,.28);
}

/* ----------------------------------------------------------------
   1. ODDS TICK — the signature moment.
   A value change does: flash bg + flip-Y of the digit + soft glow.
   Direction is encoded by data-odd ("yes"|"no") and .tick-up/.tick-down.
   ---------------------------------------------------------------- */

.pred-odd-val{
    display:inline-block;
    min-width:2.4em;
    text-align:center;
    font-variant-numeric:tabular-nums;
    transition:color var(--dur-fast) var(--ease-standard);
    will-change:transform;
}

.pred-odd-val::after{
    content:"";
    position:absolute;
    inset:0;
    border-radius:inherit;
    pointer-events:none;
    opacity:0;
}

/* Flip the digit on change */
@keyframes oddTickFlip{
    0%   { transform:translateY(0)      rotateX(0);   }
    45%  { transform:translateY(-.08em) rotateX(75deg); opacity:.35; }
    55%  { transform:translateY(.08em)  rotateX(-75deg); opacity:.35; }
    100% { transform:translateY(0)      rotateX(0);   }
}

.pred-odd-val.is-ticking{
    animation:oddTickFlip var(--dur-base) var(--ease-out-quint);
}

/* Whole button flash + ring while ticking */
.pred-btn.pred-yes.is-flashing{
    background-image:linear-gradient(180deg,var(--tick-flash-yes),transparent 70%);
    box-shadow:var(--tick-glow-yes);
    transition:background-image var(--dur-fast) var(--ease-standard),
               box-shadow var(--dur-slow) var(--ease-out-quint);
}
.pred-btn.pred-no.is-flashing{
    background-image:linear-gradient(180deg,var(--tick-flash-no),transparent 70%);
    box-shadow:var(--tick-glow-no);
    transition:background-image var(--dur-fast) var(--ease-standard),
               box-shadow var(--dur-slow) var(--ease-out-quint);
}

/* Tiny direction arrow that fades in/out next to the number.
   Injected by JS as a span.odd-arrow inside .pred-odd-val. */
.odd-arrow{
    display:inline-block;
    margin-left:.18em;
    font-size:.7em;
    opacity:0;
    transform:translateY(.15em);
    transition:opacity var(--dur-fast) var(--ease-standard),
               transform var(--dur-base) var(--ease-spring);
}
.odd-arrow.is-shown{ opacity:.95; transform:translateY(0); }
.odd-arrow.dir-up   { color:#10b981; }
.odd-arrow.dir-down { color:#ef4444; }

/* ----------------------------------------------------------------
   2. BALANCE COUNT — header DP / Kupa.
   The number itself is rolled by JS; we add a brief sheen + nudge.
   ---------------------------------------------------------------- */

#dpBalanceText,
#cupsBalanceText{
    display:inline-block;
    font-variant-numeric:tabular-nums;
    transition:color var(--dur-fast) var(--ease-standard);
}

/* Override the old opacity-only animation with a real lift + glow. */
@keyframes balanceCountUp{
    0%   { transform:translateY(0)      scale(1);    text-shadow:none; }
    40%  { transform:translateY(-.12em) scale(1.06); text-shadow:0 0 12px rgba(2,162,254,.45); }
    100% { transform:translateY(0)      scale(1);    text-shadow:none; }
}
@keyframes balanceCountDown{
    0%   { transform:translateY(0)      scale(1); text-shadow:none; }
    40%  { transform:translateY(.10em)  scale(.97); text-shadow:0 0 10px rgba(253,37,112,.35); }
    100% { transform:translateY(0)      scale(1); text-shadow:none; }
}

#dpBalanceText.animating,
#cupsBalanceText.animating,
#dpBalanceText.animating-up,
#cupsBalanceText.animating-up{
    animation:balanceCountUp var(--dur-slow) var(--ease-out-quint);
}
#dpBalanceText.animating-down,
#cupsBalanceText.animating-down{
    animation:balanceCountDown var(--dur-slow) var(--ease-out-quint);
}

/* Color hint while animating (subtle, reverts via transition). */
#dpBalanceText.animating-up,
#cupsBalanceText.animating-up   { color:#02a2fe; }
#dpBalanceText.animating-down,
#cupsBalanceText.animating-down { color:#fd2570; }

/* ----------------------------------------------------------------
   3. QUIET SUPPORTING MOTION — restraint.
   No fade-in-on-scroll. No parallax. Just hover refinement.
   ---------------------------------------------------------------- */

.pred-card{
    transition:transform var(--dur-fast) var(--ease-out-quint),
               box-shadow var(--dur-base) var(--ease-out-quint);
}
.pred-card:hover{
    transform:translateY(-2px);
}

.pred-btn{
    transition:transform var(--dur-instant) var(--ease-out-quint),
               background-color var(--dur-fast) var(--ease-standard),
               background-image var(--dur-fast) var(--ease-standard),
               box-shadow var(--dur-fast) var(--ease-out-quint);
}
.pred-btn:active{
    transform:scale(.97);
}

/* ----------------------------------------------------------------
   3b. CARD ENTER — stagger on initial mount.
   Spec: opacity 0->1 + scale 0.98->1, stagger 40ms by --card-i.
   No translateY (deliberately not AOS fade-up).
   Gated by html.js so non-JS users see content immediately (no FOUC
   trap if JS fails); --card-i comes from {% forloop.counter0 %} inline.
   ---------------------------------------------------------------- */
@keyframes cardEnter{
    from { opacity:0; transform:scale(.98); }
    to   { opacity:1; transform:scale(1); }
}

html.js .pred-grid > .pred-card{
    opacity:0;
    transform:scale(.98);
    animation:cardEnter 420ms var(--ease-out-quint) both;
    animation-delay:calc(var(--card-i, 0) * 40ms);
}

/* Hard cap: anything past the 14th card has no extra delay,
   so a 60-card grid does not feel sluggish at the tail. */
html.js .pred-grid > .pred-card[style*="--card-i:14"],
html.js .pred-grid > .pred-card[style*="--card-i:15"],
html.js .pred-grid > .pred-card[style*="--card-i:16"],
html.js .pred-grid > .pred-card[style*="--card-i:17"],
html.js .pred-grid > .pred-card[style*="--card-i:18"],
html.js .pred-grid > .pred-card[style*="--card-i:19"]{
    animation-delay:560ms;
}

/* ----------------------------------------------------------------
   3c. THEME REVEAL — view transitions radial wipe.
   Triggered by JS via document.startViewTransition().
   The JS animates ::view-transition-new(root) with a clip-path
   circle expanding from the click point. We just provide the
   pseudo-element scaffolding here.
   ---------------------------------------------------------------- */
@supports (view-transition-name: root){
    ::view-transition-old(root),
    ::view-transition-new(root){
        animation:none;
        mix-blend-mode:normal;
    }
    ::view-transition-old(root){ z-index:1; }
    ::view-transition-new(root){ z-index:2; }
}

/* ----------------------------------------------------------------
   3d. COUPON STAMP — signature of the my-coupons page.
   Chip lands like a passport stamp on a ticket: scale up + small
   rotation impulse + opacity. WON adds a one-time diagonal gold
   sheen sweep (no loop). WAITING dot breathes slowly (still alive).
   LOST is deliberately quiet — no extra sheen, just the stamp itself.
   ---------------------------------------------------------------- */

.status-chip{
    position:relative;
    overflow:hidden;
    will-change:transform;
}

@keyframes chipStamp{
    0%   { opacity:0; transform:scale(.6) rotate(-3deg); }
    60%  { opacity:1; transform:scale(1.04) rotate(.6deg); }
    100% { opacity:1; transform:scale(1) rotate(0); }
}

.status-chip.is-stamping{
    animation:chipStamp 360ms var(--ease-spring) both;
}

/* WAITING — quiet dot breath, infinite. The DOT, not the whole chip.
   Dot is 10px so we need a wider scale swing (0.7 -> 1.25) and a
   stronger opacity contrast (0.35 -> 1) for the pulse to be visible.
   Bumped specificity with .coupon-card so it overrides the existing
   inline !important sizing rule. */
@keyframes chipBreath{
    0%, 100% { opacity:.35; transform:scale(.7); }
    50%      { opacity:1;   transform:scale(1.25); }
}

html.js .coupon-card .status-chip.waiting .dot,
html.js .status-chip.waiting .dot{
    animation:chipBreath 1.4s var(--ease-standard) infinite;
    transform-origin:center;
    will-change:opacity, transform;
}

/* WON — single diagonal sheen pass, only when the chip stamps in.
   Uses ::after so we don't disturb existing chip layout. */
.status-chip.won::after{
    content:"";
    position:absolute;
    inset:-1px;
    border-radius:inherit;
    background:linear-gradient(115deg,
        transparent 30%,
        rgba(251,191,36,.55) 50%,
        transparent 70%);
    transform:translateX(-130%);
    opacity:0;
    pointer-events:none;
}
.status-chip.won.is-stamping::after{
    animation:goldSheen 900ms var(--ease-out-quint) 220ms;
}
@keyframes goldSheen{
    0%   { transform:translateX(-130%); opacity:0; }
    20%  { opacity:1; }
    80%  { opacity:1; }
    100% { transform:translateX(130%); opacity:0; }
}

/* ----------------------------------------------------------------
   3e. COUPON TAB SWAP — restraint over slide.
   When a coupon-list becomes .active, content cross-fades in.
   No horizontal slide (that's the generic dashboard pattern).
   Chip stamps + card hover handled by JS / existing rules.
   ---------------------------------------------------------------- */

@keyframes couponListEnter{
    from { opacity:0; transform:scale(.992); }
    to   { opacity:1; transform:scale(1); }
}

html.js .coupon-list.active.is-entering{
    animation:couponListEnter 240ms var(--ease-out-quint) both;
    transform-origin:top center;
}

/* Card hover — same vocabulary as predictions, no new idiom. */
.coupon-card{
    transition:transform var(--dur-fast) var(--ease-out-quint),
               box-shadow var(--dur-base) var(--ease-out-quint);
}
.coupon-card:hover{
    transform:translateY(-2px);
}

/* Search filter — soft hide/show via opacity + scale.
   The existing JS uses display:""/none; we add a transition
   on the filter-hidden state via a class instead. */
.coupon-card{
    transition:transform var(--dur-fast) var(--ease-out-quint),
               box-shadow var(--dur-base) var(--ease-out-quint),
               opacity var(--dur-fast) var(--ease-standard);
}

/* ----------------------------------------------------------------
   4. ACCESSIBILITY — respect reduced motion. ALL decorative motion off,
   but keep state-change feedback (color, opacity) so users still get
   confirmation when odds/balance update.
   ---------------------------------------------------------------- */
@media (prefers-reduced-motion:reduce){
    .pred-odd-val.is-ticking,
    #dpBalanceText.animating,
    #cupsBalanceText.animating,
    #dpBalanceText.animating-up,
    #cupsBalanceText.animating-up,
    #dpBalanceText.animating-down,
    #cupsBalanceText.animating-down,
    html.js .pred-grid > .pred-card,
    .status-chip.is-stamping,
    .status-chip.won.is-stamping::after,
    html.js .status-chip.waiting .dot,
    html.js .coupon-list.active.is-entering{
        animation:none !important;
    }
    html.js .pred-grid > .pred-card,
    .status-chip,
    html.js .coupon-list.active.is-entering{
        opacity:1 !important;
        transform:none !important;
    }
    .pred-card,
    .pred-btn,
    .pred-odd-val,
    .odd-arrow,
    .coupon-card{
        transition-duration:0ms !important;
    }
    .pred-card:hover,
    .coupon-card:hover{ transform:none; }
    .pred-btn:active{ transform:none; }

    @supports (view-transition-name: root){
        ::view-transition-old(root),
        ::view-transition-new(root){
            animation:none !important;
        }
    }
}
