/* ============================================================
   Creamy Bite – Motion & polish
   Loaded after style.css on every public page.

   Principles used here:
   - Motion is short (180–520ms) and eased; nothing bounces for its own sake.
   - Nothing animates that would move text the reader is already reading.
   - Content is VISIBLE BY DEFAULT. Reveal-on-scroll is opt-in via .reveal and
     is switched on by animations.js only once JS confirms IntersectionObserver
     exists — so if the script fails the page is never left blank.
   - Every effect is disabled under prefers-reduced-motion.
   ============================================================ */

/* ── Page entrance ───────────────────────────────────────── */
@keyframes cbFadeUp   { from { opacity:0; transform: translate3d(0,18px,0); } to { opacity:1; transform:none; } }
@keyframes cbFadeIn   { from { opacity:0; } to { opacity:1; } }
@keyframes cbScaleIn  { from { opacity:0; transform: scale(.96); } to { opacity:1; transform:none; } }
@keyframes cbPop      { 0%{transform:scale(1)} 45%{transform:scale(1.22)} 100%{transform:scale(1)} }
@keyframes cbSpin     { to { transform: rotate(360deg); } }
@keyframes cbShimmer  { 0%{background-position:-460px 0} 100%{background-position:460px 0} }

body { animation: cbFadeIn .32s ease-out both; }

/* ── Sticky header: gains depth once you scroll ──────────── */
/* .navbar is already position:sticky in style.css; this adds the scrolled state. */
.navbar {
    transition: padding .28s cubic-bezier(.4,0,.2,1),
                background .28s ease,
                box-shadow .28s ease,
                border-color .28s ease;
    will-change: padding, box-shadow;
}
.navbar.cb-scrolled {
    padding: 8px 0;
    background: rgba(255, 248, 242, 0.97);
    box-shadow: 0 6px 26px rgba(92, 29, 36, 0.10);
    border-bottom-color: rgba(199, 136, 41, 0.22);
}
.navbar .logo-img { transition: height .28s cubic-bezier(.4,0,.2,1); }
.navbar.cb-scrolled .logo-img { height: 30px; }

/* Thin progress bar showing how far down the page you are. */
.cb-progress {
    position: fixed; top: 0; left: 0; height: 3px; width: 0;
    background: linear-gradient(90deg, var(--color-primary, #c78829), var(--color-secondary, #00c9a7));
    z-index: 999; pointer-events: none;
    transition: width .1s linear;
}

/* ── Reveal on scroll ────────────────────────────────────── */
/* Only applies once JS adds .cb-motion to <html>, so no-JS users see content. */
.cb-motion .reveal          { opacity: 0; transform: translate3d(0, 22px, 0);
                              transition: opacity .52s cubic-bezier(.16,1,.3,1),
                                          transform .52s cubic-bezier(.16,1,.3,1); }
.cb-motion .reveal.is-visible { opacity: 1; transform: none; }

.cb-motion .reveal-left     { opacity:0; transform: translate3d(-26px,0,0);
                              transition: opacity .52s cubic-bezier(.16,1,.3,1),
                                          transform .52s cubic-bezier(.16,1,.3,1); }
.cb-motion .reveal-left.is-visible { opacity:1; transform:none; }

.cb-motion .reveal-right    { opacity:0; transform: translate3d(26px,0,0);
                              transition: opacity .52s cubic-bezier(.16,1,.3,1),
                                          transform .52s cubic-bezier(.16,1,.3,1); }
.cb-motion .reveal-right.is-visible { opacity:1; transform:none; }

.cb-motion .reveal-zoom     { opacity:0; transform: scale(.94);
                              transition: opacity .48s ease, transform .48s cubic-bezier(.16,1,.3,1); }
.cb-motion .reveal-zoom.is-visible { opacity:1; transform:none; }

/* Stagger children of a revealed group (product grids, feature rows). */
.cb-motion .reveal.is-visible > *,
.cb-motion .stagger.is-visible > * { animation: cbFadeUp .5s cubic-bezier(.16,1,.3,1) both; }
.cb-motion .stagger.is-visible > *:nth-child(1) { animation-delay:.02s }
.cb-motion .stagger.is-visible > *:nth-child(2) { animation-delay:.07s }
.cb-motion .stagger.is-visible > *:nth-child(3) { animation-delay:.12s }
.cb-motion .stagger.is-visible > *:nth-child(4) { animation-delay:.17s }
.cb-motion .stagger.is-visible > *:nth-child(5) { animation-delay:.22s }
.cb-motion .stagger.is-visible > *:nth-child(6) { animation-delay:.27s }
.cb-motion .stagger.is-visible > *:nth-child(7) { animation-delay:.32s }
.cb-motion .stagger.is-visible > *:nth-child(8) { animation-delay:.37s }
.cb-motion .stagger.is-visible > *:nth-child(n+9){ animation-delay:.4s  }

/* The product image is NOT staggered. It is a direct child of
   .product-card (which is itself a .reveal), so the rule above would fade
   it up a second time, sliding the photo inside a card that is already
   moving. The card's own reveal carries it in. */
.cb-motion .reveal.is-visible > .product-image-wrap { animation: none; }

/* ── Cards & panels ──────────────────────────────────────── */
/* Product cards used to be in this rule too. They now have their own
   section below — see "Product cards: the hover system". */
.glass-panel,
.gallery-item {
    transition: transform .3s cubic-bezier(.4,0,.2,1),
                box-shadow .3s cubic-bezier(.4,0,.2,1),
                border-color .3s ease;
}
.gallery-item:hover {
    transform: translateY(-6px);
    box-shadow: 0 18px 40px rgba(92, 29, 36, .14);
}
.gallery-item img       { transition: transform .5s cubic-bezier(.4,0,.2,1); }
.gallery-item:hover img { transform: scale(1.06); }

/* Keep the image zoom inside the rounded corners. */
.gallery-item { overflow: hidden; }


/* ── Product cards: the hover system ─────────────────────────
   EVERY hover / pointer state for .product-card is in this section and
   nowhere else. It was previously spread over four blocks in two files:
   two lifts, two image zooms (scale(1.06) here and scale(1.07) in
   style.css) and a jelly wobble, all resolved by source order rather
   than by intent — so which zoom you got depended on which rule happened
   to be more specific that day.

   The effect, composed as one gesture:
     · the card lifts 8px and its border warms
     · a shadow layer fades in underneath, deepening the card
     · the photo zooms slowly to 1.06 — one canonical value now
     · a soft sheen sweeps once across the photo
     · the top accent line thickens, the title takes the brand colour
     · the add-to-cart button steps forward

   Only transform and opacity are animated, so every frame is composited.
   Timing is deliberately restrained: a long, decelerating ease-out
   (cubic-bezier(.16,1,.3,1)) rather than anything that overshoots.

   Markup this styles (pages/order.php):
     article.product-card > .product-image-wrap > img|.product-image-placeholder
                          > .product-body > .product-name, .product-footer > .btn-add-cart
   ------------------------------------------------------------ */

/* Resting transitions.
   The .reveal variant is not optional: animations.js auto-tags every
   .product-card with .reveal, and `.cb-motion .reveal` (0,2,0) declares its
   own transition list, which would otherwise hand the hover its entrance
   timing. The opacity entry is repeated so the scroll-reveal fade survives.
   This block sits after .glass-panel above on purpose — a product card is
   also a .glass-panel, and `transition` is one property: last rule wins. */
.product-card {
    transition: transform .5s cubic-bezier(.16, 1, .3, 1),
                border-color .32s ease;
}
.cb-motion .product-card.reveal {
    transition: opacity .52s cubic-bezier(.16, 1, .3, 1),
                transform .5s cubic-bezier(.16, 1, .3, 1),
                border-color .32s ease;
}

/* The lift shadow, as its own layer.
   Transitioning box-shadow on the card itself repaints the card on every
   frame; cross-fading a pre-rendered layer is one composited opacity
   change. This is why .product-card dropped `overflow: hidden` (style.css)
   — an outer shadow drawn inside a clipping box is invisible.
   No z-index is needed: the layer has no background of its own and an
   outer box-shadow only paints OUTSIDE its box, so it can never sit over
   the card's content. */
.product-card::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    pointer-events: none;
    opacity: 0;
    box-shadow: 0 22px 44px -18px rgba(92, 29, 36, .38),
                0 8px 18px -10px rgba(92, 29, 36, .22);
    transition: opacity .5s cubic-bezier(.16, 1, .3, 1);
}

/* Slower than the card's own move, so the photo settles last. */
.product-card .product-image-wrap img   { transition: transform .7s cubic-bezier(.16, 1, .3, 1); }
.product-card .product-image-placeholder { transition: transform .55s cubic-bezier(.16, 1, .3, 1); }
.product-card .product-image-wrap::before { transition: transform .4s cubic-bezier(.16, 1, .3, 1); }
.product-card .product-name             { transition: color .3s ease; }

/* The sheen: a narrow diagonal highlight parked off the left edge of the
   photo, swept across once on hover. .product-image-wrap clips it. */
.product-card .product-image-wrap::after {
    content: '';
    position: absolute;
    z-index: 3;              /* over both the photo and the accent line */
    top: -20%; bottom: -20%;
    left: -75%;
    width: 60%;
    pointer-events: none;
    opacity: 0;
    background: linear-gradient(100deg,
                rgba(255, 255, 255, 0)   0%,
                rgba(255, 255, 255, .55) 50%,
                rgba(255, 255, 255, 0)   100%);
    transform: skewX(-14deg);
}
@keyframes cbCardSheen {
    from { transform: translate3d(0, 0, 0) skewX(-14deg);    opacity: 0; }
    22%  { opacity: 1; }
    to   { transform: translate3d(330%, 0, 0) skewX(-14deg); opacity: 0; }
}

/* Everything below is pointer-only. A touch device matches none of it, so
   the card is complete without hover and can never be left stuck in a
   hover state after a tap. */
@media (hover: hover) and (pointer: fine) {

    /* The lift.
       `:not(.out-of-stock)` states the intent AND buys the specificity
       (0,3,0) this rule needs to outrank `.cb-motion .reveal.is-visible {
       transform: none }` near the top of this file. Without it the lift
       silently did nothing the moment a card had been revealed on scroll,
       which is most of them.
       box-shadow is pinned to the resting value so .glass-panel:hover
       cannot swap a second shadow in underneath the ::after layer. */
    .product-card:not(.out-of-stock):hover {
        transform: translate3d(0, -8px, 0);
        border-color: var(--border-color);
        box-shadow: var(--shadow-sm);
        will-change: transform;
    }
    .product-card:not(.out-of-stock):hover::after { opacity: 1; }

    .product-card:not(.out-of-stock):hover .product-image-wrap img {
        transform: scale(1.06);
    }
    /* Cards with no photo lift their icon instead — the old jelly wobble
       was the one genuinely cartoonish thing on the card. */
    .product-card:not(.out-of-stock):hover .product-image-placeholder {
        transform: scale(1.06) translateY(-3px);
    }
    .product-card:not(.out-of-stock):hover .product-image-wrap::before {
        transform: scaleY(1.75);
    }
    .product-card:not(.out-of-stock):hover .product-image-wrap::after {
        animation: cbCardSheen .85s cubic-bezier(.33, .1, .25, 1);
    }
    .product-card:not(.out-of-stock):hover .product-name {
        color: var(--color-primary);
    }
    /* The button keeps its own, stronger :hover. `:not(:hover)` stops this
       weaker card-level cue from overriding it when the pointer is
       actually on the button. */
    .product-card:not(.out-of-stock):hover .btn-add-cart:not(:hover):not(:disabled) {
        transform: translateY(-2px);
    }
}

/* Sold out: no lift, no depth, no invitation. */
.product-card.out-of-stock:hover { transform: none; box-shadow: none; }

/* Reduced motion: nothing moves. Depth, border and colour stay, because
   they are not motion and the card should still answer the pointer.
   Must come after the (hover: hover) block — same specificity, later wins. */
@media (prefers-reduced-motion: reduce) {
    .product-card,
    .cb-motion .product-card.reveal,
    .product-card::after,
    .product-card .product-image-wrap img,
    .product-card .product-image-placeholder,
    .product-card .product-image-wrap::before { transition: none; }

    .product-card:not(.out-of-stock):hover,
    .product-card:not(.out-of-stock):hover .product-image-wrap img,
    .product-card:not(.out-of-stock):hover .product-image-placeholder,
    .product-card:not(.out-of-stock):hover .product-image-wrap::before,
    .product-card:not(.out-of-stock):hover .btn-add-cart:not(:hover):not(:disabled) {
        transform: none;
        will-change: auto;
    }
    .product-card:not(.out-of-stock):hover .product-image-wrap::after {
        animation: none;
        opacity: 0;
    }
    .product-card:not(.out-of-stock):hover::after { opacity: 1; }
}

/* ── Buttons ─────────────────────────────────────────────── */
.btn-primary, .btn-secondary, .btn-view-cart, .btn-sm, .btn-danger {
    transition: transform .18s cubic-bezier(.4,0,.2,1),
                box-shadow .22s ease, background .22s ease,
                border-color .22s ease, color .22s ease;
}
.btn-primary:hover, .btn-secondary:hover, .btn-view-cart:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 20px rgba(92, 29, 36, .18);
}
.btn-primary:active, .btn-secondary:active, .btn-view-cart:active,
.btn-sm:active, .btn-danger:active { transform: translateY(0) scale(.985); }

/* Sheen sweep across primary buttons on hover. */
.btn-primary { position: relative; overflow: hidden; }
.btn-primary::after {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    background: linear-gradient(115deg, transparent 38%, rgba(255,255,255,.28) 50%, transparent 62%);
    transform: translateX(-120%);
    transition: transform .6s ease;
}
.btn-primary:hover::after { transform: translateX(120%); }

/* Busy state for buttons that trigger a request. */
.btn-loading { pointer-events: none; opacity: .75; }
.btn-loading i { animation: cbSpin .8s linear infinite; }

/* ── Cart badge ──────────────────────────────────────────── */
.cart-badge { transition: transform .2s cubic-bezier(.4,0,.2,1), background .2s ease; }
.cart-badge.cb-bump { animation: cbPop .42s cubic-bezier(.4,0,.2,1); }

/* Flies from the product to the cart when something is added. */
.cb-fly {
    position: fixed; z-index: 900; pointer-events: none;
    font-size: 26px; line-height: 1;
    transition: transform .72s cubic-bezier(.4,0,.35,1), opacity .72s ease;
}
/* The ghost is an icon rather than an emoji, so it needs a colour of its own —
   it would otherwise inherit whatever body text colour it flew over. */
.cb-fly > i { color: var(--color-primary, #c78829); }

/* ── Forms ───────────────────────────────────────────────── */
.form-control {
    transition: border-color .22s ease, box-shadow .22s ease, background .22s ease;
}
.form-control:focus {
    box-shadow: 0 0 0 4px rgba(199, 136, 41, .14);
    outline: none;
}

/* ── Links ───────────────────────────────────────────────── */
.nav-links a {
    position: relative;
    transition: color .22s ease;
}
.nav-links a::after {
    content: ""; position: absolute; left: 0; bottom: -4px; height: 2px; width: 100%;
    background: var(--color-primary, #c78829);
    transform: scaleX(0); transform-origin: right;
    transition: transform .28s cubic-bezier(.16,1,.3,1);
}
.nav-links a:hover::after,
.nav-links a.active::after { transform: scaleX(1); transform-origin: left; }

/* ── Back to top ─────────────────────────────────────────── */
.cb-top {
    position: fixed; right: 22px; bottom: 22px; z-index: 850;
    width: 46px; height: 46px; border-radius: 50%; border: none; cursor: pointer;
    background: var(--color-primary, #c78829); color: #fff; font-size: 15px;
    display: grid; place-items: center;
    box-shadow: 0 8px 22px rgba(92, 29, 36, .26);
    opacity: 0; visibility: hidden; transform: translateY(14px) scale(.9);
    transition: opacity .3s ease, transform .3s cubic-bezier(.16,1,.3,1), visibility .3s;
}
.cb-top.is-shown { opacity: 1; visibility: visible; transform: none; }
.cb-top:hover { transform: translateY(-3px); box-shadow: 0 12px 28px rgba(92, 29, 36, .34); }
.mobile-drawer.open ~ .cb-top { opacity: 0; visibility: hidden; }

@media (max-width: 480px) {
    .cb-top {
        width: 40px; height: 40px; right: 12px; bottom: 12px; font-size: 13px;
        background: rgba(199, 136, 41, .78);
        backdrop-filter: blur(3px);
        -webkit-backdrop-filter: blur(3px);
    }
}

/* ── Toast ───────────────────────────────────────────────── */
.cb-toast-wrap {
    position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%);
    z-index: 950; display: flex; flex-direction: column; gap: 8px; align-items: center;
    pointer-events: none;
}
.cb-toast {
    background: #23252b; color: #fff; font-size: 13.5px; font-weight: 600;
    padding: 11px 20px; border-radius: 30px; box-shadow: 0 10px 30px rgba(0,0,0,.24);
    animation: cbFadeUp .3s cubic-bezier(.16,1,.3,1) both;
    max-width: min(92vw, 420px); text-align: center;
}
.cb-toast.is-leaving { animation: cbFadeIn .28s ease reverse both; }
.cb-toast.ok    { background: #0f7a5a; }
.cb-toast.error { background: #a12a2a; }

/* ── Image loading ───────────────────────────────────────── */
img { transition: opacity .4s ease; }
img.cb-img-loading {
    opacity: 0;
    background: linear-gradient(90deg, #f2efec 25%, #e8e3de 37%, #f2efec 63%);
    background-size: 920px 100%;
    animation: cbShimmer 1.4s linear infinite;
}

/* ── Modals / drawers ────────────────────────────────────── */
.cart-drawer, .mobile-drawer, .modal, .lightbox {
    transition: opacity .3s ease, transform .3s cubic-bezier(.16,1,.3,1), visibility .3s;
}

/* ── Accessibility ───────────────────────────────────────── */
/* Honour the OS "reduce motion" setting. Everything still WORKS — it just
   arrives instantly instead of animating. Note the reveal elements are forced
   back to fully visible, so content can never be trapped invisible. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
        scroll-behavior: auto !important;
    }
    .cb-motion .reveal,
    .cb-motion .reveal-left,
    .cb-motion .reveal-right,
    .cb-motion .reveal-zoom {
        opacity: 1 !important;
        transform: none !important;
    }
    .cb-progress { display: none; }
}

/* Smooth in-page anchor scrolling for everyone else. */
@media (prefers-reduced-motion: no-preference) {
    html { scroll-behavior: smooth; }
}

/* ═══════════════════════════════════════════════════════════
   ICE CREAM THEME — playful touches that say "scoops, not SaaS".

   Everything in this block is decorative: it floats behind or
   around real content, never over it, and the rules that disable
   animation under prefers-reduced-motion also hide the purely
   decorative bits so nobody is left watching static confetti.
   ═══════════════════════════════════════════════════════════ */

/* ── Floating scoops & drips in the hero ────────────────── */
/* A slow, gentle bob — like a scoop wobbling in a bowl. */
@keyframes cbBob {
    0%, 100% { transform: translateY(0) rotate(-2deg); }
    50%      { transform: translateY(-14px) rotate(3deg); }
}

/* Long lazy drips for the hero decorations — one emoji melts
   slowly and resets, looped. Fine under reduced-motion. */
@keyframes cbDrip {
    0%   { transform: translateY(-8px) scaleY(.9); opacity: 0; }
    20%  { opacity: .9; }
    70%  { transform: translateY(46px) scaleY(1.12); opacity: .85; }
    100% { transform: translateY(64px) scaleY(1.2); opacity: 0; }
}

.cb-hero-float {
    position: absolute;
    z-index: 0;
    font-size: 52px;
    line-height: 1;
    pointer-events: none;
    user-select: none;
    filter: drop-shadow(0 10px 18px rgba(92, 29, 36, .22));
}
/* Sizes + positions are set by extra classes so a page can mix
   scoops and cones without repeating the block above. */
.cb-hero-float--sm { font-size: 34px; }
.cb-hero-float--lg { font-size: 74px; }

.cb-hero-float--bob    { animation: cbBob 4.5s ease-in-out infinite; }
.cb-hero-float--bob-2  { animation: cbBob 5.2s ease-in-out infinite reverse; }
.cb-hero-float--drip   { animation: cbDrip 5.5s ease-in-out infinite; }
.cb-hero-float--drip-2 { animation: cbDrip 6.2s ease-in-out 1.8s infinite; }

/* ── Falling sprinkles in the hero ──────────────────────── */
@keyframes cbSprinkle {
    0%   { transform: translate3d(0, -20px, 0) rotate(0deg); opacity: 0; }
    12%  { opacity: .95; }
    85%  { opacity: .75; }
    100% { transform: translate3d(var(--cb-drift, 26px), 430px, 0) rotate(300deg); opacity: 0; }
}

.cb-sprinkle {
    position: absolute;
    top: -14px;
    z-index: 0;
    width: 9px;
    height: 4px;
    border-radius: 4px;
    pointer-events: none;
    user-select: none;
    opacity: 0;
    animation: cbSprinkle var(--cb-fall, 6s) linear infinite;
    animation-delay: var(--cb-delay, 0s);
}
.cb-sprinkle--tall { width: 11px; height: 5px; }
.cb-sprinkle--round { width: 8px; height: 8px; border-radius: 50%; }

/* Brand-tinted sprinkle palette — matches the warm palette
   (chocolate, gold, mint, rose) rather than primary-colour
   confetti that clashes with it. */
.cb-sprinkle--cocoa    { background: #8C3A43; }
.cb-sprinkle--gold     { background: #D49A37; }
.cb-sprinkle--mint     { background: #0FA88A; }
.cb-sprinkle--rose     { background: #E86A7F; }
.cb-sprinkle--vanilla  { background: #F4E3C7; }
.cb-sprinkle--amber    { background: #C78829; }

/* ── Flavour card photo, on hover ──────────────────────── */
/* Used to be a jelly wobble (scale + rotate) on the whole image box — the
   product card used to do the same and moved away from it because the
   squash read as cartoonish next to the rest of the card (see the
   product-image-wrap hover system above). This now matches that system
   instead: the same slow, subtle zoom, on the image itself rather than
   its box, and pointer-only for the same reason — a touch device has no
   hover to get stuck in. */
.flavour-card-img img {
    transition: transform .7s cubic-bezier(.16, 1, .3, 1);
}
@media (hover: hover) and (pointer: fine) {
    .flavour-card:hover .flavour-card-img img {
        transform: scale(1.06);
    }
}

/* ── Sprinkle burst on add-to-cart ──────────────────────── */
/* Tiny coloured particles launched from the + button. Each one
   is positioned by JS with --cb-x / --cb-y offsets, so the same
   keyframes serve every starting point. */
@keyframes cbBurst {
    0%   { transform: translate(0, 0) scale(1); opacity: 1; }
    100% { transform: translate(var(--cb-x, 30px), var(--cb-y, -40px)) scale(.15); opacity: 0; }
}

.cb-burst {
    position: fixed;
    z-index: 960;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    pointer-events: none;
    animation: cbBurst .6s cubic-bezier(.16, 1, .3, 1) forwards;
}
.cb-burst--rod {
    width: 12px;
    height: 5px;
    border-radius: 5px;
}

/* ── Melting drip under section headings ────────────────── */
/* A row of three CSS "drips" under the section header — a drop
   shape (rounded top, pointed bottom) that settles into place
   once on page load. Pure CSS so it renders identically on
   every platform, unlike emoji. */
@keyframes cbDripSettle {
    0%   { transform: scaleY(0); opacity: 0; }
    70%  { opacity: 1; }
    100% { transform: scaleY(1); opacity: 1; }
}
.cb-drip-line {
    display: flex;
    justify-content: center;
    gap: 18px;
    margin: 10px auto 0;
    height: 16px;
}
.cb-drip-line span {
    width: 13px;
    height: 16px;
    border-radius: 60% 60% 55% 55% / 70% 70% 30% 30%;
    background: var(--drip-color, var(--color-primary-light, #8C3A43));
    opacity: 0;
    transform-origin: top center;
    /* The stagger comes from each child's own delay — the parent
       carries no animation of its own, so the drips do not settle
       twice. */
    animation: cbDripSettle .6s cubic-bezier(.16, 1, .3, 1) both;
}
.cb-drip-line span:nth-child(1) { --drip-color: var(--color-primary-light, #8C3A43); }
.cb-drip-line span:nth-child(2) { --drip-color: var(--color-secondary, #C78829); animation-delay: .12s; }
.cb-drip-line span:nth-child(3) { --drip-color: var(--color-accent, #D49A37); animation-delay: .24s; }

/* ── Order confirmation scoop-pop ───────────────────────── */
/* The 🎉 (or the page's chosen emoji) pops in with a scoop-roll:
   a small scale-up and settle, once, then stays still. */
@keyframes cbScoopPop {
    0%   { transform: scale(.4) rotate(-14deg); opacity: 0; }
    55%  { transform: scale(1.12) rotate(5deg); opacity: 1; }
    75%  { transform: scale(.96) rotate(-3deg); }
    100% { transform: scale(1) rotate(0deg); }
}
.cb-scoop-pop {
    animation: cbScoopPop .7s cubic-bezier(.34, 1.56, .64, 1) both;
}

/* A second scoop peeking in beside the confirmation icon, purely
   decorative — a tiny 🍦 that bobs while you read the page. */
.cb-conf-emoji {
    display: inline-block;
    margin-left: 10px;
    animation: cbBob 3.2s ease-in-out infinite;
}

/* Hide the decorative bits entirely for reduced-motion users —
   not just freeze them; they are pure decoration and no one
   should have to stare at static sprinkles. */
@media (prefers-reduced-motion: reduce) {
    .cb-hero-float,
    .cb-sprinkle,
    .cb-drip-line,
    .cb-conf-emoji { display: none !important; }
    .cb-scoop-pop { animation: none; }
    .flavour-card-img img { transition: none; }
}

/* ── Scoop-stacking loader (checkout button) ─────────────── */
/* When Place Order is pressed the button briefly builds an ice cream
   while the request is in flight: a waffle cone drops in, two scoops
   stack on top one at a time (each settling with a small bounce), and a
   cherry lands on top. Pure CSS shapes, so it renders identically
   everywhere with no image assets; the whole thing sways gently once
   assembled. The JS in animations.js swaps in a plain spinner for
   reduced-motion users, so this block needs no override of its own. */

@keyframes cbConeIn {
    0%   { transform: translateX(-50%) translateY(-14px); opacity: 0; }
    100% { transform: translateX(-50%) translateY(0); opacity: 1; }
}
@keyframes cbScoopDrop {
    0%   { transform: translateX(-50%) translateY(-22px) scale(.55); opacity: 0; }
    60%  { transform: translateX(-50%) translateY(3px) scale(1.1); opacity: 1; }
    80%  { transform: translateX(-50%) translateY(-2px) scale(.97); }
    100% { transform: translateX(-50%) translateY(0) scale(1); }
}
@keyframes cbCherryPop {
    0%   { transform: translateX(-50%) translateY(-14px) scale(0) rotate(-30deg); opacity: 0; }
    60%  { transform: translateX(-50%) translateY(2px) scale(1.3) rotate(8deg); opacity: 1; }
    100% { transform: translateX(-50%) translateY(0) scale(1) rotate(0); }
}

.cb-scoop-loader {
    display: inline-flex;
    align-items: center;
    gap: 12px;
}
.cb-scoop-ice {
    position: relative;
    width: 40px;
    height: 36px;
    flex: 0 0 auto;
    animation: cbBob 3.6s ease-in-out .95s infinite;
}
/* Waffle cone — a striped triangle. */
.cb-scoop-cone {
    position: absolute;
    left: 50%;
    bottom: 0;
    width: 20px;
    height: 14px;
    transform: translateX(-50%);
    background: linear-gradient(135deg, #C78829 25%, #D49A37 25%, #D49A37 50%, #C78829 50%, #C78829 75%, #D49A37 75%);
    background-size: 9px 9px;
    clip-path: polygon(0 0, 100% 0, 50% 100%);
    animation: cbConeIn .3s ease-out both;
}
/* Scoops — stacked with a soft highlight so they read as scoops. */
.cb-scoop-s {
    position: absolute;
    left: 50%;
    width: 22px;
    height: 16px;
    border-radius: 50% 50% 46% 46%;
    transform: translateX(-50%);
    box-shadow: inset 0 -4px 5px rgba(92, 29, 36, .16);
    animation: cbScoopDrop .5s cubic-bezier(.34, 1.56, .64, 1) both;
}
.cb-scoop-s--1 { bottom: 7px;  background: radial-gradient(circle at 32% 28%, #F7A9C4, #E86A7F 60%, #C24E6A); animation-delay: .14s; }
.cb-scoop-s--2 { bottom: 17px; background: radial-gradient(circle at 32% 28%, #FFDA90, #D49A37 60%, #A97A1D); animation-delay: .4s;  }
.cb-scoop-cherry {
    position: absolute;
    left: 50%;
    bottom: 29px;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: radial-gradient(circle at 34% 30%, #FF8FA5, #D6305B 65%);
    animation: cbCherryPop .45s cubic-bezier(.34, 1.56, .64, 1) .66s both;
}
.cb-scoop-label {
    font-size: 15px;
    font-weight: 700;
    line-height: 1.15;
    white-space: nowrap;
}

/* ── Category filter (order page) ─────────────────────────── */
/*  Hiding is a class, not an inline style, so the card keeps whatever
    display the grid gives it when it comes back.                     */
.product-card.cb-filtered-out { display: none !important; }

/*  The `to` frame deliberately does NOT name `transform`.
    A CSS animation outranks every normal author declaration, and this one
    is filled `both` — the class is never removed once a tab is clicked. So
    a `to { transform: none }` here did not just end the entrance, it pinned
    the card's transform to none for the rest of the page's life and the
    hover lift stopped working on every card in the filtered set.
    Omitting the property makes the 100% frame implicit, which resolves to
    the underlying cascade value — i.e. the hover transform when hovered —
    so the entrance still lands exactly where it did and hover survives it.
    `opacity` stays explicit: its forwards fill is what keeps a card that
    the scroll observer never reached from fading back out. */
@keyframes cbFilterIn {
    from { opacity: 0; transform: translate3d(0, 12px, 0); }
    to   { opacity: 1; }
}
/*  Applied to every card in the filtered set (see order.php).
    "both" holds the end state so nothing snaps when the run finishes. */
.product-card.cb-filter-in {
    animation: cbFilterIn .34s cubic-bezier(.16, 1, .3, 1) both;
}

/*  While filtering, the card moves as ONE piece. Without this, the generic
    stagger rule above also runs cbFadeUp on .product-body, so the text
    drifted at a different rate from the card carrying it — which is why
    different tabs appeared to use different effects. Specificity matches
    the stagger rule and this comes later, so it wins.                     */
.cb-motion .product-card.cb-filter-in > * { animation: none; }

/*  Cascade steps. The delay depends on a card's position in the FILTERED
    set, not its position in the DOM, so :nth-child cannot express it — the
    JS applies the matching step class. Kept as classes so no style
    attribute is ever written onto the element.                            */
.product-card.cb-step-0  { animation-delay: 0ms;   }
.product-card.cb-step-1  { animation-delay: 30ms;  }
.product-card.cb-step-2  { animation-delay: 60ms;  }
.product-card.cb-step-3  { animation-delay: 90ms;  }
.product-card.cb-step-4  { animation-delay: 120ms; }
.product-card.cb-step-5  { animation-delay: 150ms; }
.product-card.cb-step-6  { animation-delay: 180ms; }
.product-card.cb-step-7  { animation-delay: 210ms; }
.product-card.cb-step-8  { animation-delay: 240ms; }
.product-card.cb-step-9  { animation-delay: 270ms; }
/* Anything beyond this holds at the last step so a long list never crawls. */
.product-card.cb-step-max { animation-delay: 300ms; }

@media (prefers-reduced-motion: reduce) {
    .product-card.cb-filter-in { animation: none; }
}
