/* =========================================================================
   AI Safety India, motion.css  (v1.0, opt-in)
   The motion layer for the site and the admin app. Nothing here runs until motion.js sets
   <html data-motion="on"> (or "reduce"), so without JavaScript every page is exactly as it is today.
   Link it with motion.js (one line, see 17-motion/spec.md). Load order does not matter: every rule
   carries html[data-motion], one step more specific than site.css, admin.css and page styles.

   Rules: motion explains a change, never decorates. Only transform and opacity move (no layout shift);
   the three exceptions are named where they appear (hover shadows, row tint, accordion height, all
   user-started or paint-only). Content is visible by default. Reduce Motion gives instant or
   opacity-only changes.
   ========================================================================= */

/* ---------- tokens ---------- */
:root {
  /* durations: feedback, small open/close, larger moves, entrances */
  --dur-1: 160ms;     /* hover, press, colour, menu fade-out */
  --dur-2: 240ms;     /* menus, dropdowns, toasts out, page crossfade */
  --dur-3: 400ms;     /* sheets, dialogs, accordions, image fades */
  --dur-4: 640ms;     /* scroll reveals, the hero settle */
  --dur-spring: 560ms;/* the settle time of --ease-spring (a 0.4s, no-bounce spring) */

  /* easings */
  --ease-standard: cubic-bezier(.2, 0, 0, 1);     /* on-screen moves: starts promptly, lands softly */
  --ease-out:      cubic-bezier(.16, 1, .3, 1);   /* emphasized out: entrances and reveals (same curve as site.css --out) */
  --ease-in:       cubic-bezier(.4, 0, 1, 1);     /* exits: leaves quickly, never lingers */
  --ease-spring:      cubic-bezier(.22, 1, .36, 1);  /* fallback for browsers without linear() */
  --ease-spring-soft: cubic-bezier(.25, 1.1, .5, 1); /* fallback, a hint of overshoot */

  /* distances and scales */
  --move-1: 2px;      /* hover lift (buttons 1px, cards 2 to 4px) */
  --move-2: 8px;      /* menus, page crossfade, toasts */
  --move-3: 14px;     /* scroll reveals */
  --move-4: 24px;     /* the hero photo frame, sheets that travel further */
  --stagger: 50ms;    /* between items in a group, capped at 6 steps */
  --scale-press: .97;
  --scale-pop:   .98; /* menus open from 98% */
  --scale-img:   1.04;/* photos settle from 104% */
}
/* springs as linear() curves, sampled from a damped spring (duration 0.4s; bounce 0 and 0.15). Chrome 113+, Safari 17.2+, Firefox 112+ */
@supports (transition-timing-function: linear(0, 1)) {
  :root {
    --ease-spring: linear(0, 0.05, 0.158, 0.286, 0.413, 0.527, 0.626, 0.707, 0.774, 0.826, 0.868, 0.9, 0.925, 0.943, 0.958, 0.969, 0.977, 0.983, 0.987, 0.991, 0.993, 0.995, 0.996, 0.997, 1);
    --ease-spring-soft: linear(0, 0.028, 0.096, 0.187, 0.289, 0.392, 0.49, 0.58, 0.661, 0.731, 0.79, 0.84, 0.88, 0.913, 0.938, 0.958, 0.973, 0.985, 0.993, 0.998, 1.002, 1.004, 1.006, 1.006, 1.006, 1.006, 1.005, 1.005, 1.004, 1.004, 1.003, 1.002, 1);
  }
}

/* ---------- keyframes (shared) ---------- */
@keyframes m-rise    { from { opacity: 0; translate: 0 var(--move-3); } }
@keyframes m-settle  { from { opacity: 0; translate: 0 18px; } }
@keyframes m-frame   { from { translate: 0 var(--move-4); } }
@keyframes m-img     { from { scale: var(--scale-img); } }
@keyframes m-pop     { from { opacity: 0; translate: 0 -4px; scale: var(--scale-pop); } }
@keyframes m-fade    { from { opacity: 0; } }
@keyframes m-dialog  { from { opacity: 0; translate: 0 var(--move-2); scale: .96; } }
@keyframes m-sheet   { from { transform: translate3d(0, 100%, 0); } }
@keyframes m-toast   { from { opacity: 0; translate: 0 16px; scale: .96; } }
@keyframes m-status  { from { opacity: 0; scale: .86; } }
@keyframes m-vt-out  { to   { opacity: 0; } }
@keyframes m-vt-in   { from { opacity: 0; translate: 0 var(--move-2); } }

/* =========================================================================
   1. SCROLL REVEAL: sections, cards, stats. Fade + 14px rise, once, staggered 50ms.
   Targets the existing .rv class (site.js and motion.js both add .is-in). Groups (.m-group, set by
   motion.js) keep the box still and reveal their children one by one (--i is the child's index).
   ========================================================================= */
html[data-motion="on"] .rv { opacity: 0; transform: none; translate: 0 var(--move-3);
  transition: opacity var(--dur-4) var(--ease-out), translate var(--dur-4) var(--ease-out); transition-delay: var(--d, 0ms); }
html[data-motion="on"] .rv.is-in { opacity: 1; translate: none; }
html[data-motion="on"] .rv.m-group { opacity: 1; translate: none; transition: none; }
html[data-motion="on"] .m-group .m-item { opacity: 0; translate: 0 var(--move-3);
  transition: opacity var(--dur-4) var(--ease-out), translate var(--dur-4) var(--ease-out);
  transition-delay: calc(var(--d, 0ms) + var(--i, 0) * var(--stagger)); }
html[data-motion="on"] .m-group.is-in .m-item { opacity: 1; translate: none; }
html[data-motion] .m-reset, html[data-motion] .m-reset * { transition: none !important; }  /* Motion.replay(): back to the start state at once */

/* =========================================================================
   2. HERO SETTLE (on load, once): headline, lede, actions arrive 60ms apart; the photo frame rises
   without fading (it is the LCP image: it must paint at once) and the photo inside settles 104% to 100%.
   Targets the existing .rise / .rise-2 / .rise-3 classes (home hero, every .p-hero and .b-hero).
   ========================================================================= */
html[data-motion="on"] .rise   { animation: m-settle 760ms var(--ease-out) both; }
html[data-motion="on"] .rise-2 { animation: m-settle 760ms var(--ease-out) 60ms both; }
html[data-motion="on"] .rise-3 { animation: m-settle 760ms var(--ease-out) 120ms both; }
html[data-motion="on"] .pic.rise-3 { animation: m-frame 900ms var(--ease-out) 120ms both; }
html[data-motion="on"] .pic.rise-3 img { animation: m-img 1400ms var(--ease-out) 120ms both; }

/* =========================================================================
   3. COUNT-UP (stat numbers, .stats b): motion.js counts 0 to the number once in view, 900ms ease-out.
   The real number stays in the HTML for readers and no-JS; the counting copy is aria-hidden.
   Numbers under 10 are left alone. The cell's width never changes (grid), so no shift.
   ========================================================================= */
html[data-motion] .m-count { display: inline !important; margin: 0 !important; font: inherit !important; color: inherit !important; }  /* .stats span would restyle it */
html[data-motion] .m-count-real { position: absolute !important; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* =========================================================================
   4. IMAGE REVEAL: photos fade in and settle 104% to 100% inside their frame. The frame keeps its
   aspect ratio and overflow: hidden, so nothing outside it moves. Waits for the image to decode.
   Targets: .shot (moments rail), .feature .cover, .wk-cover, .img-card .ph (motion.js adds .m-img).
   ========================================================================= */
html[data-motion="on"] .m-img img, html[data-motion="on"] .m-img.m-self { opacity: 0; scale: var(--scale-img);
  transition: opacity var(--dur-3) var(--ease-standard), scale 1200ms var(--ease-out), transform 800ms var(--ease-out); }
html[data-motion="on"] .m-img.is-in img, html[data-motion="on"] .m-img.m-self.is-in { opacity: 1; scale: none; }
/* the scale property composes with any transform a component sets (.shot:hover img, .pic img.zoom), so nothing is overridden */

/* =========================================================================
   5. BUTTONS AND CARDS
   Buttons: hover lifts 1px with a soft shadow (pointer devices only); press scales to 97% in 80ms and
   springs back. Cards: hover lifts 3px and the shadow deepens. Shadows change paint only on the
   hovered element (named exception: no layout, one element at a time).
   ========================================================================= */
html[data-motion="on"] .btn { transition: background-color var(--dur-1) var(--ease-standard), color var(--dur-1) var(--ease-standard),
  box-shadow var(--dur-2) var(--ease-standard), translate var(--dur-3) var(--ease-spring-soft), scale var(--dur-3) var(--ease-spring-soft); }
@media (hover: hover) {
  html[data-motion="on"] .btn-primary:hover:not([disabled]) { translate: 0 -1px; box-shadow: 0 8px 18px -10px rgba(155, 28, 46, .7); }
  html[data-motion="on"] .btn-secondary:hover:not([disabled]) { translate: 0 -1px; box-shadow: inset 0 0 0 1.5px var(--ink), 0 8px 18px -12px rgba(28, 26, 23, .35); }
  html[data-motion="on"] .btn-light:hover:not([disabled]) { translate: 0 -1px; box-shadow: 0 8px 20px -10px rgba(0, 0, 0, .55); }
  html[data-motion="on"] .btn-ghost-dark:hover:not([disabled]) { translate: 0 -1px; }
}
html[data-motion="on"] .btn:active:not([disabled]):not([aria-disabled="true"]),
html[data-motion="on"] .rb:active:not([disabled]), html[data-motion="on"] .more-btn:active,
html[data-motion="on"] .ts-x:active, html[data-motion="on"] .icon-btn:active:not([disabled]) {
  transform: none; scale: var(--scale-press); transition: scale 80ms var(--ease-standard), translate 80ms var(--ease-standard), background-color 80ms var(--ease-standard); }
html[data-motion="on"] .rb, html[data-motion="on"] .more-btn, html[data-motion="on"] .ts-x, html[data-motion="on"] .icon-btn {
  transition: scale var(--dur-3) var(--ease-spring-soft), background-color var(--dur-1) var(--ease-standard), color var(--dur-1) var(--ease-standard); }

/* cards: level tiles, image cards, event cards, hoverable cards. A card that was revealed gets .m-done
   from motion.js when its reveal ends, so the hover spring never replaces the reveal's own timing. */
html[data-motion="on"] :is(.level.tile, .img-card, a.event, .card.hoverable):is(.m-done, :not(.rv)) {
  transform: none; transition: translate var(--dur-3) var(--ease-spring), scale var(--dur-3) var(--ease-spring), box-shadow var(--dur-3) var(--ease-standard); }
@media (hover: hover) {
  html[data-motion="on"] .level.tile:is(.m-done, :not(.rv)):hover { translate: 0 -3px;
    box-shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 36px 64px -32px rgba(30, 8, 12, .62); }
  html[data-motion="on"] :is(a.img-card, a.event, .card.hoverable):is(.m-done, :not(.rv)):hover {
    translate: 0 -3px; box-shadow: 0 2px 4px rgba(28,26,23,.05), 0 28px 52px -24px rgba(28,26,23,.32); }
}
html[data-motion="on"] :is(a.img-card, a.event):is(.m-done, :not(.rv)):active { scale: .99; transition-duration: 80ms; }

/* =========================================================================
   6. HEADER: flat at the top, a glass bar once the page scrolls. OPT-IN (data-motion-header on <html>)
   because RULES R45 removed the floating header; this keeps it attached to the top edge.
   No height animation: the header sticks at top: -12px, so its 80px box shows 68px once stuck,
   the bar content moves 6px to stay centred and a glass layer fades in. Above 767px only.
   ========================================================================= */
@media (min-width: 768px) {
  html[data-motion][data-motion-header] .top { position: sticky; top: -12px; }
  html[data-motion][data-motion-header] .top::before { content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none; opacity: 0;
    background: var(--glass); backdrop-filter: var(--blur); -webkit-backdrop-filter: var(--blur); box-shadow: 0 1px 0 rgba(28,26,23,.07);
    transition: opacity var(--dur-3) var(--ease-standard); }
  html[data-motion][data-motion-header] body.hdr-dark .top::before { background: var(--glass-dark); box-shadow: 0 1px 0 rgba(255,255,255,.08); }
  html[data-motion][data-motion-header] .top .bar { transition: translate var(--dur-3) var(--ease-spring); }
  html[data-motion][data-motion-header] .top.m-stuck::before { opacity: 1; }
  html[data-motion][data-motion-header] .top.m-stuck .bar { translate: 0 6px; }
  html[data-motion][data-motion-header] .top.m-stuck .bar .brand img { scale: .94; transform-origin: left center; }
  html[data-motion][data-motion-header] .top .bar .brand img { transition: scale var(--dur-3) var(--ease-spring); }
}

/* =========================================================================
   7. MENUS AND DROPDOWNS: open from the trigger (98% to 100% + fade, 240ms). motion.js sets the origin
   (--m-ox) from the trigger's centre. Targets .drop, #sheet (.sheet), details.cal panels, admin .pop.
   Closing is instant (as on iOS menus) until site.js closes through Motion.hide().
   ========================================================================= */
html[data-motion="on"] .drop:not([hidden]), html[data-motion="on"] .top .sheet:not([hidden]), html[data-motion="on"] .pop {
  animation: m-pop var(--dur-2) var(--ease-out) both; transform-origin: var(--m-ox, 24px) 0; }
html[data-motion="on"] details.cal[open] > :not(summary) { animation: m-pop var(--dur-2) var(--ease-out) both; transform-origin: 24px 0; }
html[data-motion="on"] .m-leaving { animation: m-vt-out var(--dur-1) var(--ease-in) both !important; pointer-events: none; }
html[data-motion="on"] .menu .chev, html[data-motion="on"] .sheet .chev { transition: transform var(--dur-3) var(--ease-spring); }

/* =========================================================================
   8. SHEETS, DIALOGS, ACCORDIONS
   Tab-bar sheets (.tsheet) rise on a spring; they leave on --ease-in in 280ms (site.js waits 280ms).
   A released drag springs back. Dialogs (.dlg, dialog.modal) grow from 96% with a fade.
   Accordions (footer .fcol, FAQ .accordion details, menu sheet details): motion.js animates the
   height (named exception: user-started, so it is not counted as layout shift; content fades in).
   ========================================================================= */
@media (max-width: 767px) {
  html[data-motion="on"] .tsheet { transition: transform var(--dur-spring) var(--ease-spring); }
  html[data-motion="on"] .tsheet[open] { animation: m-sheet var(--dur-spring) var(--ease-spring); }
  html[data-motion="on"] .tsheet[open]::backdrop { animation: m-fade var(--dur-3) var(--ease-standard); }
  html[data-motion="on"] .tsheet.ts-out { transform: translate3d(0, 100%, 0); transition: transform 280ms var(--ease-in); }
  html[data-motion="on"] .tsheet.ts-out::backdrop { opacity: 0; transition: opacity 280ms var(--ease-in); }
  html[data-motion="on"] .tsheet[open] .ts-list { animation: m-rise var(--dur-4) var(--ease-out) 80ms both; }
}
html[data-motion="on"] dialog.dlg[open], html[data-motion="on"] dialog.modal[open] { animation: m-dialog var(--dur-3) var(--ease-spring) both; }
html[data-motion="on"] dialog.dlg[open]::backdrop, html[data-motion="on"] dialog.modal[open]::backdrop { animation: m-fade var(--dur-3) var(--ease-standard); }
html[data-motion] .m-h { overflow: hidden; }
html[data-motion] .fcol > ul.m-closing { display: block !important; }
html[data-motion] .accordion details.m-closing-d summary::after { transform: rotate(45deg) translate(-3px,-3px); }
html[data-motion] .sheet details.m-closing-d .chev { transform: none; }
html[data-motion="on"] .m-h-in > :not(summary) { animation: m-fade var(--dur-3) var(--ease-standard) both; }
html[data-motion="on"] .fcol .fh-c, html[data-motion="on"] .accordion summary::after { transition: transform var(--dur-3) var(--ease-spring); }

/* =========================================================================
   9. PAGE TRANSITIONS (View Transitions, cross-document; Chrome 126+, Safari 18.2+; others just load).
   A soft crossfade: the old page fades out in 240ms, the new one fades in and rises 8px in 400ms.
   The header and the phone tab bar have their own names, so they stay put between pages.
   ========================================================================= */
@view-transition { navigation: auto; }
html[data-motion="on"] .top { view-transition-name: m-header; }
html[data-motion="on"] .tabbar { view-transition-name: m-tabbar; }
html[data-motion="on"]::view-transition-old(root) { animation: m-vt-out var(--dur-2) var(--ease-in) both; }
html[data-motion="on"]::view-transition-new(root) { animation: m-vt-in var(--dur-3) var(--ease-out) 60ms both; }
html[data-motion="on"]::view-transition-group(m-header), html[data-motion="on"]::view-transition-group(m-tabbar) { animation-duration: var(--dur-2); animation-timing-function: var(--ease-standard); }

/* =========================================================================
   10. ADMIN: the applicant panel slides in from the right on a no-bounce spring (an overshoot would
   open a gap at the screen edge) and leaves on --ease-in. Rows: a status change pops the status
   word and tints the row once; rows that move slide to their new place (FLIP, transform only);
   new rows fade in. Toasts rise on a soft spring. Targets: .app > .insp, table.grid tbody tr, .toast.
   ========================================================================= */
html[data-motion="on"]:not(.is-phone) .app > .insp {
  transition: transform var(--dur-2) var(--ease-in), width var(--dur-2) var(--ease-standard), visibility 0s linear var(--dur-2); }
html[data-motion="on"]:not(.is-phone) .app.insp-open > .insp {
  transition: transform var(--dur-spring) var(--ease-spring), width var(--dur-2) var(--ease-standard), visibility 0s; }
html[data-motion="on"] .grid tbody tr.m-changed .st { animation: m-status var(--dur-3) var(--ease-spring-soft) both; }
html[data-motion="on"] .grid tbody tr.m-changed td { animation: m-row-tint 1200ms var(--ease-standard); }  /* paint only, one row */
@keyframes m-row-tint { from { background-color: color-mix(in srgb, var(--info, #2F5B8A) 10%, var(--card)); } }
html[data-motion="on"] .grid tbody tr.m-new td { animation: m-fade var(--dur-3) var(--ease-standard) both; }
html[data-motion="on"] .toast { animation: m-toast var(--dur-spring) var(--ease-spring-soft) both; }

/* =========================================================================
   REDUCE MOTION. motion.js sets data-motion="reduce" when the system asks (and follows changes).
   Nothing waits to be revealed, nothing moves; opening and closing become short fades; pages crossfade.
   ========================================================================= */
html[data-motion="reduce"] .rv, html[data-motion="reduce"] .m-item, html[data-motion="reduce"] .m-img img, html[data-motion="reduce"] .m-img.m-self {
  opacity: 1 !important; translate: none !important; scale: none !important; transition: none !important; }
html[data-motion="reduce"] .rise, html[data-motion="reduce"] .rise-2, html[data-motion="reduce"] .rise-3, html[data-motion="reduce"] .pic.rise-3 img { animation: none !important; }
html[data-motion="reduce"] .btn, html[data-motion="reduce"] .level.tile, html[data-motion="reduce"] .img-card, html[data-motion="reduce"] .event, html[data-motion="reduce"] .card { translate: none !important; scale: none !important; }
html[data-motion="reduce"] .drop:not([hidden]), html[data-motion="reduce"] .top .sheet:not([hidden]), html[data-motion="reduce"] .pop,
html[data-motion="reduce"] details.cal[open] > :not(summary), html[data-motion="reduce"] dialog.dlg[open], html[data-motion="reduce"] dialog.modal[open],
html[data-motion="reduce"] .toast { animation: m-fade var(--dur-1) linear both !important; }
html[data-motion="reduce"] .tsheet[open] { animation: m-fade var(--dur-2) linear !important; }
html[data-motion="reduce"] .tsheet.ts-out { transform: none !important; opacity: 0; transition: opacity var(--dur-1) linear !important; }
html[data-motion="reduce"] .grid tbody tr.m-changed .st, html[data-motion="reduce"] .grid tbody tr.m-new td { animation: none; }
html[data-motion="reduce"][data-motion-header] .top .bar, html[data-motion="reduce"][data-motion-header] .top .bar .brand img { translate: none !important; scale: none !important; }
html[data-motion="reduce"]::view-transition-old(root) { animation: m-vt-out var(--dur-1) linear both; }
html[data-motion="reduce"]::view-transition-new(root) { animation: m-fade var(--dur-1) linear both; }
html[data-motion="reduce"]::view-transition-group(*) { animation: none; }
