/* Single owner of all View Transition rules and navigation-motion keyframes.
   No other stylesheet may define ::view-transition-* rules or nav/stream
   motion keyframes — extend here. */

/* Default view transition: crossfade */
::view-transition-old(root),
::view-transition-new(root) {
    animation-duration: 150ms;
    animation-timing-function: ease-out;
}

/* Forward navigation: slide in from right */
html[data-turbo-visit-direction="forward"]::view-transition-old(root) {
    animation-name: slide-out-left;
}
html[data-turbo-visit-direction="forward"]::view-transition-new(root) {
    animation-name: slide-in-right;
}

/* Back navigation: slide in from left */
html[data-turbo-visit-direction="back"]::view-transition-old(root) {
    animation-name: slide-out-right;
}
html[data-turbo-visit-direction="back"]::view-transition-new(root) {
    animation-name: slide-in-left;
}

/* Slide animations */
@keyframes slide-out-left {
    from { opacity: 1; transform: translateX(0); }
    to { opacity: 0; transform: translateX(-10px); }
}
@keyframes slide-in-right {
    from { opacity: 0; transform: translateX(10px); }
    to { opacity: 1; transform: translateX(0); }
}
@keyframes slide-out-right {
    from { opacity: 1; transform: translateX(0); }
    to { opacity: 0; transform: translateX(10px); }
}
@keyframes slide-in-left {
    from { opacity: 0; transform: translateX(-10px); }
    to { opacity: 1; transform: translateX(0); }
}

/* Progress bar styling - thinner and more subtle */
.turbo-progress-bar {
    height: 2px;
    background: linear-gradient(to right, var(--bs-primary, #0d6efd), var(--bs-info, #0dcaf0));
}

/* Navbar loading indicator */
.nav-link.turbo-nav-loading {
    opacity: 0.6;
    transition: opacity 100ms ease-out;
}

/* Exclude maps from View Transitions API - prevents tile positioning issues.
   IDs are page-unique so one global rule covers job and quote detail pages. */
#jobMap,
#jobMap *,
#quoteMap,
#quoteMap * {
    view-transition-name: none !important;
}

/* Fixed chrome (bottom nav, desktop navbar, floating bell) gets its own
   snapshot so it stays put while the root transition slides the content
   underneath it. Only one of #mobile-navbar/#main-navbar is ever rendered
   at a given breakpoint (d-lg-none / d-none d-lg-flex), so naming both is
   safe. The bell is scoped to its floating state only (.is-static drops it
   into document flow, where it should scroll with the page like desktop). */
#mobile-navbar {
    view-transition-name: mobile-nav;
}
#main-navbar {
    view-transition-name: main-nav;
    /* Naming an element as a view-transition group also makes it establish a
       stacking context (idle, not only mid-transition). #main-navbar is
       Bootstrap position:relative with no z-index, so before it was named it
       was NOT a stacking context and its .dropdown-menu children (z-index:1050,
       themes/base.css) escaped to the root context and painted above the page.
       Once named, that 1050 is trapped inside the navbar's own z-auto context —
       and because the navbar precedes <main> in the DOM, later page content
       paints over it. Symptom: open account/overflow dropdowns show page content
       (search box, list rows) bleeding through wherever they overflow past the
       navbar strip. Lift the navbar's context above page content so its
       dropdowns win again. #mobile-navbar already owns z-index:1000
       (mobile-nav.css) and uses a full-screen overlay, not dropdowns. */
    z-index: 1030;
}
.notification-bell-mobile:not(.is-static) {
    view-transition-name: mobile-bell;
}
::view-transition-group(mobile-nav),
::view-transition-group(main-nav),
::view-transition-group(mobile-bell) {
    animation-duration: 0s;
}

/* Shared-element list->detail morph (quotes; see entity_morph_controller.js).
   Stamped on exactly one row at click time (list side) and unconditionally on
   the detail header via .vt-entity-hero — when there's no matching list-side
   name (e.g. arriving from a link), the header just does a plain browser
   default morph, no error. Duration/easing matches the mobile push above so
   the hero and the root transition read as one motion. */
.vt-entity-hero {
    view-transition-name: entity-hero;
}
::view-transition-group(entity-hero) {
    animation-duration: 280ms;
    animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
}

/* Suppresses the root View Transition entirely for a single Turbo visit.
   Single writer: card_swipe_controller.js sets this before its own swipe-
   commit overlay animation runs Turbo.visit (a simultaneous VT would double-
   animate underneath the overlay), and clears it once that visit lands. */
html[data-vt-skip]::view-transition-old(root),
html[data-vt-skip]::view-transition-new(root),
html[data-vt-skip]::view-transition-group(*) {
    animation: none !important;
}

/* Mobile: full-viewport iOS-style push/pop instead of the 10px desktop
   crossfade. Forward = incoming card slides over from the right, outgoing
   dims underneath; back = mirror, outgoing card slides away on top. */
@media (max-width: 991.98px) {
    html[data-turbo-visit-direction="forward"]::view-transition-old(root) {
        animation-name: mobile-push-out-forward;
        animation-duration: 280ms;
        animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
        z-index: 1;
    }
    html[data-turbo-visit-direction="forward"]::view-transition-new(root) {
        animation-name: mobile-push-in-forward;
        animation-duration: 280ms;
        animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
        z-index: 2;
    }
    html[data-turbo-visit-direction="back"]::view-transition-old(root) {
        animation-name: mobile-push-out-back;
        animation-duration: 280ms;
        animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
        z-index: 2;
    }
    html[data-turbo-visit-direction="back"]::view-transition-new(root) {
        animation-name: mobile-push-in-back;
        animation-duration: 280ms;
        animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
        z-index: 1;
    }

    @keyframes mobile-push-out-forward {
        from { transform: translateX(0); filter: brightness(1); }
        to { transform: translateX(-30%); filter: brightness(0.92); }
    }
    @keyframes mobile-push-in-forward {
        from { transform: translateX(100%); }
        to { transform: translateX(0); }
    }
    @keyframes mobile-push-out-back {
        from { transform: translateX(0); }
        to { transform: translateX(100%); }
    }
    @keyframes mobile-push-in-back {
        from { transform: translateX(-30%); filter: brightness(0.92); }
        to { transform: translateX(0); filter: brightness(1); }
    }
}

/* Bottom-nav tab switches: fast crossfade instead of a stack push, at any
   viewport. html[data-nav-kind="tab"] is stamped by turbo-navigation.js for
   the single visit the tap triggers only. Reset z-index so neither snapshot
   rides on top of the other, as the push rules above set one. */
html[data-nav-kind="tab"]::view-transition-old(root) {
    animation-name: vt-fade-out;
    animation-duration: 120ms;
    animation-timing-function: ease-out;
    z-index: auto;
}
html[data-nav-kind="tab"]::view-transition-new(root) {
    animation-name: vt-fade-in;
    animation-duration: 120ms;
    animation-timing-function: ease-out;
    z-index: auto;
}

@keyframes vt-fade-out {
    from { opacity: 1; }
    to { opacity: 0; }
}
@keyframes vt-fade-in {
    from { opacity: 0; }
    to { opacity: 1; }
}

/* Turbo Stream row FLIP (turbo-animations.js names affected rows temporarily
   as row-<id> before mutating the DOM inside a View Transition). The browser
   computes the FLIP movement itself from each row's before/after rect; this
   only tunes the crossfade for rows with no old or new counterpart (a pure
   remove or add), to match the epic's motion scale. More specific selectors
   above (root, mobile-nav, entity-hero, ...) still win over this wildcard. */
::view-transition-old(*),
::view-transition-new(*) {
    animation-duration: 200ms;
    animation-timing-function: ease-out;
}

/* Touch press-state: compress a list-card row while held. Inline transform
   from an active card_swipe drag wins over this rule (inline style beats a
   stylesheet selector), so this never fights the swipe gesture. */
@media (hover: none) and (pointer: coarse) {
    tbody[id] > tr:active {
        transform: scale(0.98);
        transition: transform 120ms ease-out;
    }
}

@media (prefers-reduced-motion: reduce) {
    ::view-transition-group(*),
    ::view-transition-old(*),
    ::view-transition-new(*) {
        animation: none !important;
    }
    tbody[id] > tr:active {
        transform: none;
    }
}
