/* chapo-motion.css — the app-wide motion system (page transitions, modal
   enter/exit, reduced-motion gate).

   This sheet is GLOBAL by design and must stay that way:
   - the modal enter/exit rules are shared by scoped components (V4Modal) and
     un-scoped Bootstrap-markup dialogs (ConfirmDialog, About) alike;
   - the page-transition rules added in the next phase use ::view-transition-*
     pseudo-elements, which are document-level and silently do nothing from a
     scoped .razor.css.

   Loaded by BOTH hosts (Web App.razor + MAUI index.html) — any new stylesheet
   or script must be added to both.

   Rules here animate transform + opacity only (compositor-friendly on mobile
   WebViews). Durations/easings come from chapo-tokens.css; exit durations must
   stay in step with ChapoMotion.ModalExitMs, the C# delay that keeps a closing
   modal mounted until its exit animation has played. */

/* ============================================================ */
/* Page navigation — ONE motion, shared by every route change     */
/* ============================================================ */
/* Every navigation animates along a single horizontal axis and differs only in
   SIGN. Mixing a cross-fade for tab switches with a slide for hierarchy is what
   made this read as "sometimes fade, sometimes slide"; one motion whose
   direction carries the meaning is calmer and more legible.

   page-transitions.js sets --chapo-nav-x to +1 or -1, already flipped for RTL,
   so "forwards" arrives from the inline-end in Hebrew without a second set of
   keyframes. Tab switches take their sign from the tab rail's own order, so the
   content moves the same way the user's eye just did.

   The offset is deliberately small. A full-width push is an iOS *stack* idiom;
   in a tab-bar app it is heavy, and any imprecision in the direction guess makes
   it look wrong. A short travel + fade reads as polished and never as wrong. */
.chapo-page {
    /* fill-mode is `backwards`, NOT `both`, and that is load-bearing: these
       keyframes animate `transform`, and a forwards/both fill keeps the effect
       applied FOREVER after the animation ends. An element with a filling
       transform animation becomes a containing block for its position:fixed
       descendants, which re-sized every page-rendered modal to the page instead
       of the viewport. `backwards` leaves the element completely clean. */
    animation: chapo-nav-in-fade var(--chapo-duration-nav) var(--chapo-ease-fade) backwards,
               chapo-nav-in-move var(--chapo-duration-nav) var(--chapo-ease-nav) backwards;
}

/* When view transitions are driving the swap, the pseudo-elements below own the
   choreography and this must stand down — otherwise both run and compound.
   .chapo-vt is a STABLE class (set once, not toggled per navigation) precisely
   so that ending a transition cannot re-trigger this animation: gating it on a
   per-transition attribute made the page snap back to opacity 0 and fade in a
   second time the instant the transition finished, which is what looked like a
   jump at the end of every navigation. */
html.chapo-vt .chapo-page { animation: none; }


/* ============================================================ */
/* Page transitions (View Transitions API)                       */
/* ============================================================ */
/* These MUST live in a global sheet: ::view-transition-* pseudo-elements are
   document-level and silently do nothing from a scoped .razor.css.

   Naming an element lifts it into its own snapshot layer for the duration of
   the transition. The persistent chrome each gets a layer purely so it is
   NOT swept into the page's animation — professional page transitions move the
   content and leave the furniture alone.

   These are scoped to html[data-nav-vt="1"] — i.e. applied ONLY while a
   transition is actually running — and that is essential, not tidiness.
   view-transition-name creates a STACKING CONTEXT. Left on permanently, it
   traps every overlay a page renders inside .chapo-page's stacking level
   (z-index:auto), so the product-detail sheet (z-index 1100) and the fullscreen
   image lightbox (z-index 9999) both painted UNDER the context bar and tab rail.
   page-transitions.js sets the attribute before calling startViewTransition and
   clears it on finish, so the names exist for both the old and new capture and
   at no other time. */
/* NOTE: the page content is deliberately NOT named, and that is a performance
   decision. A named element is captured at its FULL border-box size — the
   catalog page measures ~27 000px tall, so naming it made every catalog
   navigation snapshot a 27 000 x 508 texture (tens of MB) twice over. Chrome
   does cope with it, but it is a lot of work to do twice in the 16ms before a
   transition starts, and catalog was exactly the page that felt least smooth.

   Leaving the page unnamed puts it in the `root` snapshot instead, which is
   captured at VIEWPORT size no matter how long the document is. Bonus: root
   includes the shell background, so both layers are opaque and cannot show
   through each other. */
html[data-nav-vt="1"] .chapo-ctxbar-v4-wrap { view-transition-name: chapo-ctx; }
html[data-nav-vt="1"] .chapo-tabs-v4        { view-transition-name: chapo-tabs; }
html[data-nav-vt="1"] .chapo-cartfab        { view-transition-name: chapo-fab; }

/* Hold the chrome perfectly still: drop the old snapshot and leave the new one
   unanimated. Without this the browser's default cross-fade makes the bars
   flicker on every navigation. */
::view-transition-old(chapo-ctx),
::view-transition-old(chapo-tabs),
::view-transition-old(chapo-fab) { display: none; }
::view-transition-new(chapo-ctx),
::view-transition-new(chapo-tabs),
::view-transition-new(chapo-fab) { animation: none; }

/* Root is the page carrier (see the note above): shell background + page content
   + the inner-page back header, captured at viewport size. The group animation
   is off because root never changes size — only its contents cross-fade, below. */
::view-transition-group(root) { animation: none; }

/* ---- The one page motion ----
   Opacity and movement are split into two animations on purpose.

   The two fades share one window and one easing, with exactly complementary
   keyframes (1→0 against 0→1). That makes their opacities sum to exactly 1 at
   every instant — measured, not assumed. It matters because .chapo-page has no
   background of its own: any moment where both layers are partly transparent
   shows the bare shell through the gaps, and the whole screen dips in brightness
   and back, which is itself a blink. A fade-through (old clears, THEN new
   arrives) has exactly that hole in the middle.

   The easing is steep through the middle rather than linear. Because both layers
   share it, the sum still holds at 1; but the crossover spends less time near
   50/50, where both pages' text is legible at once and reads as ghosting.

   The movement keeps the eased curve, so the motion still feels shaped rather
   than mechanical. The outgoing layer drifts a little in the same direction the
   incoming one is travelling — a parallax, so the two read as one movement
   instead of two things passing each other. */
::view-transition-old(root) {
    animation: chapo-nav-out-fade var(--chapo-duration-nav) var(--chapo-ease-fade) both,
               chapo-nav-out-move var(--chapo-duration-nav) var(--chapo-ease-nav) both;
}
::view-transition-new(root) {
    animation: chapo-nav-in-fade var(--chapo-duration-nav) var(--chapo-ease-fade) both,
               chapo-nav-in-move var(--chapo-duration-nav) var(--chapo-ease-nav) both;
}

/* plus-lighter is what makes two cross-fading layers whose opacities sum to 1
   composite to a SOLID result — without it the layers blend normally and up to
   25% of whatever is BEHIND the snapshot stack (the document canvas; on the
   native apps that canvas sits over a brand-green native window) shows through
   at the midpoint. The browser normally applies it itself, but it does so via
   a UA *animation* (-ua-mix-blend-mode-plus-lighter) — which the `animation`
   overrides above silently wipe. Re-state it as a plain style so it survives. */
::view-transition-old(root),
::view-transition-new(root) { mix-blend-mode: plus-lighter; }

/* The arriving page paints above the leaving one throughout. */
::view-transition-new(root) { z-index: 1; }

@keyframes chapo-nav-in-fade   { from { opacity: 0; } }
@keyframes chapo-nav-out-fade  { to   { opacity: 0; } }
@keyframes chapo-nav-in-move {
    from { transform: translateX(calc(var(--chapo-nav-shift) * var(--chapo-nav-x, 1))); }
}
@keyframes chapo-nav-out-move {
    to   { transform: translateX(calc(var(--chapo-nav-shift) * var(--chapo-nav-x, 1) * -0.35)); }
}

/* ============================================================ */
/* V4Modal — backdrop + bottom sheet (mobile) / dialog (desktop) */
/* ============================================================ */
/* The dim layer is a real element inside the backdrop (.chapo-modal-dim,
   structure in V4Modal.razor.css) so it can fade independently of the sheet —
   and be driven per-frame by the swipe drag. While closing, V4Modal keeps the
   DOM mounted with .is-closing until the exit animation has played. */
.chapo-modal-backdrop .chapo-modal-dim {
    animation: chapo-backdrop-in 160ms var(--chapo-ease-decel) both;
}
.chapo-modal-backdrop.is-closing .chapo-modal-dim {
    animation: chapo-backdrop-out var(--chapo-duration-base) var(--chapo-ease-accel) both;
}
@keyframes chapo-backdrop-in  { from { opacity: 0; } }
@keyframes chapo-backdrop-out { to   { opacity: 0; } }

/* Mobile (<600px): a real bottom-sheet rise; exit slides fully off-screen.
   The sheet rises from BELOW ITS OWN EDGE, not from 48px away. The old 48px nudge
   was the reason this read as "too fast": at that distance the eye sees a fade with
   a twitch, and no duration fixes it — the missing information is the travel, which
   is what tells you where the sheet came from and that it can go back down there.
   340ms with a decelerating curve is the pace to notice without waiting: it lands
   with most of the distance covered early, and the exit stays quicker than the
   entrance (200ms), which is what keeps a dismissed sheet from feeling sticky. */
.chapo-modal {
    animation: chapo-sheet-in var(--chapo-duration-sheet) var(--chapo-ease-sheet) both;
}
.chapo-modal-backdrop.is-closing .chapo-modal {
    animation: chapo-sheet-out var(--chapo-duration-base) var(--chapo-ease-accel) both;
}
/* Opacity finishes early (60%) so the sheet is solid for most of its travel — a
   panel that is still translucent as it arrives looks like a glitch, not a rise. */
@keyframes chapo-sheet-in  {
    from { opacity: 0; transform: translateY(100%); }
    60%  { opacity: 1; }
}
@keyframes chapo-sheet-out { to   { transform: translateY(102%); } }

/* ---- Sheet swipe-down (chapo-gestures.js) ----
   While a finger drags the sheet, the dim follows it per-frame through the
   .chapo-modal-dim element's inline opacity (a single-element style change on
   its own compositor layer — a custom property on the backdrop was measured to
   re-resolve the whole sheet subtree every frame and stutter on device). The
   gesture never overrides the animation property: it fast-forwards a running
   entry animation via the Web Animations API instead, so animation-name never
   changes and nothing can replay — and after a swipe dismissal the .is-closing
   exit keyframes are no-ops by construction (their implicit from-values are the
   inline end-state styles the gesture left behind). No extra rules needed. */

/* Desktop (≥600px, matches the V4Modal layout breakpoint): centered dialog —
   scale + small rise in, quick scale-fade out. */
@media (min-width: 600px) and (hover: hover) {
    .chapo-modal {
        animation: chapo-dialog-in 260ms var(--chapo-ease-sheet) both;
    }
    .chapo-modal-backdrop.is-closing .chapo-modal {
        animation: chapo-dialog-out 160ms var(--chapo-ease-accel) both;
    }
}
@keyframes chapo-dialog-in  { from { opacity: 0; transform: translateY(16px) scale(0.96); } }
@keyframes chapo-dialog-out { to   { opacity: 0; transform: scale(0.98); } }

/* ============================================================ */
/* Bootstrap-markup dialogs (ConfirmDialog, About, legacy)       */
/* ============================================================ */
/* Drop-in utility for the `.modal.fade.show.d-block` pattern: add
   .chapo-anim-modal next to .modal, and .is-closing during the two-phase
   close. The overlay fade also fades the dialog (they're nested) — fine for a
   centered dialog; the dialog adds its own scale on top. */
.chapo-anim-modal {
    animation: chapo-backdrop-in 160ms var(--chapo-ease-decel) both;
}
.chapo-anim-modal .modal-dialog {
    animation: chapo-dialog-in 220ms var(--chapo-ease-decel) both;
}
.chapo-anim-modal.is-closing {
    animation: chapo-backdrop-out var(--chapo-duration-base) var(--chapo-ease-accel) both;
}
.chapo-anim-modal.is-closing .modal-dialog {
    animation: chapo-dialog-out 160ms var(--chapo-ease-accel) both;
}

/* ============================================================ */
/* Skeleton → content                                            */
/* ============================================================ */
/* Softens the moment real data replaces a placeholder. A pure opacity fade on
   purpose: the content lands in the space the skeleton already reserved, so any
   translation would read as the layout shifting rather than resolving.

   `backwards`, NOT `both`: this class sits on page-content wrappers that contain
   modals. An animation that targets opacity keeps its element a stacking context
   for as long as it applies, and `both` means it applies forever after it ends —
   so a fixed, z-index:1100 sheet rendered inside the wrapper was confined to that
   context and painted UNDER the bottom tab bar and cart bar, which live outside
   it. `backwards` still hides the content before the animation starts (the whole
   point) and stops applying the moment it finishes. */
.chapo-fade-in {
    animation: chapo-fade-in var(--chapo-duration-fast) var(--chapo-ease-decel) backwards;
}
@keyframes chapo-fade-in {
    from { opacity: 0; }
}

/* ============================================================ */
/* Reduced motion — global kill-switch                           */
/* ============================================================ */
/* Movement is removed entirely; opacity fades are kept but shortened so state
   changes stay perceivable (a modal appearing with zero feedback is worse for
   vestibular users than a brief fade). */
@media (prefers-reduced-motion: reduce) {
    /* page-transitions.js already declines to start a view transition under
       reduced motion; this also neutralises any that is already in flight. */
    ::view-transition-group(*),
    ::view-transition-old(*),
    ::view-transition-new(*) { animation: none !important; }
    .chapo-page { animation: none; }
    .chapo-fade-in { animation-duration: 80ms; }
    .chapo-modal,
    .chapo-anim-modal .modal-dialog {
        animation-name: chapo-backdrop-in !important;
        animation-duration: 80ms !important;
    }
    .chapo-modal-backdrop.is-closing .chapo-modal,
    .chapo-anim-modal.is-closing .modal-dialog {
        animation-name: chapo-backdrop-out !important;
        animation-duration: 80ms !important;
    }
    .chapo-modal-backdrop .chapo-modal-dim,
    .chapo-anim-modal {
        animation-duration: 80ms !important;
    }
}

/* ============================================================================
   Skeletons + spinners — one definition each, app-wide.

   These used to be scattered: `chapo-shimmer` was declared inside ONE page's
   scoped stylesheet (HomeV4) but consumed by four, working only because Blazor
   bundles scoped CSS into a single file and does not scope @keyframes — delete
   that page's skeleton and three unrelated pages silently stop shimmering. The
   spin keyframe existed four times over (chapo-spin twice, chapo-spin-g,
   chapo-btn-spin-kf), and four different shimmer speeds meant two skeletons on
   screen at once visibly beat against each other.
   ========================================================================== */

/* One sweep, matching the product card's: 1.5s ease-in-out, inline-start to
   inline-end. Consumers supply their own gradient + `background-size: 200%`. */
@keyframes chapo-skeleton-shimmer {
    0%   { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

@keyframes chapo-spin { to { transform: rotate(360deg); } }

/* The skeleton surface itself, so a placeholder is one class rather than five
   lines repeated per page. */
.chapo-skel {
    background: linear-gradient(90deg,
        var(--chapo-surface-2) 25%,
        color-mix(in srgb, var(--chapo-surface-2) 55%, white) 50%,
        var(--chapo-surface-2) 75%);
    background-size: 200% 100%;
    animation: chapo-skeleton-shimmer 1.5s ease-in-out infinite;
    border-radius: var(--chapo-radius-md);
}

/* Infinite animation is exactly what reduced-motion is for: a shimmer that
   never stops is the most persistent motion in the app. Keep the placeholder
   visible (it still communicates "loading"), just still. */
@media (prefers-reduced-motion: reduce) {
    .chapo-skel,
    .chapo-imgskel::after,
    [class*="skel"] { animation: none !important; }
    .chapo-spin,
    .chapo-btn-spin { animation-duration: 1.6s !important; }
}

/* Safety net: page-level animations that live in scoped sheets without their own
   reduced-motion block (skeleton slabs, the store-row flash, spinners). Named
   broadly on purpose — a scoped sheet added later is covered without having to
   remember this file. */
@media (prefers-reduced-motion: reduce) {
    [class*="-skel"],
    [class*="skeleton"] { animation: none !important; }
    .chapo-storerow,
    [class*="-flash"] { animation: none !important; }
    body.chapo-v4 *:hover { transform: none !important; }
}


/* ============================================================ */
/* Press feedback                                               */
/* ============================================================ */
/* Every tappable surface answers the finger. Without this a tap on a card is
   silent until the next screen paints — which on a slow network is long enough for
   a shopper to tap again, and the app read as unresponsive rather than busy. The
   move is deliberately small (a 1.5% squeeze) and fast: it should register as the
   surface yielding, never as an animation being played at you.

   ABOUT THE SELECTORS. Every one of these elements belongs to a component with
   Blazor CSS isolation, so its own rules carry an extra [b-xxxxx] attribute and beat
   a plain global class — measured: the compare row's scoped `transition` won and the
   squeeze never eased. Doubling the final class and anchoring at `body` raises these
   above that without touching a dozen component stylesheets, which is the trade this
   file already exists to make.

   :active is the right hook in an iOS WebView (a finger has no :hover), and Safari's
   default grey tap-wash is turned off because a rounded card flashing a grey
   rectangle is exactly what this replaces. */
body .chapo-storerow.chapo-storerow.is-openable,
body .chapo-storerow-buy.chapo-storerow-buy,
body .chapo-cartsheet-row.chapo-cartsheet-row,
body .chapo-ordercard.chapo-ordercard,
body .chapo-orderd-apply.chapo-orderd-apply,
body .chapo-rc-card.chapo-rc-card,
body .chapo-hometrk-card.chapo-hometrk-card,
body .catalog-dept-tile.catalog-dept-tile,
body .catalog-chip.catalog-chip,
body .pcard.pcard,
body .chapo-compare-showmore.chapo-compare-showmore,
body .chapo-scale-dot.chapo-scale-dot,
body .chapo-btn.chapo-btn {
    -webkit-tap-highlight-color: transparent;
    transition: transform var(--chapo-duration-fast) var(--chapo-ease-decel),
                filter var(--chapo-duration-fast) var(--chapo-ease-decel);
}
body .chapo-storerow.chapo-storerow.is-openable:active,
body .chapo-storerow-buy.chapo-storerow-buy:active,
body .chapo-cartsheet-row.chapo-cartsheet-row:active,
body .chapo-ordercard.chapo-ordercard:active,
body .chapo-orderd-apply.chapo-orderd-apply:active,
body .chapo-rc-card.chapo-rc-card:active,
body .chapo-hometrk-card.chapo-hometrk-card:active,
body .catalog-dept-tile.catalog-dept-tile:active,
body .catalog-chip.catalog-chip:active,
body .pcard.pcard:active,
body .chapo-compare-showmore.chapo-compare-showmore:active,
body .chapo-scale-dot.chapo-scale-dot:active,
body .chapo-btn.chapo-btn:active {
    transform: scale(0.985);
    /* A hair darker as well: on the dark theme a 1.5% squeeze alone is easy to miss
       on a large card, and brightness is free to composite. */
    filter: brightness(0.94);
}
/* A small round control squeezing by 1.5% is invisible — give those more. */
body .chapo-scale-dot.chapo-scale-dot:active { transform: scale(0.93); }

/* A row that is sliding horizontally (swipe actions, chapo-gestures.js) must
   not also squeeze — the :active scale reads as the card flinching mid-slide. */
body .chapo-ordswipe.is-swiping .chapo-ordercard:active {
    transform: none !important;
    filter: none !important;
}

@media (prefers-reduced-motion: reduce) {
    body .chapo-storerow.chapo-storerow.is-openable:active,
    body .chapo-storerow-buy.chapo-storerow-buy:active,
    body .chapo-cartsheet-row.chapo-cartsheet-row:active,
    body .chapo-ordercard.chapo-ordercard:active,
    body .chapo-rc-card.chapo-rc-card:active,
    body .catalog-dept-tile.catalog-dept-tile:active,
    body .catalog-chip.catalog-chip:active,
    body .pcard.pcard:active,
    body .chapo-compare-showmore.chapo-compare-showmore:active,
    body .chapo-scale-dot.chapo-scale-dot:active,
    body .chapo-btn.chapo-btn:active {
        transform: none;                 /* the brightness change still answers the tap */
    }
}
