/* ==========================================================================
   DIGISTREET Monitor — MOTION LAYER  (motion.css)
   Added 2026-10-08. Purely additive polish on top of app.css. Loaded right
   after app.css by layouts/app.php (staff app) and layouts/auth.php (sign-in).

   What it does
     1. Page entrance  — top-level blocks of .page fade up in a quick stagger;
                         grid tiles, hero pills, KPI numbers and progress bars
                         follow their block in.
     2. Hover / press  — softer elevation on cards, press-in on buttons, a
                         yellow rail on sidebar links, smooth table-row
                         highlights, badge/pill pops, screenshot zoom.
     3. Focus          — brand-yellow keyboard focus ring that eases in.
     4. Disclosures    — <details>, <dialog> and the staff modals fade/pop in.
     5. Reveal         — blocks that start below the fold fade up the first
                         time they scroll into view (classes set by
                         /assets/js/motion.js).
     6. Targeted fixes — dead hover-lift on the dashboard bento tiles.
     7. Sign-in pages  — card entrance + error nudge.

   House rules (why it is written this way)
     • Only opacity + transforms are animated — mostly via the individual
       `translate` / `scale` properties, which COMPOSE with an element's own
       `transform` instead of replacing it (hover lifts, centring tricks and
       rotations keep working, even mid-animation). No layout properties.
     • Entrance keyframes are FROM-ONLY with fill-mode `backwards`: when they
       end they hand every property back to the element's own styles, so
       nothing stays pinned (hover lifts work, faded `.done` cards keep their
       opacity, no leftover transform traps position:fixed overlays).
     • Entrance selectors are wrapped in :where() (zero specificity), so any
       view that defines its own `animation` always wins.
     • Load-only effects are gated on html.mo-enter: the inline snippet in
       layouts/app.php adds it before first paint and removes it ~1.8s after
       DOMContentLoaded, so overlays/panels that are display:none at load
       don't replay the entrance when they are opened later.
     • Everything that moves is screen-only and switched off under
       prefers-reduced-motion. Print output is untouched.

   Opt-out : add data-no-reveal to an element to keep it (and its subtree)
             out of reveal-on-scroll.
   Revert  : delete the "MOTION LAYER" block in layouts/app.php and the
             motion.css <link> in layouts/auth.php. Nothing else depends on
             this file or on motion.js.
   ========================================================================== */

:root {
  --mo-ease-out:     cubic-bezier(.22,.61,.36,1);   /* same curve as the client portal's cpFadeUp */
  --mo-ease-pop:     cubic-bezier(.34,1.45,.64,1);  /* tiny overshoot for pops */
  --mo-fast:         .16s;
  --mo-base:         .22s;
  --mo-enter:        .42s;
  --mo-shadow-hover: 0 10px 28px rgba(28,28,55,.10), 0 3px 8px rgba(28,28,55,.05);
}

/* From-only keyframes: they animate FROM this state TO the element's own. */
@keyframes mo-rise  { from { opacity: 0; translate: 0 10px; } }
@keyframes mo-up    { from { opacity: 0; translate: 0 6px; } }
@keyframes mo-fade  { from { opacity: 0; } }
@keyframes mo-pop   { from { opacity: 0; translate: 0 6px; scale: .97; } }
@keyframes mo-grow  { from { scale: 0 1; } }
@keyframes mo-ring  { from { outline-offset: 6px; outline-color: transparent; } }
@keyframes mo-nudge { 20% { translate: -4px 0; } 40% { translate: 4px 0; } 60% { translate: -2px 0; } 80% { translate: 2px 0; } }


/* ── 1. PAGE ENTRANCE ──────────────────────────────────────────────────── */

/* app.css rises the whole .page with `pageRise … both`. The filled
   `transform: translateY(0)` never goes away, which turns .page into the
   containing block AND stacking context of every position:fixed overlay
   inside it (Team Chat "new message", My Files, weekend thank-you modal):
   they get sized/centred to .page instead of the viewport and paint under
   the topbar and sidebar. Dropped for everyone (incl. reduced-motion); the
   staggered entrance below replaces it. */
.page { animation: none; }

@media screen and (prefers-reduced-motion: no-preference) {

  /* Top-level blocks: hero, page-head, cards, tables, forms, alerts …
     Grid containers sit out — their tiles animate one by one instead. */
  :where(html.mo-enter .page > :not(.stat-grid, .tk-list, .live-grid, .dl-grid, .bento, .strip)) {
    animation: mo-rise var(--mo-enter) var(--mo-ease-out) var(--mo-d, 0ms) backwards;
  }
  /* Stagger by position; <style>/<script> siblings don't count. */
  :where(.page > :nth-child(1 of :not(style, script, link, template)))   { --mo-d: 0ms; }
  :where(.page > :nth-child(2 of :not(style, script, link, template)))   { --mo-d: 45ms; }
  :where(.page > :nth-child(3 of :not(style, script, link, template)))   { --mo-d: 90ms; }
  :where(.page > :nth-child(4 of :not(style, script, link, template)))   { --mo-d: 135ms; }
  :where(.page > :nth-child(5 of :not(style, script, link, template)))   { --mo-d: 180ms; }
  :where(.page > :nth-child(6 of :not(style, script, link, template)))   { --mo-d: 220ms; }
  :where(.page > :nth-child(n+7 of :not(style, script, link, template))) { --mo-d: 250ms; }

  /* Tiles inside the shared grids. Uses `translate`, not `transform`, so the
     tiles' own hover lifts (.stat, .tk-card, .kpi …) are never blocked. */
  :where(html.mo-enter :is(.stat-grid, .tk-list, .live-grid, .dl-grid, .bento, .strip) > *) {
    animation: mo-up var(--mo-enter) var(--mo-ease-out) calc(var(--mo-d, 0ms) + var(--mo-g, 0ms) + 40ms) backwards;
  }
  :where(:is(.stat-grid, .tk-list, .live-grid, .dl-grid, .bento, .strip, .hb-pills) > :nth-child(1))    { --mo-g: 0ms; }
  :where(:is(.stat-grid, .tk-list, .live-grid, .dl-grid, .bento, .strip, .hb-pills) > :nth-child(2))    { --mo-g: 40ms; }
  :where(:is(.stat-grid, .tk-list, .live-grid, .dl-grid, .bento, .strip, .hb-pills) > :nth-child(3))    { --mo-g: 80ms; }
  :where(:is(.stat-grid, .tk-list, .live-grid, .dl-grid, .bento, .strip, .hb-pills) > :nth-child(4))    { --mo-g: 120ms; }
  :where(:is(.stat-grid, .tk-list, .live-grid, .dl-grid, .bento, .strip, .hb-pills) > :nth-child(5))    { --mo-g: 160ms; }
  :where(:is(.stat-grid, .tk-list, .live-grid, .dl-grid, .bento, .strip, .hb-pills) > :nth-child(6))    { --mo-g: 200ms; }
  :where(:is(.stat-grid, .tk-list, .live-grid, .dl-grid, .bento, .strip, .hb-pills) > :nth-child(n+7)) { --mo-g: 230ms; }

  /* KPI numbers "count in" a beat after their tile; hero pills follow the hero. */
  :where(html.mo-enter :is(.stat .stat-value, .hero-banner .hb-bignum)) {
    animation: mo-up .5s var(--mo-ease-out) calc(var(--mo-d, 0ms) + var(--mo-g, 0ms) + 150ms) backwards;
  }
  :where(html.mo-enter .hero-banner .hb-pills > *) {
    animation: mo-up .38s var(--mo-ease-out) calc(var(--mo-d, 0ms) + var(--mo-g, 0ms) + 160ms) backwards;
  }

  /* Progress bars fill from the left (scale, not width — no layout work). */
  :where(html.mo-enter .bar > span) {
    transform-origin: 0 50%;
    animation: mo-grow .6s var(--mo-ease-out) calc(var(--mo-d, 0ms) + var(--mo-g, 0ms) + 200ms) backwards;
  }

  /* Mobile bottom tabs: the active tab's yellow indicator draws in. Origin 0
     because the bar is centred with translateX(-50%) — keeps the growth centred. */
  :where(html.mo-enter .bottom-tabs .tab.active)::after {
    transform-origin: 0 50%;
    animation: mo-grow .35s var(--mo-ease-out) .12s backwards;
  }
}


/* ── 2. HOVER & PRESS ──────────────────────────────────────────────────── */

/* Content cards / form sections get a deeper, softer shadow on hover — and
   while you're typing inside them. No lift here on purpose: big cards full
   of text or tables shouldn't jump under the pointer. Clickable cards,
   .stat tiles and .tk-cards keep their existing lifts from app.css (those
   rules are more specific, so they still win). */
:where(.fsec) { transition: box-shadow var(--mo-base) var(--mo-ease-out); }
:where(.card, .fsec):focus-within { box-shadow: var(--mo-shadow-hover); }

/* Bespoke tables highlight their cells (not the row) — let those fade too. */
:where(.page tbody td) { transition: background-color var(--mo-fast) ease-out, box-shadow var(--mo-fast) ease-out; }

@media (hover: hover) {
  :where(.card, .fsec):hover { box-shadow: var(--mo-shadow-hover); }

  /* .table rows: a slim brand rail on the hovered row, next to app.css's tint. */
  .table tbody tr:hover > td:first-child { box-shadow: inset 2px 0 0 var(--yellow-d); }
}

/* Sidebar links: a yellow rail draws in on hover / keyboard focus. */
@media screen and (hover: hover) and (min-width: 901px) {
  .nav-link { position: relative; }
  .nav-link::before {
    content: ""; position: absolute; left: 0; top: 50%;
    width: 3px; height: 18px; margin-top: -9px; border-radius: 0 3px 3px 0;
    background: var(--yellow); pointer-events: none;
    opacity: 0; transform: scaleY(0);
    transition: transform var(--mo-base) var(--mo-ease-out), opacity var(--mo-fast) ease-out;
  }
  .nav-link:hover::before, .nav-link:focus-visible::before { opacity: 1; transform: none; }
  .nav-link.active::before { content: none; }
}

@media screen and (prefers-reduced-motion: no-preference) {

  /* Press-in for plain/bespoke buttons. `scale` composes with any transform
     the button already has. Classes that already press via app.css are
     excluded so they don't double up. */
  :where(button, input[type="submit"], input[type="button"], input[type="reset"], [role="button"]) {
    transition: scale .12s var(--mo-ease-out);   /* zero specificity: any existing transition wins */
  }
  /* (one compound selector — no whitespace before :not, which would make it a descendant match) */
  :where(button, input[type="submit"], input[type="button"], input[type="reset"], [role="button"]):not(.btn, .btn-pri, .btn-ghost, .btn-sec, .edit-btn, .save, :disabled):active { scale: .97; }

  /* Clickable cards and segmented tabs give under the finger/cursor. */
  :where(a.card, a.tcard, a.live-card, a.dl-card, a.shot):active { scale: .985; }
  .segment > a:active, .segment > button:active { scale: .96; }

  /* Mobile bottom tabs: tactile press (keeps app.css's colour transition). */
  .bottom-tabs .tab { transition: color .15s, scale .14s var(--mo-ease-out); }
  .bottom-tabs .tab:active { scale: .92; }
}

@media screen and (hover: hover) and (prefers-reduced-motion: no-preference) {

  /* Sidebar label nudges toward the rail; its count badge pops. */
  .nav-link .nav-label { transition: translate var(--mo-base) var(--mo-ease-out); }
  .nav-link:hover .nav-label { translate: 3px 0; }

  /* Badges, chips and counters: a small pop. */
  :where(.badge, .chip-soft, .nav-badge) { transition: scale var(--mo-fast) var(--mo-ease-pop); }
  :where(.badge, .chip-soft):hover, .nav-link:hover .nav-badge { scale: 1.07; }

  /* Hero pills float up a touch. */
  :where(.hero-banner .hb-pill) { transition: translate var(--mo-fast) var(--mo-ease-out), box-shadow var(--mo-base) var(--mo-ease-out); }
  :where(.hero-banner .hb-pill):hover { translate: 0 -2px; box-shadow: 0 8px 18px rgba(0,0,0,.14); }

  /* Stacked avatars: the hovered face peeks up. */
  :where(.avatar-stack > *) { transition: translate var(--mo-fast) var(--mo-ease-out); }
  :where(.avatar-stack > *):hover { translate: 0 -3px; }

  /* Screenshot thumbnails zoom gently inside their frame (.shot clips). */
  :where(.shot img) { transition: scale .45s var(--mo-ease-out); }
  :where(.shot:hover img) { scale: 1.045; }
}


/* ── 3. FOCUS ──────────────────────────────────────────────────────────── */

/* Brand-yellow keyboard ring for controls that don't draw their own focus
   style. Fully zero specificity (pseudo-class inside :where too), so .btn,
   inputs, .nav-link and any view's own focus style keep theirs; it only
   replaces the browser's default blue ring. */
:where(button, summary, [role="button"], [tabindex]:not([tabindex="-1"])):where(:focus-visible) {
  outline: 2px solid var(--yellow-d); outline-offset: 2px;
}
/* Sidebar group toggles set outline:none with no replacement — give them a ring. */
.nav-group > summary:focus-visible { color: #fff; box-shadow: inset 0 0 0 2px rgba(255,212,0,.55); }

@media screen and (prefers-reduced-motion: no-preference) {
  /* The ring eases in from a looser, transparent outline (zero specificity:
     never displaces an animation a view has set on the element). */
  :where(a, button, summary, [role="button"], [tabindex]):where(:focus-visible) {
    animation: mo-ring var(--mo-base) var(--mo-ease-out);
  }
}


/* ── 4. DISCLOSURES, DIALOGS & MODALS ──────────────────────────────────── */

@media screen and (prefers-reduced-motion: no-preference) {

  /* <details> content fades + drops in on open and back out on close, in
     browsers that support ::details-content (current Chrome/Edge/Safari;
     verified in Chrome). Elsewhere the rule is ignored and details keep
     toggling instantly. Content-area only — the sidebar's nav groups keep
     their own navExpand animation. */
  .page details::details-content {
    opacity: 0; translate: 0 -4px;
    transition: opacity var(--mo-base) var(--mo-ease-out),
                translate var(--mo-base) var(--mo-ease-out),
                content-visibility var(--mo-base) allow-discrete;
  }
  .page details[open]::details-content { opacity: 1; translate: none; }

  /* Native <dialog> (e.g. My Accounts upload dialog). */
  :where(.page dialog[open]) { animation: mo-pop var(--mo-base) var(--mo-ease-out); }
  :where(.page dialog[open])::backdrop { animation: mo-fade var(--mo-base) ease-out; }

  /* Staff modals that open instantly today: Team Chat "new message" and
     My Files. Backdrop fades; the chat panel pops (My Files' card already
     has its own mfRise). */
  :where(.tc-modal-bg.on, .mf-modal.open) { animation: mo-fade .18s ease-out; }
  :where(.tc-modal-bg.on .tc-modal) { animation: mo-pop var(--mo-base) var(--mo-ease-out); }
}


/* ── 5. REVEAL ON SCROLL  (classes toggled by /assets/js/motion.js) ────── */

@media screen and (prefers-reduced-motion: no-preference) {
  /* !important so a view's own opacity can't make a pending block flash in
     before its reveal. Screen-only: printing always shows everything. */
  .mo-pending { opacity: 0 !important; }
  .mo-in { animation: mo-rise .5s var(--mo-ease-out) var(--mo-rd, 0ms) backwards; }
}


/* ── 6. TARGETED FIXES ─────────────────────────────────────────────────── */

/* Dashboard (dashboard/overview.php): the bento tiles enter with their own
   `rise … both`, which pins transform:translateY(0) forever — so
   `.dash .tile:hover { transform: translateY(-3px) }` never fired. Same
   entrance; it just hands transform back once it has finished. */
.page .dash .bento > * { animation-fill-mode: backwards; }


/* ── 7. SIGN-IN PAGES (layouts/auth.php) ───────────────────────────────── */

@media screen and (prefers-reduced-motion: no-preference) {
  :where(.auth-wrap > *) { animation: mo-rise .5s var(--mo-ease-out) backwards; }
  /* A failed sign-in gives the error a small sideways nudge. */
  :where(.auth-card .alert-error) { animation: mo-nudge .42s ease-in-out .45s; }
}
