/* Apple-style motion for the management and employee apps (owner ask
   2026-09-20, a deliberate change from DESIGN.md's minimal-functional motion).
   Spring-like curves; dialogs grow in and shrink back out; buttons give under
   the finger and spring back; tab panels, disclosures, expand / collapse and
   page changes ease in.
   Everything here is off when the system asks for reduced motion, and the
   reduce block at the end also stops the transitions the page stylesheets
   ship themselves.
   Loaded after the page styles, so it wins over their older animations. */
:root {
  --motion-spring: cubic-bezier(.32, .72, 0, 1);    /* iOS sheet / navigation */
  --motion-bounce: cubic-bezier(.34, 1.4, .64, 1);  /* small overshoot */
  --motion-exit: cubic-bezier(.4, 0, 1, 1);
  --motion-fast: 180ms;
  --motion-base: 320ms;
  --motion-slow: 460ms;
}

@media (prefers-reduced-motion: no-preference) {
  :root { interpolate-size: allow-keywords; }

  /* Dialogs and cards: grow in with a slight overshoot, shrink back out.
     The exit plays before the dialog disappears because `display` and
     `overlay` take part in the transition as discrete properties, so the
     element stays in the top layer until it has finished.  No script: the
     66 `close()` call sites keep their own timing, and the `close` event and
     returnValue still arrive the moment they always did.  A browser without
     allow-discrete drops that declaration and simply shows no exit.
     `scale` and `translate` are their own properties, so a dialog that
     carries its own `transform` keeps it. */
  dialog {
    opacity: 0;
    scale: .95;
    translate: 0 6px;
    transition:
      opacity 200ms var(--motion-exit),
      scale 200ms var(--motion-exit),
      translate 200ms var(--motion-exit),
      overlay 200ms allow-discrete,
      display 200ms allow-discrete;
  }
  dialog[open] {
    opacity: 1;
    scale: 1;
    translate: 0 0;
    transition:
      opacity var(--motion-base) var(--motion-spring),
      scale var(--motion-slow) var(--motion-bounce),
      translate var(--motion-slow) var(--motion-bounce),
      overlay var(--motion-slow) allow-discrete,
      display var(--motion-slow) allow-discrete;
  }
  @starting-style {
    dialog[open] { opacity: 0; scale: .92; translate: 0 12px; }
  }
  /* Literal durations: ::backdrop does not inherit the custom properties of
     its dialog in every engine. */
  dialog::backdrop {
    opacity: 0;
    transition:
      opacity 200ms ease,
      overlay 200ms ease allow-discrete,
      display 200ms ease allow-discrete;
  }
  dialog[open]::backdrop { opacity: 1; }
  @starting-style {
    dialog[open]::backdrop { opacity: 0; }
  }

  /* Pop-overs and panels shown by removing [hidden] (account menu, CSV
     language menu): they scale in, and on hide fade out before
     display:none applies (allow-discrete; older browsers switch at once). */
  :is(.account-popover, .csv-language-menu) {
    transition: opacity var(--motion-fast) var(--motion-spring), scale var(--motion-base) var(--motion-bounce), display var(--motion-fast) allow-discrete;
  }
  .account-popover { transform-origin: top right; }
  .csv-language-menu { transform-origin: top left; }
  :is(.account-popover, .csv-language-menu)[hidden] {
    opacity: 0; scale: .96; transition-duration: var(--motion-fast); transition-timing-function: var(--motion-exit);
  }
  @starting-style {
    :is(.account-popover, .csv-language-menu):not([hidden]) { opacity: 0; scale: .9; }
  }

  /* Expand / collapse and "show more" (payroll sections, report and account
     tables, notifications).  motion.js marks the page for a moment after such
     a click, so only what that click reveals slides in and what it hides
     fades out; ordinary re-renders (search, refresh) stay still. */
  :root[data-motion-reveal] :is(section:has(> header [aria-expanded]) > :not(header), tbody > tr:nth-child(n+6), .notification-list > article:nth-child(n+6)) {
    transition: opacity var(--motion-base) var(--motion-spring), translate var(--motion-base) var(--motion-spring), display var(--motion-fast) allow-discrete;
  }
  :root[data-motion-reveal] :is(section:has(> header [aria-expanded]) > :not(header), tbody > tr:nth-child(n+6), .notification-list > article:nth-child(n+6))[hidden] {
    opacity: 0; translate: 0 -4px; transition-duration: var(--motion-fast); transition-timing-function: var(--motion-exit);
  }
  @starting-style {
    :root[data-motion-reveal] :is(section:has(> header [aria-expanded]) > :not(header), tbody > tr:nth-child(n+6), .notification-list > article:nth-child(n+6)):not([hidden]) { opacity: 0; translate: 0 8px; }
  }

  /* Press feedback: give a little, spring back on release.  ``scale`` is its
     own property, so it composes with any transform a button already has. */
  :where(button, .button, .mini-button, a.button, [role="button"], .bottom-nav a, .rail-nav a) {
    transition: scale var(--motion-base) var(--motion-bounce), background-color var(--motion-fast) var(--motion-spring),
      border-color var(--motion-fast) var(--motion-spring), color var(--motion-fast) var(--motion-spring),
      box-shadow var(--motion-fast) var(--motion-spring), opacity var(--motion-fast) var(--motion-spring);
  }
  :where(button, .button, .mini-button, a.button, [role="button"], .bottom-nav a, .rail-nav a):active:not(:disabled, [aria-disabled="true"]) {
    scale: .96;
    transition-duration: 90ms;
    transition-timing-function: var(--motion-spring);
  }
  /* Not on the schedule board's drag surfaces, where a shrinking cell would
     move under the pointer, nor on menu triggers (a scaled trigger becomes the
     containing block of a pop-up inside it). */
  :is(.quick-time-slot, .week-time-slot, .quick-track-cell *, .call-resize-handle, .day-column-resizer, .time-row-resizer, .week-axis-resizer, .schedule-call, [draggable="true"], [aria-haspopup], [aria-expanded]):active { scale: none; }

  /* Tab panels and switches. */
  .tab-panel:not([hidden]) { animation: ssj-panel-in var(--motion-base) var(--motion-spring) both; }
  :where([role="tab"], .self-tabs button, .segmented button) { transition: color var(--motion-base) var(--motion-spring), background-color var(--motion-base) var(--motion-spring), scale var(--motion-base) var(--motion-bounce); }
  :where([role="tab"], .self-tabs button)::after { transition: transform var(--motion-base) var(--motion-spring), opacity var(--motion-base) var(--motion-spring); }

  /* Disclosures open and close to their content height. */
  details::details-content { height: 0; overflow: clip; transition: height var(--motion-base) var(--motion-spring), content-visibility var(--motion-base) allow-discrete; }
  details[open]::details-content { height: auto; }

  /* Page changes inside one app: the navigation stays, the page eases in. */
  @view-transition { navigation: auto; }
  .stage-rail { view-transition-name: ssj-rail; }
  /* A named element is a stacking context of its own, so the account menu inside the
     header (z-index 90) only counted against the header's other children: the page's
     cards and the bottom navigation, painted later, covered the open menu (2026-10-06).
     The header therefore takes a level itself, above the bottom navigation (20) and
     below toasts and banners (1000 and up). */
  .app-header { view-transition-name: ssj-header; position: relative; z-index: 100; }
  ::view-transition-old(root) { animation: ssj-page-out 180ms var(--motion-exit) both; }
  ::view-transition-new(root) { animation: ssj-page-in var(--motion-slow) var(--motion-spring) both; }
  ::view-transition-group(ssj-rail), ::view-transition-group(ssj-header) { animation: none; }

  @keyframes ssj-panel-in { from { opacity: 0; translate: 0 8px; } }
  @keyframes ssj-page-in { from { opacity: 0; transform: translateY(14px) scale(.99); } }
  @keyframes ssj-page-out { to { opacity: 0; } }
}

/* Clicking a field used to pop two focus rings at once: the page's own
   border-and-halo on :focus, and the 3px outline the shells draw on
   :focus-visible -- which Chrome also matches for a field that was clicked,
   because a field takes the keyboard (owner ask 2026-09-20).  While the focus
   came from a pointer, fields show neither: the border colour still changes,
   which is the feedback a mouse user needs.  Tab clears the mark, so keyboard
   focus keeps the full ring the design asks for.  Buttons and links are not
   touched.  No transition here -- the ring is meant to appear at once, the
   complaint was that it appeared at all. */
:root[data-pointer-focus] :is(input, select, textarea):is(:focus, :focus-visible) {
  outline: none;
  box-shadow: none;
}

/* Reduced motion stops everything, not only what this file adds.  The page
   stylesheets ship their own 160-180ms transitions (the account menu
   switches, the AI config and native settings toggles, the attendance board's
   pulse) and enumerating them would go stale, so every element is held to a
   duration near zero.  Near zero rather than `none`: transitionend and
   animationend still fire, so scripts that wait for them do not hang. */
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }

  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }

  *,
  *::before,
  *::after {
    transition-duration: .01ms !important;
    transition-delay: 0ms !important;
    animation-duration: .01ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
  }
}
