/*
 * 1confirmed portal — motion between things.
 *
 * portal.css animates what happens *inside* a page: the entrance, a press, a
 * menu, a toast. This sheet is what happens *between* them — one page handing
 * over to the next, the wait while it is fetched, a dialog coming and going,
 * a table filling in, a note appearing under a field. It loads on every
 * portal page and on the sign-in pages, after portal.css and before any
 * page sheet, so a section can still say something more specific.
 *
 * The rules it keeps:
 *   · tokens only — durations, curves, colours and levels from tokens.css,
 *     save the progress line's own long creep
 *   · named properties, never `transition: all`
 *   · nothing here fills past its own end: a lingering transform is a
 *     containing block for every `position: fixed` inside it
 *   · prefers-reduced-motion turns all of it off, and the page is simply
 *     there — the reduced-motion block in portal.css already collapses the
 *     keyframes, and the page handover below is not even declared
 *
 * The behaviour half is resources/js/portal/motion.js.
 */

/* ═══════════════════════════════════════════════════════════
   PAGE TO PAGE — a cross-document view transition
   ═══════════════════════════════════════════════════════════

   The portal is server-rendered, so every click on the rail used to be a
   full repaint: white, then the next page. A cross-document view transition
   lets the browser hold the old page, take a picture of it, and hand over
   to the new one instead.

   What moves, and what does not:
     · the rail and the topbar are named, so they stay exactly where they
       are and only their contents change — the frame never flickers
     · the current page's marker in the rail is named on its own, so it
       glides from the item you left to the item you chose
     · the logo is named in the rail and on the sign-in card, so signing in
       carries it from the middle of the card to the corner of the product
     · everything else is the root: the old page lifts away on top while
       the new page runs its own staggered entrance underneath

   Browsers without cross-document view transitions — and anybody who has
   asked for reduced motion — navigate exactly as before.
*/

@media (prefers-reduced-motion: no-preference) {
    @view-transition {
        navigation: auto;
    }
}

/* The old page leaves above the new one rather than cross-fading with it.
   A cross-fade adds the two pictures together, which reads as two pages at
   once; this reads as one page making way for the next, and lets the new
   page's own .cl-enter entrance do the arriving. Both blend normally — the
   default `plus-lighter` would wash an opaque page out to white. */
::view-transition-old(root),
::view-transition-new(root) {
    mix-blend-mode: normal;
}

::view-transition-old(root) {
    z-index: 1;
    animation: cl-page-out var(--dur-page) var(--ease-out) both;
}

::view-transition-new(root) {
    animation: none;
}

@keyframes cl-page-out {
    to {
        opacity: 0;
        transform: translateY(-10px) scale(0.995);
    }
}

/* The frame. Named only where it is a frame — beside the content on a wide
   screen. On a phone the rail is a sheet parked off the edge, and naming it
   there would fly a picture of it across the page. */
.cl-topbar { view-transition-name: cl-topbar; }

@media (min-width: 64rem) {
    .cl-rail { view-transition-name: cl-rail; }
    .cl-rail__marker { view-transition-name: cl-rail-marker; }
    .cl-rail__brand > .cl-rail__logo { view-transition-name: cl-brand; }
}

/* The sign-in card is the same card from page to page — sign in, create an
   account, reset — so it is named too, and changes shape rather than being
   replaced: the narrow sign-in card widens into the two-column registration
   one. The pictures are shown at their own size inside the changing frame
   instead of being stretched to fit it. */
.cl-auth__panel { view-transition-name: cl-auth-panel; }
.cl-auth__brand > .cl-auth__logo { view-transition-name: cl-brand; }

::view-transition-group(cl-auth-panel) {
    animation-duration: calc(var(--dur-page) * 1.3);
    animation-timing-function: var(--ease-out);
}

::view-transition-old(cl-auth-panel),
::view-transition-new(cl-auth-panel) {
    block-size: 100%;
    object-fit: none;
    object-position: 50% 0;
}

/* The marker lands on the spring: it is the one thing on the page that is
   answering the click directly. */
::view-transition-group(cl-rail-marker) {
    animation-duration: calc(var(--dur-page) * 1.4);
    animation-timing-function: var(--ease-spring);
}

::view-transition-group(cl-brand) {
    animation-duration: calc(var(--dur-page) * 1.5);
    animation-timing-function: var(--ease-out);
}

/* The frame's own contents — a new title in the topbar, a new item lit in
   the rail — change quickly; the frame is not the news. */
::view-transition-group(cl-topbar),
::view-transition-group(cl-rail) {
    animation-duration: var(--dur-short);
}

/* ═══════════════════════════════════════════════════════════
   THE WAIT — a progress line across the top of the page
   ═══════════════════════════════════════════════════════════

   Between a click and the next page there is a server round trip, and on a
   busy account it is a long one. Nothing on the page used to say that the
   click had been taken. motion.js adds this line when a navigation starts;
   it creeps towards the end and never quite reaches it, because nothing
   knows how long the server will take, and it fills the rest of the way at
   the moment the next page takes over.

   It shows only after a beat: a page that arrives at once should not flash
   a line on its way.
*/
.cl-progress {
    position: fixed;
    inset-block-start: 0;
    inset-inline: 0;
    z-index: var(--z-toast);
    block-size: 3px;
    pointer-events: none;
    background: var(--color-accent);
    transform: scaleX(0);
    transform-origin: left center;
    opacity: 0;
    /* Its own name, so the handover can fade it out over the new page
       instead of cutting it off with the old one. */
    view-transition-name: cl-progress;
}

[dir="rtl"] .cl-progress { transform-origin: right center; }

/* The creep is the one curve of its own here: fast for the first second, so
   the click visibly registered, then slower and slower for as long as the
   server takes. */
.cl-progress.is-running {
    animation:
        cl-progress-show var(--dur-short) var(--ease-out) var(--dur-short) both,
        cl-progress-creep 14s cubic-bezier(0.08, 0.7, 0.2, 1) var(--dur-short) both;
}

/* Done: the rest of the way, at once, then gone. */
.cl-progress.is-done {
    opacity: 0;
    transform: scaleX(1);
    transition: opacity var(--dur-long) var(--ease-out) var(--dur-short);
}

@keyframes cl-progress-show {
    from { opacity: 0; }
    to { opacity: 1; }
}

@keyframes cl-progress-creep {
    0% { transform: scaleX(0); }
    6% { transform: scaleX(0.3); }
    30% { transform: scaleX(0.62); }
    100% { transform: scaleX(0.94); }
}

::view-transition-old(cl-progress) {
    animation: cl-progress-leave var(--dur-page) var(--ease-out) both;
}

@keyframes cl-progress-leave {
    to { opacity: 0; }
}

/* ═══════════════════════════════════════════════════════════
   DIALOGS — every native <dialog> arrives and leaves
   ═══════════════════════════════════════════════════════════

   Opened with showModal() a dialog used to be on screen in one frame and
   gone in the next. Now it rises a little and scales up into place on the
   spring, and the backdrop fades in under it; closing reverses it, faster.
   `allow-discrete` keeps the dialog drawn while it leaves — browsers that
   cannot do that simply close it at once, as before.

   :where() keeps all of this at zero weight, so any page sheet that styles
   its own dialog still wins outright. The two dialogs that already have an
   entrance of their own are left to it.
*/
:where(dialog:not(.ti-dialog, .cl-com__dialog)) {
    transition:
        opacity var(--dur-mid) var(--ease-out),
        scale var(--dur-mid) var(--ease-spring),
        translate var(--dur-mid) var(--ease-spring),
        overlay var(--dur-mid) allow-discrete,
        display var(--dur-mid) allow-discrete;
}

:where(dialog:not(.ti-dialog, .cl-com__dialog):not([open])) {
    opacity: 0;
    scale: 0.98;
    translate: 0 6px;
    transition-duration: var(--dur-short);
    transition-timing-function: var(--ease-in);
}

@starting-style {
    :where(dialog:not(.ti-dialog, .cl-com__dialog)[open]) {
        opacity: 0;
        scale: 0.95;
        translate: 0 14px;
    }
}

:where(dialog:not(.ti-dialog, .cl-com__dialog))::backdrop {
    transition:
        opacity var(--dur-mid) var(--ease-out),
        overlay var(--dur-mid) allow-discrete,
        display var(--dur-mid) allow-discrete;
}

:where(dialog:not(.ti-dialog, .cl-com__dialog):not([open]))::backdrop {
    opacity: 0;
}

@starting-style {
    :where(dialog:not(.ti-dialog, .cl-com__dialog)[open])::backdrop {
        opacity: 0;
    }
}

/* ═══════════════════════════════════════════════════════════
   TABLES — rows arrive in order once their data does
   ═══════════════════════════════════════════════════════════

   The skeleton gives way to the rows, and the first screenful of rows comes
   in top to bottom rather than all at once — it is the moment a list of
   messages becomes yours, and it should read as arriving. Opacity and a
   short rise only, and `backwards`, so a row is never left transformed
   under its own action menu.

   The datatable only raises the skeleton when a fetch is slow (see
   startLoading in datatable.js), so a quick page turn does not replay this.
*/
.cl-dt:not(.is-loading) .gridjs-tbody .gridjs-tr {
    animation: cl-row-in var(--dur-mid) var(--ease-out) backwards;
}

.cl-dt:not(.is-loading) .gridjs-tbody .gridjs-tr:nth-child(2) { animation-delay: calc(var(--dur-stagger) * 0.5); }
.cl-dt:not(.is-loading) .gridjs-tbody .gridjs-tr:nth-child(3) { animation-delay: calc(var(--dur-stagger) * 1); }
.cl-dt:not(.is-loading) .gridjs-tbody .gridjs-tr:nth-child(4) { animation-delay: calc(var(--dur-stagger) * 1.5); }
.cl-dt:not(.is-loading) .gridjs-tbody .gridjs-tr:nth-child(5) { animation-delay: calc(var(--dur-stagger) * 2); }
.cl-dt:not(.is-loading) .gridjs-tbody .gridjs-tr:nth-child(6) { animation-delay: calc(var(--dur-stagger) * 2.5); }
.cl-dt:not(.is-loading) .gridjs-tbody .gridjs-tr:nth-child(7) { animation-delay: calc(var(--dur-stagger) * 3); }
.cl-dt:not(.is-loading) .gridjs-tbody .gridjs-tr:nth-child(n + 8) { animation-delay: calc(var(--dur-stagger) * 3.5); }

@keyframes cl-row-in {
    from { opacity: 0; transform: translateY(6px); }
    to { opacity: 1; transform: none; }
}

/* ═══════════════════════════════════════════════════════════
   NOTES — a line that appears under a field, or a result
   ═══════════════════════════════════════════════════════════

   "Sent", "That number is not on WhatsApp", "Code resent": these appear in
   answer to something, mostly by Alpine flipping display. A CSS animation
   restarts every time display does, so each one arrives instead of being
   suddenly there — the success line after a send most of all.
*/
.cl-note,
.cl-auth__note {
    animation: cl-note-in var(--dur-mid) var(--ease-out) backwards;
}

@keyframes cl-note-in {
    from { opacity: 0; transform: translateY(-4px); }
    to { opacity: 1; transform: none; }
}

/* ═══════════════════════════════════════════════════════════
   SMOOTH SCROLLING — the wheel glides in .cl-main
   ═══════════════════════════════════════════════════════════

   resources/js/smooth-scroll.js eases each turn of a desktop wheel instead
   of jumping by it (never on touch, never with reduced motion). It moves the
   scroller itself a little each frame, so the browser's own smooth behaviour
   must not ease those moves a second time. Anything that scrolls on its own
   keeps its overscroll to itself, so reaching its end does not carry on
   into the page.
*/
.cl-main.lenis.lenis-smooth { scroll-behavior: auto !important; }

.lenis [data-lenis-prevent] { overscroll-behavior: contain; }

/* ═══════════════════════════════════════════════════════════
   REDUCED MOTION
   ═══════════════════════════════════════════════════════════

   portal.css collapses every keyframe and transition to nothing, and the
   page handover above is only declared for no-preference. The line still
   says that something is loading — it just appears instead of creeping.
*/
@media (prefers-reduced-motion: reduce) {
    .cl-progress.is-running {
        animation: none;
        opacity: 1;
        transform: scaleX(0.6);
    }
}
