/* ============================================================================
 * motion.css — the site's shared motion layer.
 *
 * Loaded last, after i18n.css and system-refinements.css, so it can layer
 * transitions on top of whatever those files paint. It is deliberately a
 * separate file: motion is the first thing you want to tune or switch off,
 * and keeping it here means you can do that without touching layout CSS.
 *
 * Two rules governed every choice below:
 *
 *  1. NEVER put a `transform` on a long-lived ancestor of a fixed-position
 *     child. A transformed element becomes the containing block for its
 *     `position: fixed` descendants, which is what once trapped the nav
 *     drawer and the modals inside the (tall) page shell. Entrance keyframes
 *     here always END at `transform: none`, and `.system-page-shell`,
 *     `.system-sidebar` and `.system-form-panel` are never transformed.
 *
 *  2. Only animate cheap properties — opacity, transform, colour, shadow.
 *     Nothing here animates width/height/top/left, so no animation can
 *     reflow the page or fight the inline positions that JS writes onto the
 *     portalled menus.
 * ========================================================================= */

:root {
  /* Durations. Interactive feedback is near-instant; entrances are slower. */
  --motion-instant: 90ms;
  --motion-fast: 140ms;
  --motion-base: 220ms;
  --motion-slow: 320ms;

  /* Easings. `out` for things arriving, `in-out` for things moving in place,
   * `spring` for a small amount of overshoot on press/pop. */
  --motion-ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);
  --motion-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --motion-ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1);
}

/* ---------------------------------------------------------------------------
 * 1) Interactive feedback
 *
 * One shared transition for anything clickable or typable. Colour, border,
 * shadow and opacity only — plus `transform` so the press effect below has
 * something to ease.
 * ------------------------------------------------------------------------ */
a,
button,
input,
select,
textarea,
label[for],
summary,
[role="button"],
[data-open-modal],
[data-open-editor],
[data-close-modal],
.system-table-button,
.system-quick-button,
.system-icon-button,
.system-ghost-button,
.system-sidebar-link,
.site-nav-link,
.site-nav-cta,
.system-member-toolbar__action,
.system-member-plus-menu__item,
.system-topbar-nav__button,
.system-topbar-nav__menu a,
.system-topbar-nav__lang {
  transition:
    background-color var(--motion-fast) var(--motion-ease-out),
    border-color var(--motion-fast) var(--motion-ease-out),
    color var(--motion-fast) var(--motion-ease-out),
    box-shadow var(--motion-base) var(--motion-ease-out),
    opacity var(--motion-fast) var(--motion-ease-out),
    transform var(--motion-fast) var(--motion-ease-out);
}

/* Press feedback: a small, quick dip. Applied to buttons only — links and
 * inputs keep their own behaviour. */
button:not(:disabled):active,
[role="button"]:not([aria-disabled="true"]):active,
.system-table-button:not(:disabled):active,
.system-quick-button:not(:disabled):active,
.system-icon-button:not(:disabled):active {
  transform: scale(0.96);
  transition-duration: var(--motion-instant);
}

/* Lift the primary actions slightly on hover (pointer devices only, so this
 * never sticks on a touchscreen after a tap). */
@media (hover: hover) and (pointer: fine) {
  .system-table-button--solid:not(:disabled):hover,
  .system-quick-button:not(:disabled):hover,
  .site-nav-cta:hover,
  .system-member-toolbar__action:not(:disabled):hover {
    transform: translateY(-1px);
  }
}

/* Focus rings fade in rather than snapping. */
:focus-visible {
  transition: box-shadow var(--motion-fast) var(--motion-ease-out),
              outline-color var(--motion-fast) var(--motion-ease-out);
}

/* ---------------------------------------------------------------------------
 * 2) Surfaces
 *
 * Cards and table rows react to hover, and every surface eases its colours
 * so the dark-mode switch is a fade instead of a hard cut.
 * ------------------------------------------------------------------------ */
body,
.system-screen-layout,
.system-page-shell,
.system-sidebar,
.system-form-dialog,
.system-table-shell,
.system-report-card,
.system-safe-module,
.system-expense-module,
.system-data-table tbody tr,
.system-member-plus-menu,
.system-topbar-nav__menu {
  transition:
    background-color var(--motion-base) var(--motion-ease-in-out),
    border-color var(--motion-base) var(--motion-ease-in-out),
    color var(--motion-base) var(--motion-ease-in-out);
}

@media (hover: hover) and (pointer: fine) {
  .system-data-table tbody tr:hover {
    background-color: rgba(255, 122, 47, 0.045);
  }
  html.dark .system-data-table tbody tr:hover {
    background-color: rgba(255, 122, 47, 0.09);
  }
}

/* ---------------------------------------------------------------------------
 * 3) Dropdown menus
 *
 * These are toggled with the `hidden` attribute, so a transition would never
 * run (there is no frame at `display: none` to start from). A keyframe does
 * run the moment the element is displayed, so entrances use animation.
 *
 * `transform-origin` follows the writing direction so a menu grows out of
 * the corner nearest its trigger.
 * ------------------------------------------------------------------------ */
/* NOTE ON FILL MODE
 * ----------------
 * Every entrance below uses `backwards`, never `both`.
 *
 * `both` holds the LAST keyframe after the animation ends — which is correct
 * only if the animation actually runs. If the animation clock never advances
 * (a throttled background tab, an offscreen iframe, a browser that skipped
 * the animation), `both` also holds the FIRST keyframe, and the element stays
 * at `opacity: 0` forever — an invisible dialog.
 *
 * `backwards` applies the from-state only during the delay and the active
 * period. Outside that, the element falls back to its normal styles, so the
 * worst case is simply "no entrance animation" rather than "invisible".
 * Every `to` frame here already matches the element's natural state
 * (opacity 1, transform none), so nothing is lost by not holding it.
 */

@keyframes motion-menu-in {
  from { opacity: 0; transform: translateY(-6px) scale(0.98); }
  to   { opacity: 1; transform: none; }
}

.system-member-plus-menu:not([hidden]),
.system-topbar-nav__menu:not([hidden]),
.system-access-menu:not([hidden]),
.system-course-print-menu:not([hidden]),
.lang-dropdown-menu:not(.hidden) {
  animation: motion-menu-in var(--motion-fast) var(--motion-ease-out) backwards;
  transform-origin: top var(--motion-menu-origin, left);
}

html[dir="rtl"] .system-member-plus-menu:not([hidden]),
html[dir="rtl"] .system-topbar-nav__menu:not([hidden]),
html[dir="rtl"] .system-access-menu:not([hidden]),
html[dir="rtl"] .system-course-print-menu:not([hidden]),
html[dir="rtl"] .lang-dropdown-menu:not(.hidden) {
  transform-origin: top right;
}

/* Menu items nudge toward the reading edge on hover. Done with `transform`,
 * not padding — padding would reflow the menu on every pointer move. */
@media (hover: hover) and (pointer: fine) {
  .system-member-plus-menu__item {
    transition: background-color var(--motion-fast) var(--motion-ease-out),
                transform var(--motion-fast) var(--motion-ease-out);
  }
  .system-member-plus-menu__item:hover {
    transform: translateX(2px);
  }
  html[dir="rtl"] .system-member-plus-menu__item:hover {
    transform: translateX(-2px);
  }
}

/* ---------------------------------------------------------------------------
 * 4) Dialogs
 *
 * The backdrop fades; the dialog rises a few pixels. The keyframe finishes
 * at `transform: none` so the dialog is NOT a containing block once it has
 * settled — the access-type and member menus are portalled to <body> and
 * positioned with viewport coordinates, and a lingering transform on an
 * ancestor would re-anchor them.
 * ------------------------------------------------------------------------ */
@keyframes motion-dialog-in {
  from { opacity: 0; transform: translateY(10px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}

@keyframes motion-backdrop-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.system-form-panel.is-open > .system-form-dialog,
.system-form-panel.is-visible > .system-form-dialog {
  animation: motion-dialog-in var(--motion-base) var(--motion-ease-out) backwards;
}

.system-form-panel.is-open,
.system-form-panel.is-visible {
  animation: motion-backdrop-in var(--motion-fast) linear backwards;
}

/* The drawer backdrop on the system pages. */
.system-sidebar-backdrop {
  transition: opacity var(--motion-base) var(--motion-ease-out);
}

/* ---------------------------------------------------------------------------
 * 5) Chevrons and toggles
 * ------------------------------------------------------------------------ */
[aria-expanded] > .material-symbols-outlined,
.system-sidebar-link .material-symbols-outlined:last-child,
[data-system-branch-toggle] .material-symbols-outlined {
  transition: transform var(--motion-base) var(--motion-ease-out);
}

[aria-expanded="true"] > .material-symbols-outlined:last-child {
  transform: rotate(180deg);
}

/* The theme toggle's icon swaps with a quarter turn. */
[data-theme-toggle] .material-symbols-outlined {
  transition: transform var(--motion-slow) var(--motion-ease-spring),
              opacity var(--motion-fast) var(--motion-ease-out);
}
[data-theme-toggle]:hover .material-symbols-outlined {
  transform: rotate(-20deg);
}

/* ---------------------------------------------------------------------------
 * 6) Content arriving
 *
 * Rows and cards that JS renders fade up in a short stagger. Kept to two
 * frames and a small distance so long lists never feel slow.
 * ------------------------------------------------------------------------ */
@keyframes motion-rise-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

.system-panel-scroll > section:not(.system-form-panel):not([data-system-modal]) {
  animation: motion-rise-in var(--motion-slow) var(--motion-ease-out) backwards;
}
.system-panel-scroll > section:not(.system-form-panel):nth-child(2) { animation-delay: 40ms; }
.system-panel-scroll > section:not(.system-form-panel):nth-child(3) { animation-delay: 80ms; }
.system-panel-scroll > section:not(.system-form-panel):nth-child(4) { animation-delay: 120ms; }
.system-panel-scroll > section:not(.system-form-panel):nth-child(n + 5) { animation-delay: 160ms; }

/* ---------------------------------------------------------------------------
 * 7) Reduced motion
 *
 * A blanket switch-off. Anyone who has asked their OS for less motion gets
 * an instant, static interface — including every animation defined in the
 * other stylesheets, not just the ones above.
 * ------------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 1ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }
  /* Press/hover displacement is removed outright rather than shortened. */
  button:active,
  [role="button"]:active,
  .system-table-button:active,
  .system-quick-button:active,
  .system-icon-button:active,
  .system-table-button--solid:hover,
  .system-quick-button:hover,
  .site-nav-cta:hover,
  [data-theme-toggle]:hover .material-symbols-outlined {
    transform: none !important;
  }
}

/* Printing must never wait on, or capture, a half-finished animation. */
@media print {
  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
  }
}

/* ---------------------------------------------------------------------------
 * 8) Demo-request form status
 *
 * One live region that reports sending / sent / failed. It fades in rather
 * than appearing abruptly, because it is announced to screen readers at the
 * same moment and a hard flash next to a form field reads as an error even
 * when the message is a success.
 * ------------------------------------------------------------------------ */
.demo-form-status {
  margin: 0;
  padding: 0.7rem 0.95rem;
  border-radius: 0.75rem;
  font-size: 0.9rem;
  line-height: 1.45;
  border: 1px solid transparent;
  animation: motion-rise-in var(--motion-base) var(--motion-ease-out) backwards;
}
.demo-form-status[hidden] { display: none; }

.demo-form-status[data-state="pending"] {
  background: #f1f5f9;
  border-color: #e2e8f0;
  color: #475569;
}
.demo-form-status[data-state="success"] {
  background: #ecfdf5;
  border-color: #a7f3d0;
  color: #065f46;
}
.demo-form-status[data-state="error"] {
  background: #fef2f2;
  border-color: #fecaca;
  color: #b42318;
}

html.dark .demo-form-status[data-state="pending"] { background: #1e293b; border-color: #334155; color: #cbd5e1; }
html.dark .demo-form-status[data-state="success"] { background: #052e21; border-color: #065f46; color: #6ee7b7; }
html.dark .demo-form-status[data-state="error"]   { background: #3f1212; border-color: #7f1d1d; color: #fca5a5; }

/* The submit button while a request is in flight. */
#demo-form button[aria-busy="true"] {
  opacity: 0.72;
  cursor: progress;
}

/* Fields the server rejected. Ring + tint rather than colour alone, so it
 * still reads for anyone who can't distinguish the red. */
#demo-form [aria-invalid="true"] {
  box-shadow: 0 0 0 2px #dc2626 !important;
  background-color: #fef2f2 !important;
}
html.dark #demo-form [aria-invalid="true"] {
  background-color: #3f1212 !important;
}

/* ---------------------------------------------------------------------------
 * 9) Demo form — inline field errors
 *
 * Replaces the browser's validation bubbles, which are always in the
 * browser's language rather than the site's and disappear after a moment.
 * Each message sits under its field with a warning glyph, and stays until
 * the field is fixed. `margin-inline-start` keeps the indent correct in RTL.
 * ------------------------------------------------------------------------ */
.demo-field-error {
  display: flex;
  align-items: flex-start;
  gap: 0.4rem;
  margin: 0.45rem 0 0;
  margin-inline-start: 0.25rem;
  color: #b42318;
  font-size: 0.82rem;
  font-weight: 500;
  line-height: 1.4;
  animation: motion-rise-in var(--motion-fast) var(--motion-ease-out) backwards;
}
.demo-field-error[hidden] { display: none; }
.demo-field-error::before {
  content: "!";
  flex: 0 0 auto;
  display: inline-grid;
  place-items: center;
  width: 1.05rem;
  height: 1.05rem;
  margin-top: 0.05rem;
  border-radius: 999px;
  background: #dc2626;
  color: #ffffff;
  font-size: 0.7rem;
  font-weight: 800;
  line-height: 1;
}
html.dark .demo-field-error { color: #fca5a5; }

/* The field itself: a ring plus a tint, so the state doesn't rely on colour
 * alone. Outline, not box-shadow, so no transition can leave it half drawn. */
#demo-form [aria-invalid="true"] {
  outline: 2px solid #dc2626 !important;
  outline-offset: 0 !important;
  box-shadow: none !important;
  background-color: #fef2f2 !important;
}
#demo-form [aria-invalid="true"]:focus {
  outline-width: 2px !important;
}
html.dark #demo-form [aria-invalid="true"] { background-color: #3f1212 !important; }
