/* ============================================================================================================
   TTA MOTION + INTERACTION SYSTEM (2026-11-13). One shared vocabulary for every page (public, dashboard, pages
   inside the dashboard). NOT loaded in the test room (no motion while a student answers questions).

   The 20 rules it encodes, and where each one lives:
     1 squash & stretch ........ press feedback: `scale` .97 on :active / .is-pressed (does not touch `transform`)
     2 anticipation ............ .m-go (arrow pulls back 2px before it moves forward on hover)
     3 staging / one focus ..... .m-stage (hovering one card quiets its siblings) + "one filled button per card"
     4 pose to pose ............ keyframes hold only key poses; the browser fills the frames between
     5 follow through .......... --m-spring easing overshoots then settles (.m-pop, .m-in-spring)
     6 slow in / slow out ...... --m-out / --m-inout; nothing moves at a constant speed (no `linear`)
     7 arcs .................... .m-arc (enters on a curve: translate + slight rotate that unwinds)
     8 secondary action ........ .m-pop + .m-spark ring behind a success moment
     9 snappy timing ........... every duration token is under 300ms
    10 exaggerated feedback .... .m-shake (errors), .m-pop (success)
    11 depth ................... .m-lift (hover raise + shadow), --m-shadow-1/2
    12 appeal .................. consistent radius / shadow / focus ring tokens
    13 Fitts .................. primary actions full width on phones (.m-cta)
    14 bigger hit areas ....... .m-hit (44px invisible tap zone around a small control)
    15 Hick ................... one primary choice per group (component rule, see .m-quiet)
    16 Miller ................. .m-chunk groups (max ~7 visible, rest behind "show more")
    17 Doherty ................ .is-pressed is applied on pointerdown (under 100ms); .m-busy shows work at once
    18 Postel ................. inputs: see TTAMotion.cleanInput in tta-motion.js
    19 Jakob .................. standard patterns only (bottom sheet, swipe rows, pull-to-nothing)
    20 frequency of use ....... [data-m-freq="high"] and navigation never animate longer than --m-instant

   Rules for anyone using this file:
     - Animate only `opacity`, `transform`, `scale`, `translate`, `rotate`, `box-shadow`, `background-color`.
       Never animate width / height / top / left (layout jank on phones).
     - Content is visible without JavaScript. Hidden-until-revealed states exist only under `html.m-js`.
     - Everything is switched off under `prefers-reduced-motion: reduce`.
     - This file sets NO colours except the focus ring, so it cannot break the contrast rule.
   ============================================================================================================ */
:root {
  --m-instant: 90ms;
  --m-fast: 140ms;
  --m-base: 200ms;
  --m-slow: 280ms;                                   /* ceiling: UI motion stays under 300ms */
  --m-out: cubic-bezier(.22, 1, .36, 1);             /* fast start, soft landing (enter, hover) */
  --m-in: cubic-bezier(.55, 0, 1, .45);              /* soft start, fast exit (leave) */
  --m-inout: cubic-bezier(.65, 0, .35, 1);           /* moves on screen */
  --m-spring: cubic-bezier(.34, 1.56, .64, 1);       /* overshoot then settle (follow through) */
  --m-shadow-1: 0 1px 2px rgba(10, 35, 66, .08), 0 2px 8px rgba(10, 35, 66, .06);
  --m-shadow-2: 0 2px 4px rgba(10, 35, 66, .08), 0 12px 28px rgba(10, 35, 66, .14);
  --m-ring: #B89B2E;
}
html[data-theme="dark"] {
  --m-shadow-1: 0 1px 2px rgba(0, 0, 0, .35), 0 2px 8px rgba(0, 0, 0, .25);
  --m-shadow-2: 0 2px 4px rgba(0, 0, 0, .4), 0 14px 32px rgba(0, 0, 0, .45);
  --m-ring: #F0D060;
}

/* ---- 1 + 17: press feedback. `scale` is its own property, so existing `transform` rules keep working. -------- */
.m-press, .m-cta, .tc-btn, .d-btn, .pz-btn, .ip-resume, .adaptive-start {
  transition: scale var(--m-instant) var(--m-out);
  -webkit-tap-highlight-color: transparent;
}
.m-press:active, .m-press.is-pressed,
.m-cta:active, .m-cta.is-pressed,
.tc-btn:active, .tc-btn.is-pressed,
.d-btn:active, .d-btn.is-pressed,
.pz-btn:active, .pz-btn.is-pressed,
.ip-resume:active, .ip-resume.is-pressed,
.adaptive-start:active, .adaptive-start.is-pressed { scale: .97; }

/* ---- 2: anticipation. Put .m-go on a link/button whose label ends in an arrow wrapped in .m-go-arrow. --------- */
.m-go .m-go-arrow { display: inline-block; transition: translate var(--m-base) var(--m-spring); }
.m-go:hover .m-go-arrow, .m-go:focus-visible .m-go-arrow { animation: m-go var(--m-slow) var(--m-out) both; }
@keyframes m-go { 0% { translate: 0 0; } 30% { translate: -2px 0; } 100% { translate: 4px 0; } }

/* ---- 3: staging. Hovering one item in a .m-stage group quiets the others (pointer devices only). -------------- */
@media (hover: hover) and (pointer: fine) {
  .m-stage > * { transition: opacity var(--m-base) var(--m-out); }
  .m-stage:hover > *:not(:hover):not(:focus-within) { opacity: .72; }
}

/* ---- 5 + 7 + 9: entrances. Visible by default; only hidden-then-revealed when JS is running (html.m-js). ------ */
html.m-js .m-reveal { opacity: 0; translate: 0 10px; }
html.m-js .m-reveal.is-in {
  opacity: 1; translate: 0 0;
  transition: opacity var(--m-slow) var(--m-out), translate var(--m-slow) var(--m-out);
  transition-delay: calc(var(--m-i, 0) * 45ms);
}
html.m-js .m-arc { opacity: 0; translate: -8px 12px; rotate: -1.5deg; }
html.m-js .m-arc.is-in {
  opacity: 1; translate: 0 0; rotate: 0deg;
  transition: opacity var(--m-slow) var(--m-out), translate var(--m-slow) var(--m-out), rotate var(--m-slow) var(--m-out);
  transition-delay: calc(var(--m-i, 0) * 45ms);
}
.m-in-spring { animation: m-in-spring var(--m-slow) var(--m-spring) both; }
@keyframes m-in-spring { from { opacity: 0; scale: .94; } to { opacity: 1; scale: 1; } }

/* ---- 8 + 10: success and error moments. Add the class from JS (TTAMotion.pop / .shake); it removes itself. ---- */
.m-pop { animation: m-pop var(--m-slow) var(--m-spring) both; }
@keyframes m-pop { 0% { scale: 1; } 40% { scale: 1.08; } 100% { scale: 1; } }
.m-spark { position: relative; }
.m-spark::after {
  content: ''; position: absolute; inset: -4px; border-radius: inherit; pointer-events: none;
  border: 2px solid var(--m-ring); opacity: 0;
  animation: m-spark 420ms var(--m-out) both;
}
@keyframes m-spark { 0% { opacity: .9; scale: .96; } 100% { opacity: 0; scale: 1.12; } }
.m-shake { animation: m-shake var(--m-slow) var(--m-inout) both; }
@keyframes m-shake { 0%, 100% { translate: 0 0; } 20% { translate: -6px 0; } 45% { translate: 5px 0; } 70% { translate: -3px 0; } 88% { translate: 2px 0; } }

/* ---- 11: depth. --------------------------------------------------------------------------------------------- */
.m-lift { transition: translate var(--m-base) var(--m-out), box-shadow var(--m-base) var(--m-out); }
@media (hover: hover) and (pointer: fine) {
  .m-lift:hover, .m-lift:focus-within { translate: 0 -3px; box-shadow: var(--m-shadow-2); }
}

/* ---- 13 + 14: targets. ---------------------------------------------------------------------------------------- */
.m-hit { position: relative; }
.m-hit::before { content: ''; position: absolute; top: 50%; left: 50%; width: max(100%, 44px); height: max(100%, 44px); translate: -50% -50%; }
@media (max-width: 640px) {
  .m-cta { display: flex; align-items: center; justify-content: center; width: 100%; min-height: 48px; }
}

/* ---- 15: one loud choice. A group marked .m-one keeps a single filled action; the rest carry .m-quiet. -------- */
.m-quiet { background: transparent !important; box-shadow: none !important; }

/* ---- 17: instant acknowledgement of work. --------------------------------------------------------------------- */
.m-busy { position: relative; pointer-events: none; }
.m-busy::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 2px; border-radius: 2px;
  background: currentColor; opacity: .55; transform-origin: 0 50%;
  animation: m-busy 900ms var(--m-inout) infinite;
}
@keyframes m-busy { 0% { transform: scaleX(.05); } 60% { transform: scaleX(.8); } 100% { transform: scaleX(1); opacity: 0; } }

/* ---- 20: frequency of use. Things touched many times a minute do not perform. --------------------------------- */
[data-m-freq="high"], [data-m-freq="high"] * { transition-duration: var(--m-instant) !important; animation-duration: var(--m-instant) !important; }

/* ---- focus ring (12): one ring everywhere it is not already styled. ------------------------------------------- */
.m-press:focus-visible, .m-cta:focus-visible, .m-lift:focus-visible { outline: 2px solid var(--m-ring); outline-offset: 2px; }

/* ---- accessibility: no motion when the device asks for none. --------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html.m-js .m-reveal, html.m-js .m-arc { opacity: 1 !important; translate: none !important; rotate: none !important; transition: none !important; }
  .m-pop, .m-shake, .m-in-spring, .m-spark::after, .m-busy::after, .m-go:hover .m-go-arrow { animation: none !important; }
  .m-lift, .m-press, .m-cta, .tc-btn, .d-btn, .pz-btn, .ip-resume, .adaptive-start, .m-stage > * { transition: none !important; }
  .m-lift:hover { translate: none; }
  .m-press:active, .m-cta:active { scale: 1; }
}

/* ============================================================================================================
   BATCH 2 ADDITIONS (2026-11-14) - laws 21-26, shared form/one-action rules, chart tokens.
   Folded in from the September `tta-ux-laws` layer where it already had the idea (stepper, More fold, 16px
   fields); that file stays on the sign-up page only and is not to be included anywhere new.
   ============================================================================================================ */
:root {
  --m-gold-text: #7A6619;                 /* gold as TEXT on light surfaces (5.6:1 on white) */
  --m-dim: rgba(6, 14, 28, .55);
  /* Chart colours. Data = navy + gold. One gold ramp for "how much" (light to dark). Status colours are never
     used alone: always with a word or an icon. Navy against gold stays distinct for red-green colour blindness. */
  --viz-a: #0A2342; --viz-b: #B8891A; --viz-muted: #94A3B8; --viz-grid: #E2E8F0; --viz-ink: #334155;
  --viz-ramp-1: #F6EBC4; --viz-ramp-2: #EBD27A; --viz-ramp-3: #D4AF37; --viz-ramp-4: #A07F1C; --viz-ramp-5: #5E4E12;
  --viz-ok: #047857; --viz-warn: #A3480A; --viz-bad: #A40E1E;
}
html[data-theme="dark"] {
  --m-gold-text: #F0D060;
  --viz-a: #9DB7DA; --viz-b: #F0D060; --viz-muted: #8FA3BC; --viz-grid: #284663; --viz-ink: #B8C6D8;
  --viz-ramp-1: #3A4A2A; --viz-ramp-2: #7A6619; --viz-ramp-3: #B89B2E; --viz-ramp-4: #D4AF37; --viz-ramp-5: #F0D060;
  --viz-ok: #34D399; --viz-warn: #FBBF24; --viz-bad: #FF8A80;
}

/* ---- 21 closure: a swipe row whose next card peeks in. No JS. ---------------------------------------------- */
.m-peek { display: flex; gap: 12px; overflow-x: auto; scroll-snap-type: x proximity; scroll-padding-inline: 16px; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
.m-peek::-webkit-scrollbar { display: none; }
.m-peek > * { flex: 0 0 auto; scroll-snap-align: start; min-width: 0; }
@media (max-width: 640px) { .m-peek > * { flex: 0 0 78%; } .m-peek.m-peek--sm > * { flex: 0 0 62%; } }

/* ---- 22 figure-ground: one dim layer. TTAMotion.dim(true, onClose) adds it and locks scroll; dim(false) always
        removes it. Never leave it on (the gray-screen bug was a dim that was never lifted). ---------------------- */
.m-dim { position: fixed; inset: 0; z-index: 1040; background: var(--m-dim); opacity: 0; transition: opacity var(--m-base) var(--m-out); }
.m-dim.is-on { opacity: 1; }
html.m-dim-lock, html.m-dim-lock body { overflow: hidden; }

/* ---- 25 connectedness: steps joined by a line. <ol class="m-steps"><li class="is-done">...<li class="is-active">... */
.m-steps { display: flex; list-style: none; margin: 0; padding: 0; gap: 0; counter-reset: m-step; }
.m-steps > li { flex: 1 1 0; min-width: 0; position: relative; padding-top: 34px; text-align: center; font-size: 13px; font-weight: 600; color: inherit; opacity: .72; counter-increment: m-step; }
.m-steps > li::before { content: counter(m-step); position: absolute; top: 0; left: 50%; translate: -50% 0; width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center; font-size: 12px; font-weight: 700; background: var(--viz-grid); color: #0A2342; z-index: 1; }
.m-steps > li::after { content: ''; position: absolute; top: 11px; left: -50%; width: 100%; height: 4px; background: var(--viz-grid); }
.m-steps > li:first-child::after { display: none; }
.m-steps > li.is-done, .m-steps > li.is-active { opacity: 1; }
.m-steps > li.is-done::before, .m-steps > li.is-active::before { background: #D4AF37; color: #0A2342; }
.m-steps > li.is-done::after, .m-steps > li.is-active::after { background: #D4AF37; }
.m-steps > li.is-done::before { content: '\2713'; }

/* ---- 26 choice overload: fold the rest behind "More". Nothing is removed. TTAMotion.more(root, keep) builds it. */
details.m-more > summary { list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; min-height: 44px; padding: 0 12px; border-radius: 10px; font-weight: 600; font-size: 14px; color: inherit; }
details.m-more > summary::-webkit-details-marker { display: none; }
details.m-more > summary::after { content: ''; width: 7px; height: 7px; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; rotate: 45deg; translate: 0 -2px; transition: rotate var(--m-fast) var(--m-out); }
details.m-more[open] > summary::after { rotate: 225deg; translate: 0 2px; }
details.m-more > summary:focus-visible { outline: 2px solid var(--m-ring); outline-offset: 2px; }

/* ---- 23 banner blindness: our own call to action must read as part of the page, not as an ad box. ------------ */
.m-brand-cta { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 44px; padding: 0 16px; border-radius: 10px; background: #D4AF37; color: #0A2342; font-weight: 700; text-decoration: none; border: 0; box-shadow: none; background-image: none; }
.m-brand-cta:hover { background: #C9A227; color: #0A2342; text-decoration: none; }

/* ---- 3 + 15: one loud action per group. Mark the keeper with data-m-primary; other filled buttons go quiet. --- */
.m-one :is(.m-cta, .m-press, .d-btn--primary, .btn-primary):not([data-m-primary]) { background: transparent !important; background-image: none !important; box-shadow: none !important; color: inherit !important; border: 1px solid currentColor !important; }

/* ---- 13 + 19: phone forms. 16px stops iOS zooming the page on focus; 44px rows. ----------------------------- */
@media (max-width: 640px) {
  .m-form input:not([type="checkbox"]):not([type="radio"]):not([type="range"]), .m-form select, .m-form textarea { font-size: 16px; min-height: 44px; }
}

@media (prefers-reduced-motion: reduce) { .m-dim, details.m-more > summary::after { transition: none !important; } }
