@import 'tailwindcss';
@import 'swiper/css';
@import 'swiper/css/navigation';
@import 'swiper/css/pagination';
@import 'swiper/css/effect-fade';

/* Self-hosted brand typefaces — only the weights actually used sitewide
   (checked via grep across every Blade view before adding these) so we
   don't ship unused font files. Previously `--font-display`/`--font-sans`
   named these families with no @font-face anywhere, so every browser was
   silently falling back to system serif/sans-serif the whole time. */
@import '@fontsource/playfair-display/400.css';
@import '@fontsource/manrope/400.css';
@import '@fontsource/manrope/500.css';
@import '@fontsource/manrope/600.css';
@import '@fontsource/manrope/700.css';

@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
@source '../../storage/framework/views/*.php';
@source '../**/*.blade.php';
@source '../**/*.js';

/*
|--------------------------------------------------------------------------
| Niayarsa Storefront Design Tokens
|--------------------------------------------------------------------------
| Luxury palette: ink/charcoal for depth, ivory for breathing room, and a
| champagne-gold + maroon duo for premium fashion accents. Used across
| the frontend layout, product cards, banners and buttons.
*/
@theme {
    --font-display: 'Playfair Display', ui-serif, Georgia, serif;
    --font-sans: 'Manrope', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
        'Segoe UI Symbol', 'Noto Color Emoji';

    --color-ink: #16130f;
    --color-charcoal: #262220;
    --color-ivory: #faf7f2;
    --color-cream: #f1e9dd;
    --color-champagne: #c9a661;
    --color-champagne-light: #e4c98e;
    --color-maroon: #6e1423;
    --color-maroon-light: #8c1f30;
    --color-blush: #f1e4da;

    --shadow-soft: 0 2px 12px -2px rgb(22 19 15 / 0.08);
    --shadow-card: 0 8px 30px -8px rgb(22 19 15 / 0.14);
    --shadow-luxury: 0 20px 60px -15px rgb(22 19 15 / 0.25);
    --shadow-luxury-lg: 0 30px 80px -20px rgb(22 19 15 / 0.35);
    --shadow-glow: 0 0 40px 0 rgb(201 166 97 / 0.28);
    --shadow-glow-strong: 0 0 50px 0 rgb(201 166 97 / 0.38);

    --ease-luxury: cubic-bezier(0.22, 1, 0.36, 1);

    --animate-fade-up: fade-up 0.8s var(--ease-luxury) both;
    --animate-float: float 6s ease-in-out infinite;
    --animate-float-slow: float 9s ease-in-out infinite;
    --animate-count-in: count-in 0.5s var(--ease-luxury) both;
    --animate-marquee: marquee 28s linear infinite;
    --animate-glow-pulse: glow-pulse 4s ease-in-out infinite;
}

/* Keyframes referenced by the --animate-* theme keys above (Tailwind v4
   generates the animate-* utilities from those keys, but the @keyframes
   themselves must live at the stylesheet's top level, not nested inside
   @theme) plus a couple used directly by component classes below. */
@keyframes fade-up {
    from {
        opacity: 0;
        transform: translateY(22px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}
@keyframes float {
    0%, 100% { transform: translateY(0) rotate(0deg); }
    50% { transform: translateY(-14px) rotate(1deg); }
}
@keyframes shimmer {
    0% { background-position: -400px 0; }
    100% { background-position: 400px 0; }
}
@keyframes ripple {
    to {
        transform: scale(3.2);
        opacity: 0;
    }
}
@keyframes count-in {
    from { opacity: 0; transform: scale(0.85); }
    to { opacity: 1; transform: scale(1); }
}
@keyframes marquee {
    from { transform: translateX(0); }
    to { transform: translateX(-50%); }
}
@keyframes glow-pulse {
    0%, 100% { opacity: 0.5; transform: scale(0.92); }
    50% { opacity: 1; transform: scale(1.08); }
}
/* Icon micro-animations — badge count change and wishlist-toggle heartbeat.
   Both are triggered from app.js by removing+re-adding the class (a plain
   class add doesn't restart a CSS animation already at rest on repeat
   toggles), and both are covered by the prefers-reduced-motion override
   below since it targets every animation-duration site-wide. */
@keyframes badge-pop {
    0% { transform: scale(1); }
    35% { transform: scale(1.35); }
    60% { transform: scale(0.92); }
    100% { transform: scale(1); }
}
@keyframes heartbeat {
    0%, 100% { transform: scale(1); }
    25% { transform: scale(1.3); }
    40% { transform: scale(0.95); }
    60% { transform: scale(1.15); }
}

@layer utilities {
    .glass {
        background: rgb(255 255 255 / 0.08);
        backdrop-filter: blur(16px) saturate(160%);
        -webkit-backdrop-filter: blur(16px) saturate(160%);
        border: 1px solid rgb(255 255 255 / 0.18);
    }

    .glass-light {
        background: rgb(255 255 255 / 0.55);
        backdrop-filter: blur(12px) saturate(150%);
        -webkit-backdrop-filter: blur(12px) saturate(150%);
        border: 1px solid rgb(255 255 255 / 0.4);
    }

    .text-gradient-gold {
        background: linear-gradient(120deg, #c9a661 0%, #e4c98e 50%, #c9a661 100%);
        -webkit-background-clip: text;
        background-clip: text;
        color: transparent;
    }

    .bg-gradient-luxury {
        background: linear-gradient(135deg, #16130f 0%, #262220 45%, #6e1423 100%);
    }

    .bg-gradient-champagne {
        background: linear-gradient(120deg, #c9a661 0%, #e4c98e 55%, #c9a661 100%);
        background-size: 200% auto;
    }

    .hover-lift {
        transition: transform 0.3s var(--ease-luxury), box-shadow 0.3s var(--ease-luxury);
    }
    .hover-lift:hover {
        transform: translateY(-6px);
        box-shadow: var(--shadow-luxury);
        /* Scoped to :hover only — reserves a compositor layer while the
           transition actually runs, not permanently on every card. */
        will-change: transform, box-shadow;
    }

    /* Subtle 3D tilt — paired with the mousemove handler in app.js, which
       toggles `will-change` itself on enter/leave. No permanent
       will-change here: a grid of 20 product cards each holding its own
       GPU layer all the time is wasted memory for 19 that are never
       touched. */
    .tilt-card {
        transition: transform 0.35s var(--ease-luxury);
        transform-style: preserve-3d;
    }

    /* Defers rendering cost for offscreen sections — paired with
       `data-reveal` below, both driven by IntersectionObserver so the
       browser still knows to un-defer right as content nears view. */
    .cv-auto {
        content-visibility: auto;
        contain-intrinsic-size: 0 700px;
    }

    /* Grid-level scroll reveal (see initRevealGroups in app.js): one
       IntersectionObserver per grid instead of AOS tracking every single
       card — cheap even on a page with dozens of products. Children fade
       up with a short CSS-only stagger, capped so a 30-item grid doesn't
       queue a 3-second cascade. */
    [data-reveal] > * {
        opacity: 0;
        transform: translateY(18px);
        transition: opacity 0.6s var(--ease-luxury), transform 0.6s var(--ease-luxury);
    }
    [data-reveal].is-visible > * {
        opacity: 1;
        transform: translateY(0);
    }
    [data-reveal] > *:nth-child(2) { transition-delay: 60ms; }
    [data-reveal] > *:nth-child(3) { transition-delay: 120ms; }
    [data-reveal] > *:nth-child(4) { transition-delay: 180ms; }
    [data-reveal] > *:nth-child(5) { transition-delay: 240ms; }
    [data-reveal] > *:nth-child(6) { transition-delay: 300ms; }
    [data-reveal] > *:nth-child(7) { transition-delay: 360ms; }
    [data-reveal] > *:nth-child(n+8) { transition-delay: 420ms; }

    /* Same reveal, for a single element instead of a group of children —
       replaces every remaining AOS usage sitewide. AOS ran its own
       scroll+resize listener (with per-element getBoundingClientRect reads)
       for the lifetime of the page regardless of how many elements used it;
       this is one IntersectionObserver, the same one data-reveal already
       uses, and it costs nothing between scroll frames. */
    [data-reveal-self] {
        opacity: 0;
        transform: translateY(18px);
        transition: opacity 0.6s var(--ease-luxury), transform 0.6s var(--ease-luxury);
    }
    [data-reveal-self="left"] { transform: translateX(-24px); }
    [data-reveal-self="right"] { transform: translateX(24px); }
    [data-reveal-self="down"] { transform: translateY(-18px); }
    [data-reveal-self].is-visible {
        opacity: 1;
        transform: translate(0, 0);
    }

    /* GPU-friendly zoom images: promotes any element using the sitewide
       group-hover:scale-* zoom pattern to its own compositor layer so the
       hover scale animates smoothly instead of repainting on every frame.
       Attribute-contains selector matches the raw class string, so this
       applies automatically wherever that Tailwind pattern is used —
       no per-template edits needed. */
    img[class*="group-hover:scale"] {
        backface-visibility: hidden;
        transform: translateZ(0);
    }

    /* Slow Ken Burns zoom on the active hero slide, done as a transition
       rather than a keyframe animation. A keyframe with `forwards` snaps
       instantly back to its base transform the moment Swiper removes
       swiper-slide-active (the class rule stops matching, so the animation
       is torn down mid-flight) — that abrupt snap, landing right as the
       fade-out starts, is what read as the hero "hanging" between slides.
       A transition has no such edge case: whatever the current scale is
       when the class comes off, it eases back to 1 smoothly instead of
       jumping. */
    .hero-zoom img {
        transform: scale(1);
        transition: transform 6s linear;
        will-change: transform;
    }
    .swiper-slide-active .hero-zoom img {
        transform: scale(1.08);
    }

    /* One-shot fade-up for the hero caption — plain CSS, not AOS. AOS runs
       an IntersectionObserver + getBoundingClientRect per data-aos element;
       with N hero slides stacked in the same viewport spot, that's N
       redundant measurements for text that's 95% never seen (only the
       active slide's caption is visible). This animates once on load and
       costs nothing per slide transition. */
    .hero-caption {
        animation: fade-up 0.8s var(--ease-luxury) both;
    }

    /* Breathing gold glow behind the logo on the empty-hero fallback (no
       banner uploaded yet). A pseudo-element instead of another absolutely
       positioned sibling div, so it's guaranteed centered on the logo it
       belongs to with no extra markup. */
    .hero-logo-glow { position: relative; }
    .hero-logo-glow::before {
        content: '';
        position: absolute;
        inset: -35%;
        z-index: -1;
        border-radius: 9999px;
        background: radial-gradient(circle, rgb(201 166 97 / 0.4), transparent 70%);
        animation: var(--animate-glow-pulse);
    }

    /* Floating fashion-motif icons drifting behind the empty-hero copy —
       reuses the float keyframes already defined above via animate-float /
       animate-float-slow utility classes; this just adds the depth shadow
       and keeps them from ever intercepting clicks/taps. */
    .hero-float {
        pointer-events: none;
        filter: drop-shadow(0 4px 10px rgb(0 0 0 / 0.3));
    }

    .skeleton {
        position: relative;
        overflow: hidden;
        background-color: var(--color-cream);
    }
    .skeleton::after {
        content: '';
        position: absolute;
        inset: 0;
        transform: translateX(-100%);
        background: linear-gradient(90deg, transparent, rgb(255 255 255 / 0.55), transparent);
        animation: shimmer 1.6s infinite;
    }

    .focus-premium {
        transition: box-shadow 0.25s var(--ease-luxury), border-color 0.25s var(--ease-luxury);
    }
    .focus-premium:focus {
        outline: none;
        border-color: var(--color-champagne);
        box-shadow: 0 0 0 4px rgb(201 166 97 / 0.18);
    }
}

@layer components {
    /* ---------------------------------------------------------------
     * Buttons — one system, every variant shares timing/radius/press.
     * A ripple span is injected by app.js on click; the CSS just
     * animates whatever gets appended.
     * --------------------------------------------------------------- */
    .btn {
        position: relative;
        overflow: hidden;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        gap: 0.5rem;
        border-radius: 999px;
        padding: 0.8rem 1.9rem;
        font-size: 0.8rem;
        font-weight: 600;
        letter-spacing: 0.04em;
        text-transform: uppercase;
        white-space: nowrap;
        cursor: pointer;
        /* transform/box-shadow/color only — no background-position, which
           animates a gradient and forces a repaint every frame instead of
           just compositing. */
        transition: transform 0.3s var(--ease-luxury), box-shadow 0.3s var(--ease-luxury), color 0.2s ease, border-color 0.2s ease;
        isolation: isolate;
    }
    .btn:hover:not(:disabled) { will-change: transform, box-shadow; }
    /* Press feedback is instant, not eased over 0.3s like the hover lift —
       a button should feel like it's actually being pressed. */
    .btn:active:not(:disabled) { transform: scale(0.97); transition-duration: 0.1s; }
    .btn:disabled { cursor: not-allowed; opacity: 0.45; transform: none !important; }

    .btn-primary {
        background: linear-gradient(120deg, #16130f 0%, #262220 55%, #16130f 100%);
        color: var(--color-ivory);
        box-shadow: var(--shadow-card);
    }
    .btn-primary:hover:not(:disabled) {
        box-shadow: var(--shadow-luxury);
        transform: translateY(-2px);
    }

    .btn-accent {
        background: linear-gradient(120deg, #c9a661 0%, #e4c98e 50%, #c9a661 100%);
        color: var(--color-ink);
        box-shadow: 0 8px 24px -10px rgb(201 166 97 / 0.45);
    }
    .btn-accent:hover:not(:disabled) {
        box-shadow: var(--shadow-glow-strong);
        transform: translateY(-2px);
    }

    .btn-outline {
        background: transparent;
        color: var(--color-ink);
        border: 1.5px solid rgb(22 19 15 / 0.18);
    }
    .btn-outline:hover:not(:disabled) {
        border-color: var(--color-ink);
        background: rgb(22 19 15 / 0.04);
        transform: translateY(-2px);
    }

    .btn-outline-light {
        background: rgb(255 255 255 / 0.08);
        color: var(--color-ivory);
        border: 1.5px solid rgb(255 255 255 / 0.35);
        backdrop-filter: blur(8px);
    }
    .btn-outline-light:hover:not(:disabled) {
        border-color: var(--color-champagne-light);
        background: rgb(255 255 255 / 0.14);
        transform: translateY(-2px);
    }

    .btn-ghost {
        background: transparent;
        color: var(--color-ink);
        padding-inline: 1rem;
        box-shadow: none;
    }
    .btn-ghost:hover:not(:disabled) { color: var(--color-champagne); }

    .btn-sm { padding: 0.55rem 1.3rem; font-size: 0.72rem; }
    .btn-lg { padding: 1rem 2.5rem; font-size: 0.85rem; }
    .btn-block { width: 100%; }

    .btn-ripple {
        position: absolute;
        border-radius: 50%;
        transform: scale(0);
        background: rgb(255 255 255 / 0.55);
        pointer-events: none;
        animation: ripple 0.7s ease-out forwards;
    }
    .btn-primary .btn-ripple,
    .btn-accent .btn-ripple { background: rgb(255 255 255 / 0.35); }
    .btn-outline .btn-ripple,
    .btn-ghost .btn-ripple { background: rgb(22 19 15 / 0.12); }

    /* ---------------------------------------------------------------
     * Cards
     * --------------------------------------------------------------- */
    .card-premium {
        background: #fff;
        border-radius: 1.25rem;
        border: 1px solid rgb(22 19 15 / 0.05);
        box-shadow: var(--shadow-soft);
        transition: transform 0.3s var(--ease-luxury), box-shadow 0.3s var(--ease-luxury), border-color 0.3s var(--ease-luxury);
    }
    .card-premium:hover {
        transform: translateY(-4px);
        box-shadow: var(--shadow-card);
        border-color: rgb(201 166 97 / 0.25);
        will-change: transform, box-shadow;
    }

    /* ---------------------------------------------------------------
     * Icon system — every icon-only control (<x-icon-button>) shares this:
     * a guaranteed 44x44 tap target, lift+scale+glow on hover, an instant
     * press-back, and a visible keyboard focus ring. `.icon-chip` layers a
     * circular, translucent "raised button" container on top for social
     * and standalone action icons; plain header/footer icons stay ghost
     * (no container, just the icon itself getting the same motion).
     * --------------------------------------------------------------- */
    .icon-btn {
        position: relative;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-height: 44px;
        min-width: 44px;
        border-radius: 9999px;
        color: inherit;
        cursor: pointer;
        transition: transform 220ms cubic-bezier(.2,.8,.2,1),
                    box-shadow 220ms cubic-bezier(.2,.8,.2,1),
                    color 220ms cubic-bezier(.2,.8,.2,1),
                    background-color 220ms cubic-bezier(.2,.8,.2,1),
                    border-color 220ms cubic-bezier(.2,.8,.2,1);
    }
    .icon-btn svg {
        filter: drop-shadow(0 2px 4px rgb(0 0 0 / 0.18));
        transition: transform 220ms cubic-bezier(.2,.8,.2,1), filter 220ms cubic-bezier(.2,.8,.2,1);
    }
    /* Icons sitting on the cream/ivory surfaces (header, footer contact
       list) want a lighter, barely-there shadow instead of the darker one
       tuned for the maroon footer band — opt in per usage site. */
    .icon-btn--on-light svg { filter: drop-shadow(0 1px 2px rgb(22 19 15 / 0.12)); }

    .icon-btn:hover { transform: translateY(-2px) scale(1.08); color: var(--color-champagne-light); }
    .icon-btn:hover svg { filter: drop-shadow(0 4px 10px rgb(201 166 97 / 0.35)); }
    .icon-btn:active { transform: scale(0.96); transition-duration: 100ms; }
    .icon-btn:focus-visible {
        outline: 2px solid var(--color-champagne);
        outline-offset: 3px;
    }

    .icon-chip {
        background: rgb(255 255 255 / 0.06);
        border: 1px solid rgb(255 255 255 / 0.16);
        box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.12), 0 2px 8px -2px rgb(22 19 15 / 0.35);
    }
    .icon-chip:hover {
        border-color: rgb(201 166 97 / 0.5);
        box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.18), 0 10px 24px -8px rgb(201 166 97 / 0.5);
    }
    /* Same chip treatment, tuned for a cream/white background instead of
       the dark footer band. */
    .icon-chip--on-light {
        background: rgb(22 19 15 / 0.04);
        border-color: rgb(22 19 15 / 0.08);
        box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.7), 0 2px 8px -2px rgb(22 19 15 / 0.12);
    }
    .icon-chip--on-light:hover {
        border-color: rgb(201 166 97 / 0.4);
        box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.8), 0 8px 20px -8px rgb(201 166 97 / 0.35);
    }

    /* Brand-color chips — each brand's own color at rest (not just on
       hover), so they read at a glance instead of looking like plain gold
       icons until touched. Hover just deepens the border glow (the lift +
       scale already come from .icon-btn). Instagram's actual gradient mark
       is set on the <x-icon gradient> instance itself (icon.blade.php),
       the CSS `fill` here is a fallback for any usage that forgets it. */
    .icon-chip--whatsapp { color: #25D366; }
    .icon-chip--whatsapp:hover { border-color: rgb(37 211 102 / 0.5); }
    .icon-chip--facebook { color: #1877F2; }
    .icon-chip--facebook:hover { border-color: rgb(24 119 242 / 0.5); }
    .icon-chip--instagram svg { fill: url(#icon-instagram-gradient); }
    .icon-chip--instagram:hover { border-color: rgb(238 42 123 / 0.5); }

    /* Wishlist heart — outline at rest, fills solid and heartbeats once
       when app.js marks it .is-active after a successful toggle. */
    .icon-heart svg { transition: transform 220ms cubic-bezier(.2,.8,.2,1), fill 180ms ease; }
    .icon-heart.is-active svg { fill: currentColor; }
    .icon-heart.heartbeat svg { animation: heartbeat 600ms cubic-bezier(.2,.8,.2,1); }

    /* Cart/wishlist count badge — pops whenever app.js updates the number. */
    .icon-badge {
        position: absolute;
        right: -0.35rem;
        top: -0.35rem;
        display: flex;
        height: 1.15rem;
        min-width: 1.15rem;
        align-items: center;
        justify-content: center;
        border-radius: 9999px;
        background: var(--color-maroon);
        padding-inline: 0.25rem;
        font-size: 0.65rem;
        font-weight: 600;
        color: var(--color-ivory);
        box-shadow: 0 2px 6px -1px rgb(110 20 35 / 0.5);
    }
    .icon-badge.badge-pop { animation: badge-pop 420ms cubic-bezier(.2,.8,.2,1); }

    /* Search icon — a small playful tilt+zoom on hover, independent of the
       Alpine-driven overlay it opens (which already animates in via its
       own x-transition). */
    .icon-search:hover svg { transform: rotate(-10deg) scale(1.12); }

    /* Track Order truck — a gentle horizontal nudge, not a full bounce. */
    .icon-truck:hover svg { transform: translateX(3px); }

    /* Hamburger — a real 3-bar-to-X morph instead of swapping glyphs, bound
       to the same Alpine `mobileOpen` state that already drives the drawer. */
    .hamburger {
        display: inline-flex;
        flex-direction: column;
        justify-content: center;
        gap: 5px;
        width: 20px;
    }
    .hamburger span {
        display: block;
        height: 2px;
        width: 100%;
        border-radius: 999px;
        background: currentColor;
        transform-origin: center;
        transition: transform 250ms cubic-bezier(.2,.8,.2,1), opacity 200ms ease;
    }
    .hamburger.is-open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
    .hamburger.is-open span:nth-child(2) { opacity: 0; }
    .hamburger.is-open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

    /* "We Accept" payment badges — uniform height, shown in full brand
       color (not grayscale) so they're recognizable at a glance; hover
       just lifts them slightly, matching every other icon control. */
    .payment-badge {
        display: inline-flex;
        align-items: center;
        gap: 0.35rem;
        height: 1.75rem;
        border-radius: 0.4rem;
        padding-inline: 0.1rem;
        transition: transform 220ms cubic-bezier(.2,.8,.2,1), box-shadow 220ms cubic-bezier(.2,.8,.2,1);
    }
    .payment-badge:hover {
        transform: translateY(-1px);
        box-shadow: 0 4px 10px -4px rgb(0 0 0 / 0.35);
    }
    .payment-badge--cod {
        border-radius: 999px;
        background: rgb(255 255 255 / 0.92);
        color: var(--color-ink);
        padding: 0 0.55rem;
    }

    /* Footer column headings — small gold underline accent. */
    .heading-accent {
        position: relative;
        padding-bottom: 0.6rem;
    }
    .heading-accent::after {
        content: '';
        position: absolute;
        left: 0;
        bottom: 0;
        height: 2px;
        width: 1.5rem;
        border-radius: 999px;
        background: linear-gradient(90deg, var(--color-champagne), var(--color-champagne-light));
    }
}

body {
    background-color: var(--color-ivory);
    color: var(--color-ink);
}

/* Keyboard-navigation focus ring, site-wide — mouse/touch users never see
   it (that's the point of :focus-visible over :focus), keyboard users
   always get a clearly visible gold ring. `.icon-btn` above layers its own
   copy with different offset/radius on top of this. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
    outline: 2px solid var(--color-champagne);
    outline-offset: 2px;
    border-radius: 4px;
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
        scroll-behavior: auto !important;
    }
}

/* ---------------------------------------------------------------
 * Print — global rather than inlined per-page, so any printable page
 * gets the same guarantee. `.no-print` is applied directly to the
 * persistent site chrome (header, utility bar, mobile menu, footer)
 * rather than relying on `header`/`footer` tag selectors alone: the
 * "Free shipping / Track Order" utility bar is a plain <div> that sits
 * *outside* the <header> element, so a tag-only rule silently missed it.
 * --------------------------------------------------------------- */
@media print {
    @page {
        size: A4;
        margin: 10mm;
    }

    .no-print {
        display: none !important;
    }

    body {
        background: #fff !important;
    }

    * {
        -webkit-print-color-adjust: exact !important;
        print-color-adjust: exact !important;
    }

    .avoid-break {
        break-inside: avoid;
        page-break-inside: avoid;
    }

    /* Receipt-specific compacting, scoped under .receipt-page so printing
       any other page never inherits these margins. Reclaims the vertical
       space (outer section padding, card padding, per-section padding)
       that was pushing a short receipt onto a second page. */
    .receipt-page {
        max-width: 100% !important;
        padding: 0 !important;
        margin: 0 !important;
    }
    .receipt-page .receipt-card {
        border: none !important;
        box-shadow: none !important;
        border-radius: 0 !important;
    }
    .receipt-page .receipt-card-body {
        padding: 4mm !important;
    }
    .receipt-page .receipt-watermark {
        opacity: 0.05 !important;
    }
    .receipt-page .receipt-section {
        padding-top: 3mm !important;
        padding-bottom: 3mm !important;
    }
    .receipt-page .receipt-card-inner {
        padding: 2.5mm !important;
    }
    .receipt-page table th,
    .receipt-page table td {
        padding-top: 3px !important;
        padding-bottom: 3px !important;
    }
    .receipt-page .receipt-item-thumb {
        height: 30px !important;
        width: 26px !important;
    }
    .receipt-page .receipt-total-highlight {
        padding: 3mm 4mm !important;
    }
}
