/*
 * Alertify motion — portal-owned replacement for alertify's pulse/backOut
 * factory defaults. House rules: no overshoot, enter (dur-3) slower than
 * exit (dur-2), opacity/transform only — the one sanctioned exception is
 * the exit-only toast stack collapse, which is inherently layout.
 *
 * Unscoped on purpose: these rules only match alertify-generated DOM, and
 * they must reach basePublic pages (check-in kiosk, academy activation)
 * that never load the .portal-page-themed skin. Tokens are read with
 * literal fallbacks for the same reason.
 *
 * CONTRACTS (verified against vendored alertify.min.js 1.13.1):
 *  - Dialogs MUST use CSS animations: alertify chains focus/cleanup off
 *    animationend on .ajs-dialog (with a 1000ms setTimeout fallback).
 *    Transitions or animation:none defer focus/cleanup to that 1s timer —
 *    keep even reduced-motion durations at 1ms, never none.
 *  - The alertify `transition` option (pulse/zoom/fade/…) is bypassed:
 *    animation-name is overridden at the .ajs-in/.ajs-out level, so every
 *    dialog gets house motion regardless of per-dialog settings. The JS
 *    lifecycle (class toggling + animationend) is untouched, and the
 *    vendor's .ajs-no-transition / transitionOff escape hatch still wins
 *    via its own !important rules.
 *  - Toast DOM removal is a fixed 1000ms timer after dismiss (no
 *    transition listener) — the exit below finishes in 320ms and collapses
 *    the box to ~0 height so nothing snaps when the node is removed.
 *  - ajs-shake is removed by JS 200ms after it is added — portal-nudge
 *    must complete inside that window.
 *  - Dimmer/.ajs-modal wrapper: override transition longhands ONLY; the
 *    vendor's transition-property list (opacity, visibility) is
 *    load-bearing for the show/hide toggle.
 */

/* ── Dialogs (alert / confirm / prompt) ──────────────────────────── */
/* NO scale() on blurred surfaces: the dialog shell carries
   backdrop-filter, and animating a fractional scale makes Chromium
   re-sample the backdrop through a changing transform every frame — the
   page text behind the glass visibly shimmers ("background glitch").
   Pure translate samples 1:1 and is proven safe here (content scrolls
   under the blurred chrome all day). Rise + fade only. */
@keyframes portal-dialog-in {
    from { opacity: 0; transform: translateY(10px); }
    to   { opacity: 1; transform: translateY(0); }
}

@keyframes portal-dialog-out {
    from { opacity: 1; }
    to   { opacity: 0; }
}

/* The repeated .ajs-in/.ajs-out class is a deliberate specificity bump:
   the vendor's transition-name rules (.alertify.ajs-pulse.ajs-in…) are
   0,5,0 and would otherwise out-rank a plain 0,4,0 override on
   animation-name — the bounce would survive. Repeating the class ties at
   0,5,0 and this sheet loads later, so house motion wins for EVERY
   transition name (pulse, zoom, slide, …). */
.alertify.ajs-in.ajs-in:not(.ajs-hidden) .ajs-dialog {
    animation-name: portal-dialog-in;
    animation-duration: var(--portal-dur-3, 320ms);
    animation-timing-function: var(--portal-ease-emphasized, cubic-bezier(0.05, 0.7, 0.1, 1));
    animation-fill-mode: both;
}

.alertify.ajs-out.ajs-out.ajs-hidden .ajs-dialog {
    animation-name: portal-dialog-out;
    animation-duration: var(--portal-dur-2, 200ms);
    animation-timing-function: var(--portal-ease-in-out, cubic-bezier(0.45, 0, 0.25, 1));
    animation-fill-mode: both;
}

/* ── Dimmer + modal wrapper fades (longhands only — see header) ──── */
.alertify .ajs-dimmer,
.alertify .ajs-modal {
    transition-duration: var(--portal-dur-3, 320ms);
    transition-timing-function: var(--portal-ease-out, cubic-bezier(0.2, 0.7, 0.3, 1));
}

.alertify.ajs-hidden .ajs-dimmer,
.alertify.ajs-hidden .ajs-modal {
    transition-duration: var(--portal-dur-2, 200ms);
    transition-timing-function: var(--portal-ease-in-out, cubic-bezier(0.45, 0, 0.25, 1));
}

/* ── Invalid feedback: a nudge, not a seizure ─────────────────────
 * Vendor shake is ±10px with nine reversals in 100ms. Two reversals at
 * ±5px still reads as a head-shake "no". 180ms, because the JS removes
 * the class 200ms after adding it. The shorthand deliberately resets the
 * vendor's animation longhands. */
@keyframes portal-nudge {
    0%   { transform: translateX(0); }
    30%  { transform: translateX(-5px); }
    65%  { transform: translateX(3px); }
    100% { transform: translateX(0); }
}

.alertify .ajs-dialog.ajs-shake {
    animation: portal-nudge 180ms var(--portal-ease-out, ease-out) both;
}

/* ── Toasts (bottom-right is the only position the portal uses) ──── */
/* WHERE THE STACK SITS. One inset, both axes, measured from the portal's
   chrome rather than from the raw viewport edge.
   The bottom is not a bare 10px because the bottom of the window is not
   empty: the footer is fixed-bottom on a desk and the tab bar takes its
   place under 768px, so the vendor's flat inset parked every toast ON the
   chrome (z-index 1982 against the bar's 1035 — the toast won, and covered
   the middle tab). --portal-chrome-offset-bottom is the portal's existing
   answer to "how much of the bottom is spoken for": main's padding, the
   sidebar's own footer and every other bottom-anchored thing already read
   it, and it resolves to the footer's height on a desk and the tab bar's on
   a phone. The notifier simply never learned about it.
   The safe-area strip is inside that token already (both bars pad
   themselves with it), so adding --portal-safe-bottom here would reserve
   the home indicator twice.
   The 0px fallback is load-bearing for basePublic pages: the check-in kiosk
   and academy activation load THIS sheet but not navigation.css, so the
   token does not exist there — and those pages have no bottom chrome to
   clear, so the plain inset is the right answer. */
.alertify-notifier {
    --portal-toast-inset: 10px;   /* the vendor's own corner inset, named */
}

.alertify-notifier.ajs-right {
    right: var(--portal-toast-inset);
}

.alertify-notifier.ajs-bottom {
    bottom: calc(var(--portal-chrome-offset-bottom, 0px) + var(--portal-toast-inset));
}

/* Kill the 610px horizontal slingshot: pin the vendor's positional
   offset to its resting value in both states so nothing slides sideways.
   The offset IS the width, and has to be: .alertify-notifier is a
   ZERO-width fixed box pinned at the corner inset above, so the message
   overflows to the right of that point and the offset drags it back. Drag
   it by exactly its own width and its right edge lands on the container's
   — i.e. on that same inset. The vendor ships 290px against a 260px box,
   which is where the lopsided 40px right gutter came from: the stack read
   as shoved left, because it was. Deriving both from one custom property is
   what keeps them from drifting apart again if the width is ever retuned. */
.alertify-notifier .ajs-message {
    --portal-toast-width: 260px;   /* the vendor's own width, named */
    width: var(--portal-toast-width);
}

/* BOTH states, and the second selector is not redundant: the vendor's
   visible rule is 0,4,0 (.alertify-notifier.ajs-right .ajs-message.ajs-visible)
   and would out-rank a lone 0,3,0 pin — the toast would then rest at one
   offset and show at another, putting a 30px sideways twitch back into the
   enter. Matching 0,4,0 wins on file order (this sheet loads after the
   vendor's, enforced by BaseStylesheetOrderTest). */
.alertify-notifier.ajs-right .ajs-message,
.alertify-notifier.ajs-right .ajs-message.ajs-visible {
    right: var(--portal-toast-width);
}

/* Exit (destination = this base state): fade + drop first, then the
   geometry collapse glides the stack closed while the toast is already
   mostly transparent. Total 320ms — well inside the 1s removal timer. */
/* NO scale() here, same rule as the dialog keyframes above. This used to
   be the documented exception — toasts were the one overlay left opaque,
   so a fractional scale was safe on them. They carry backdrop-filter now
   (alertify-portal-theme.css), which puts them straight back under the
   rule: scaling a blurred surface makes Chromium re-sample the backdrop
   through a changing transform every frame and the page text behind the
   toast shimmers. Translate samples 1:1. */
.alertify-notifier .ajs-message {
    opacity: 0;
    transform: translateY(12px);
    /* overflow pairs with the max-height cap below: without it, text taller
       than the cap (or mid-collapse content on exit) would paint past the
       toast background instead of clipping with the box. */
    overflow: hidden;
    transition-property: opacity, transform, max-height, padding, margin-top;
    transition-duration: var(--portal-dur-2, 200ms);
    transition-timing-function: var(--portal-ease-in-out, cubic-bezier(0.45, 0, 0.25, 1));
    transition-delay: 0s, 0s, 120ms, 120ms, 120ms;
}

/* Enter: rise + fade only. The stack gap opens instantly by design — the
   vendor's max-height:100% "grow" never interpolated anyway (% against an
   auto-height parent), so the snap was always there; the arriving toast's
   rise is what the eye follows. max-height is a real length here so the
   exit collapse above has something to tween from. */
.alertify-notifier .ajs-message.ajs-visible {
    opacity: 1;
    transform: none;
    /* A real length so the exit collapse can tween (the vendor's 100% never
       interpolated anyway — % against an auto-height notifier). Generous:
       ~17 lines at toast width, so only a pathological message (a raw HTML
       error page in responseText) ever hits it — and overflow:hidden above
       makes that a clean clip, not text spilling past the pill. Taller caps
       only shorten the visible part of the exit collapse. */
    max-height: 24rem;
    transition-property: opacity, transform;
    transition-duration: var(--portal-dur-3, 320ms);
    transition-timing-function: var(--portal-ease-emphasized, cubic-bezier(0.05, 0.7, 0.1, 1));
    transition-delay: 0s;
}

/* ── Reduced motion (portal-owned) ────────────────────────────────
 * The vendored alertify.min.css used to carry a locally appended
 * reduced-motion patch that set animation:none — which silently deferred
 * dialog focus/cleanup to alertify's 1s fallback timer. The vendor file
 * is pristine 1.13.1 again; this block replaces the patch. 1ms durations
 * keep animationend/transition timing effectively instant while the
 * events still fire. */
@media (prefers-reduced-motion: reduce) {
    /* Selectors mirror the bumped specificity above — a plain 0,4,0 here
       would lose the duration to this file's own normal-path rules. */
    .alertify.ajs-in.ajs-in:not(.ajs-hidden) .ajs-dialog,
    .alertify.ajs-out.ajs-out.ajs-hidden .ajs-dialog,
    .alertify .ajs-dialog.ajs-shake {
        animation-duration: 1ms;
    }

    .alertify .ajs-dimmer,
    .alertify .ajs-modal,
    .alertify.ajs-hidden .ajs-dimmer,
    .alertify.ajs-hidden .ajs-modal,
    .alertify-notifier .ajs-message,
    .alertify-notifier .ajs-message.ajs-visible {
        transition-duration: 1ms;
        transition-delay: 0s;
    }
}
