/* ============================================================================
   MOTION + CLARITY  ·  always on, in both themes
   ----------------------------------------------------------------------------
   Linked after tailwind.css, concierge.css AND index.html's <style>, so a rule here wins any tie
   on specificity. It is a separate file because ?theme=classic switches concierge.css off, and
   none of this is theme paint: page and dialog entrances, toasts, the disabled look and the
   keyboard focus ring must hold whichever theme is on.

   The house rules for motion:
   - 120-220ms; opacity plus at most a few px of movement or 1.5% of scale;
   - never in the way of a click, never moving layout;
   - all of it inside `prefers-reduced-motion: no-preference`, so someone who asked their phone
     for less motion gets none (the reduce block at the end also stills the older loops);
   - an ENTRANCE uses fill-mode `backwards`, never `both`: a transform kept after the animation
     makes the element the containing block of every position:fixed child, which is how the top
     bar once trapped the mobile drawer (index.html). An EXIT may use `forwards`, because the
     node is removed as it ends.
   Bump the ?v= on this file's <link> in index.html whenever it changes (tests/ui-motion.test.js).
   ============================================================================ */

@keyframes lsm-fade-in   { from { opacity: 0; } }
@keyframes lsm-rise-in   { from { opacity: 0; transform: translateY(4px); } }
@keyframes lsm-panel-in  { from { opacity: 0; transform: translateY(6px) scale(.985); } }
@keyframes lsm-fade-out  { to   { opacity: 0; } }
@keyframes lsm-panel-out { to   { opacity: 0; transform: translateY(4px) scale(.99); } }
@keyframes lsm-drop-in   { from { opacity: 0; transform: translateY(-4px); } }
@keyframes lsm-slide-in  { from { opacity: 0; transform: translateX(12px); } }
@keyframes lsm-down-in   { from { opacity: 0; transform: translateY(-8px); } }
@keyframes lsm-sheen     { from { background-position: 150% 0; } to { background-position: -50% 0; } }

/* ---------- Page entrance --------------------------------------------------------------------
   setLoading() puts the skeleton in; when the page's markup first replaces it, app.js
   (_pageEnter) tags those new top-level blocks .ls-enter for 300ms. Blocks that hold a fixed
   overlay get .ls-enter-fade: no transform, so the overlay stays pinned to the viewport. */
@media (prefers-reduced-motion: no-preference) {
    #content-area > .ls-enter      { animation: lsm-rise-in .18s cubic-bezier(.2,.8,.2,1) backwards; }
    #content-area > .ls-enter-fade { animation: lsm-fade-in .18s ease-out backwards; }
    /* The skeleton stays see-through for its first 120ms: a page that loads faster than that
       goes straight from the old page to the new one, with no grey blink in between. Its space
       is held all the while, so nothing below it moves. */
    .ls-sk-wrap { animation: lsm-fade-in .16s ease-out .12s backwards; }
}

/* ---------- Skeleton blocks: a soft sheen instead of the full-strength blink ------------------
   The plain border-colour block is the base, and all a reduced-motion user sees. The sheen is a
   separate background-image with no var() in it, so a browser that cannot draw it simply keeps
   the plain block (a var() inside one `background` shorthand would blank the whole thing). */
.ls-sk { background-color: var(--ls-border); }
@media (prefers-reduced-motion: no-preference) {
    .ls-sk {
        background-image: linear-gradient(90deg, transparent 0, rgba(255,255,255,.55) 50%, transparent 100%);
        background-size: 200% 100%;
        background-repeat: no-repeat;
        animation: lsm-sheen 1.4s linear infinite;
    }
    body.dark .ls-sk { background-image: linear-gradient(90deg, transparent 0, rgba(255,255,255,.07) 50%, transparent 100%); }
}

/* ---------- Dialogs ----------------------------------------------------------------------------
   .ls-pop is the opt-in class for an overlay (`fixed inset-0` scrim with ONE panel child): the
   scrim fades, the panel rises 6px. _modalCore (every _alert / _confirm / _prompt) uses it, and
   .ls-pop-out is its 140ms exit. The static dialogs in index.html are named by id instead of
   given the class, and animate each time `hidden` comes off them (display:none -> flex restarts
   a CSS animation, no JS needed). Overlays a page rebuilds on every keystroke or tick are left
   out on purpose: they would pop again each time. */
@media (prefers-reduced-motion: no-preference) {
    .ls-pop,
    :is(#punch-modal, #emp-modal, #holiday-modal, #user-modal, #role-modal, #list-modal,
        #task-modal, #pcl-modal, #blog-modal, #confirm-modal):not(.hidden) {
        animation: lsm-fade-in .16s ease-out backwards;
    }
    .ls-pop > *,
    :is(#punch-modal, #emp-modal, #holiday-modal, #user-modal, #role-modal, #list-modal,
        #task-modal, #pcl-modal, #blog-modal, #confirm-modal):not(.hidden) > * {
        animation: lsm-panel-in .2s cubic-bezier(.2,.8,.2,1) backwards;
    }
    .ls-pop-out       { animation: lsm-fade-out .14s ease-in forwards; }
    .ls-pop-out > *   { animation: lsm-panel-out .14s ease-in forwards; }

    /* Bell panel and avatar menu drop 4px into place. */
    #notif-panel:not(.hidden),
    #tb-profile-menu:not(.hidden) { animation: lsm-drop-in .14s ease-out backwards; }

    /* "A new version is available": fade only, its inline transform centres it. */
    #app-update-banner { animation: lsm-fade-in .2s ease-out backwards; }
}

/* ---------- Toasts ---------------------------------------------------------------------------
   Quicker in and out than before (.22s in, .3s out). On a phone the stack spans the width just
   under the top bar instead of sitting in the corner, where it covered Clock IN and the avatar
   for up to six seconds. No env(safe-area-inset-*): the page has no viewport-fit=cover, so
   those insets are always 0 here. */
.ls-toast { animation: none; }
.ls-toast.ls-leaving { opacity: 0; transform: none; transition: opacity .12s ease-in; }
@media (prefers-reduced-motion: no-preference) {
    .ls-toast { animation: lsm-slide-in .2s cubic-bezier(.2,.8,.2,1) backwards; }
    .ls-toast.ls-leaving { transform: translateX(12px); transition: opacity .18s ease-in, transform .18s ease-in; }
}
/* The dismiss button: a 28px target that does not make the toast taller (the phone rule
   button{min-height:40px} in index.html used to leave a blank band under one-line toasts). */
.ls-toast-x {
    flex: none; width: 28px; height: 28px; min-height: 0; padding: 0; margin: -5px -8px -5px 0;
    display: inline-flex; align-items: center; justify-content: center; border-radius: 8px;
}
.ls-toast-x:hover { background: var(--ls-bg-2) !important; color: var(--ls-text) !important; }
@media (max-width: 640px) {
    #ls-toasts { top: 5rem; left: .75rem; right: .75rem; width: auto; max-width: none; }
}
/* Desktop too: the stack sat at top:16px, over the 74px top bar, covering the bell and the avatar
   while a toast was up. Just under the bar instead. */
@media (min-width: 641px) {
    #ls-toasts { top: calc(74px + .75rem); }
}
@media (max-width: 640px) and (prefers-reduced-motion: no-preference) {
    .ls-toast { animation-name: lsm-down-in; }
    .ls-toast.ls-leaving { transform: translateY(-6px); }
}

/* The clock in/out banner (showPunchToast): drops in from above, fades out via .ls-leaving. */
#punch-toast.ls-leaving { opacity: 0; transition: opacity .18s ease-in; }
@media (prefers-reduced-motion: no-preference) {
    #punch-toast:not(.hidden) { animation: lsm-down-in .2s cubic-bezier(.2,.8,.2,1) backwards; }
}

/* ---------- Disabled buttons ------------------------------------------------------------------
   Nothing styled a disabled button, so "Recording…" and every other busy or blocked button
   looked as clickable as ever and people tapped again. Faded, with a not-allowed cursor; no
   pointer-events:none, because some explain the block in their title tooltip.
   :where() keeps this weaker than any button's own :disabled rule (handbook editor, onboarding,
   Noga, Ask Vini), and it skips buttons that already draw their own disabled state: a class
   with opacity- in it (disabled:opacity-40, housekeeping's cancelled-round cards, which are
   whole <button disabled> cards already at opacity-50), cursor-not-allowed, the handbook sign
   button that turns bg-gray-300, and the theme swatches people without the permission see. */
button:disabled:where(:not([class*="opacity-"], .cursor-not-allowed, .bg-gray-300, .theme-swatch)) {
    opacity: .55;
    cursor: not-allowed;
}
/* ...and a disabled solid button keeps neither its glow nor the hover lift (index.html and
   concierge.css set both with !important or higher specificity, hence the long selectors). */
body:not(.dark) button:is(.bg-teal-600, .bg-teal-700, .bg-indigo-600, .bg-indigo-700):disabled,
body:not(.dark) button:is(.bg-teal-600, .bg-teal-700, .bg-indigo-600, .bg-indigo-700):disabled:hover,
body.dark button:is(.bg-teal-500, .bg-teal-600, .bg-teal-700, .bg-indigo-500, .bg-indigo-600, .bg-indigo-700):disabled {
    box-shadow: none !important;
    filter: none;
    transform: none;
}

/* ---------- Keyboard focus ring ---------------------------------------------------------------
   There was no focus style at all, so tabbing fell back to the browser's ring, which nearly
   vanishes on the pine and cyan buttons. :focus-visible shows it for the keyboard only, never on
   a click or tap. :where() keeps it weak: an element's own focus style (focus:outline-none, the
   smart-select trigger, inputs' ring) still wins. */
:where(button, a[href], summary, [role="button"], [role="menuitem"], [role="tab"], [tabindex]:not([tabindex="-1"])):focus-visible {
    outline: 2px solid var(--ls-accent, #0d9488);
    outline-offset: 2px;
}
/* The top bar is dark in both themes: a white ring there. */
#main-app > nav :where(button, a[href]):focus-visible { outline-color: rgba(255,255,255,.9); }
/* Sidebar groups clip their rows (overflow:hidden, for the open slide), so the ring is drawn
   inside the row; white on the solid "you are here" pill, where the accent would not show. */
.sidebar-col :where(button, a[href], summary):focus-visible { outline-offset: -2px; }
.sidebar-col [aria-current="page"]:focus-visible { outline-color: #fff; }

/* ---------- Sidebar hover: calm, nothing moves -------------------------------------------------
   Hover used to slide the label 6px right (an animated padding change, so the row's contents
   moved under the pointer, in light mode only) and tilt and grow the icon. The background and
   colour change stay; the padding stays put (p-2 rows and p-1.5 sub-rows) and the icon is still. */
body:not(.dark) :is(.sidebar-col ul button, .sidebar-col ul a).p-2:hover { padding-left: .5rem; }
body:not(.dark) :is(.sidebar-col ul button, .sidebar-col ul a).p-1\.5:hover { padding-left: .375rem; }
:is(.sidebar-col ul button, .sidebar-col ul a):hover .menu-icon,
.sidebar-col details summary:hover .menu-icon { transform: none; }
/* Collapsed icon rail: most rows are <a> (view-mapped links), and the rail only centred <button>
   and <summary> rows, so link icons sat 11px left of the rest. */
@media (min-width: 768px) {
    body.sidebar-collapsed .sidebar-col ul a {
        justify-content: center !important;
        padding: 0.55rem 0.25rem !important;
    }
}

/* ---------- Sidebar accordion: the group being shut now folds too ----------------------------
   Opening a group already slid in; the one closed by the accordion vanished at once, so when it
   sat above the tapped heading, that heading jumped up under the finger. Where the browser can
   animate to height:auto (interpolate-size + ::details-content, Chromium) both directions fold
   over 180ms; elsewhere nothing changes. interpolate-size is inherited, so it is set on the
   groups only, never on .sidebar-col, whose own transition-all would start animating auto sizes.
   Not in the collapsed icon rail, which shows and hides groups with display rules of its own. */
@supports (interpolate-size: allow-keywords) {
    @media (prefers-reduced-motion: no-preference) {
        body:not(.sidebar-collapsed) .sidebar-col details { interpolate-size: allow-keywords; }
        body:not(.sidebar-collapsed) .sidebar-col details::details-content {
            block-size: 0;
            overflow-y: clip;
            transition: block-size .18s ease, content-visibility .18s allow-discrete;
        }
        body:not(.sidebar-collapsed) .sidebar-col details[open]::details-content { block-size: auto; }
    }
}

/* ---------- Less motion, when asked -----------------------------------------------------------
   Older animations that ignored the setting: the command palette pop, the skeleton blink, the
   card and dashboard hover lifts, the press-down on buttons and the logo wiggle. */
@media (prefers-reduced-motion: reduce) {
    #ls-cmd-box,
    .ls-sk { animation: none; }
    .ls-dash-card.lift:hover,
    body:not(.dark) .bg-white.rounded-2xl:hover,
    body:not(.dark) .bg-white.shadow-sm:hover,
    body:not(.dark) .bg-white.shadow:hover,
    nav:hover .nav-logo,
    button:active,
    :is(.sidebar-col ul button, .sidebar-col ul a):active { transform: none; }
}
