/* ==========================================================================
   TeachAssist Dashboard — motion layer
   --------------------------------------------------------------------------
   Loads AFTER style.css and mobile.css. Adds micro-interactions only: no
   colours, sizes, copy or layout of the existing design are changed here.

   The single switch is the `.m-on` class that js/motion.js puts on <html>.
   It is on only when BOTH are true:
     • the existing Settings toggle "Interface animations" is ON
       (personalization.js writes data-motion="true|false"), and
     • the device is not asking for `prefers-reduced-motion: reduce`.
   Everything below is scoped under `.m-on`, so when motion is off nothing in
   this file applies and every element renders in its final state instantly.

   Every colour comes from the existing theme tokens (--accent, --text, …) so
   all 15 themes, including both dark modes and the high-contrast ones, are
   correct by construction. Only transform / opacity / filter are animated.
   ========================================================================== */

:root {
  /* The calm, decelerating ease used across the whole motion pass. */
  --m-ease: cubic-bezier(0.16, 1, 0.3, 1);
  --m-fast: 150ms;
  --m-base: 260ms;
  --m-slow: 400ms;
  --m-draw: 1200ms;
}

/* ============================ Shared keyframes =========================== */
@keyframes m-fade-up {
  from {
    opacity: 0;
    transform: translateY(14px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes m-fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
@keyframes m-pop {
  0% {
    opacity: 0;
    transform: scale(0.6);
  }
  62% {
    opacity: 1;
    transform: scale(1.15);
  }
  100% {
    opacity: 1;
    transform: scale(1);
  }
}
@keyframes m-pop-soft {
  0% {
    opacity: 0;
    transform: scale(0.8);
  }
  100% {
    opacity: 1;
    transform: scale(1);
  }
}
@keyframes m-bar-grow {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}
@keyframes m-shake {
  0%,
  100% {
    transform: translateX(0);
  }
  15% {
    transform: translateX(-8px);
  }
  32% {
    transform: translateX(8px);
  }
  50% {
    transform: translateX(-6px);
  }
  68% {
    transform: translateX(6px);
  }
  85% {
    transform: translateX(-3px);
  }
}
@keyframes m-float {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-6px);
  }
}
@keyframes m-sweep {
  0% {
    transform: translateX(-130%);
  }
  55%,
  100% {
    transform: translateX(130%);
  }
}
@keyframes m-shine {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(220%);
  }
}
@keyframes m-screen-right {
  from {
    opacity: 0;
    transform: translateX(24px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes m-screen-left {
  from {
    opacity: 0;
    transform: translateX(-24px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes m-tick-in {
  from {
    opacity: 0;
    transform: translateY(60%) rotateX(-60deg);
    filter: blur(4px);
  }
  to {
    opacity: 1;
    transform: none;
    filter: blur(0);
  }
}
@keyframes m-tick-out {
  from {
    opacity: 1;
    transform: none;
    filter: blur(0);
  }
  to {
    opacity: 0;
    transform: translateY(-60%) rotateX(60deg);
    filter: blur(4px);
  }
}
@keyframes m-orbit {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
@keyframes m-orbit-counter {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(-360deg);
  }
}
/* Opacity only: each sparkle's transform belongs to its counter-rotation, and
   two animations on one property would fight (the last one silently wins). */
@keyframes m-twinkle {
  0%,
  100% {
    opacity: 0.4;
  }
  50% {
    opacity: 1;
  }
}
@keyframes m-burst {
  0% {
    opacity: 0;
    transform: translate(-50%, -50%) rotate(var(--m-a)) translateY(0) scale(0.4);
  }
  22% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform: translate(-50%, -50%) rotate(var(--m-a)) translateY(-44px)
      scale(1);
  }
}
@keyframes m-typing-dot {
  0%,
  60%,
  100% {
    transform: translateY(0);
    opacity: 0.45;
  }
  30% {
    transform: translateY(-5px);
    opacity: 1;
  }
}
@keyframes m-ring-pulse {
  0% {
    opacity: 0.55;
    transform: translateY(-50%) scale(0.9);
  }
  100% {
    opacity: 0;
    transform: translateY(-50%) scale(1.9);
  }
}
@keyframes m-nudge-ne {
  0%,
  100% {
    transform: none;
  }
  50% {
    transform: translate(3px, -3px);
  }
}

/* ========================================================================
   Generic entrance utilities
   `.m-stagger` children fade up one after another; `--m-step` tunes the gap
   and `--m-i` (set in JS) is the child's index.
   ======================================================================== */
.m-on .m-stagger > * {
  animation: m-fade-up var(--m-slow) var(--m-ease) both;
  animation-delay: calc(var(--m-i, 0) * var(--m-step, 0.06s));
}
.m-on .m-fade-up {
  animation: m-fade-up var(--m-slow) var(--m-ease) both;
  animation-delay: calc(var(--m-i, 0) * var(--m-step, 0.06s));
}
.m-on .m-fade-in {
  animation: m-fade-in var(--m-base) ease both;
  animation-delay: calc(var(--m-i, 0) * var(--m-step, 0.06s));
}
.m-on .m-pop {
  animation: m-pop var(--m-slow) var(--m-ease) both;
  animation-delay: calc(var(--m-i, 0) * var(--m-step, 0.04s));
}
.m-on .m-pop-soft {
  animation: m-pop-soft var(--m-base) var(--m-ease) both;
  animation-delay: calc(var(--m-i, 0) * var(--m-step, 0.04s));
}
.m-on .m-shake {
  animation: m-shake var(--m-slow) ease both;
}

/* Infinite loops carry `.m-loop` so they can be parked when they are not
   visible or the tab is in the background (battery + 60fps on phones). */
.m-on .m-loop.m-paused,
.m-on [data-tab-hidden="true"] .m-loop {
  animation-play-state: paused !important;
}
html[data-tab-hidden="true"] .m-loop,
html[data-tab-hidden="true"] .m-loop::before,
html[data-tab-hidden="true"] .m-loop::after {
  animation-play-state: paused !important;
}

/* ============================== 1 · Sign in ============================== */
.m-on .auth-card {
  animation: screen-in var(--m-slow) var(--m-ease) both;
}
.m-on .auth-logo {
  animation: m-fade-up var(--m-slow) var(--m-ease) both;
}
.m-on .auth-logo .mark {
  animation: m-float 5s ease-in-out infinite;
}
.m-on .auth-features .feature-item {
  animation: m-fade-up var(--m-slow) var(--m-ease) both;
  animation-delay: calc(0.12s + var(--m-i, 0) * 0.08s);
}

/* "Advanced (Worker setup)" — smooth open/close instead of a jump. */
.m-on details#advanced .m-disclosure {
  display: grid;
  grid-template-rows: 0fr;
  transition:
    grid-template-rows 320ms var(--m-ease),
    opacity 220ms ease;
  opacity: 0;
}
.m-on details#advanced.m-open .m-disclosure {
  grid-template-rows: 1fr;
  opacity: 1;
}
.m-on details#advanced .m-disclosure > .m-disclosure-inner {
  overflow: hidden;
  min-height: 0;
}
.m-on details#advanced > summary::before {
  display: inline-block;
  transition: transform 260ms var(--m-ease);
}
.m-on details#advanced.m-open > summary::before {
  transform: rotate(90deg);
}

/* Failed sign in: the form shakes, the message fades in. */
.m-on #auth-error.m-error-in {
  animation: m-fade-in 260ms ease both;
}

/* "Explore the demo" — a soft shimmer sweep invites the tap. */
.m-on #demo-btn {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.m-on #demo-btn::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    100deg,
    transparent 28%,
    color-mix(in srgb, var(--accent) 16%, transparent) 50%,
    transparent 72%
  );
  transform: translateX(-130%);
  animation: m-sweep 4s var(--m-ease) infinite;
  pointer-events: none;
}
/* ========================= 2 · Courses dashboard ========================= */
.m-on #screen-courses .screen-header .eyebrow {
  animation: m-fade-up 380ms var(--m-ease) both;
}
.m-on #screen-courses .screen-header h1 {
  animation: m-fade-up 460ms var(--m-ease) both;
  animation-delay: 90ms;
}
.m-on #screen-courses .dashboard-subtitle {
  animation: m-fade-up 460ms var(--m-ease) both;
  animation-delay: 160ms;
}

/* Rotating encouragement line. Every line lives in the SAME grid cell so the
   layout can never shift as they swap. */
.m-ticker {
  display: inline-grid;
  vertical-align: top;
  perspective: 420px;
}
.m-ticker > span {
  grid-area: 1 / 1;
  opacity: 0;
}
.m-ticker > span.m-tick-current {
  opacity: 1;
}
.m-on .m-ticker > span.m-tick-current {
  animation: m-tick-in 700ms var(--m-ease) both;
}
.m-on .m-ticker > span.m-tick-leaving {
  opacity: 0;
  animation: m-tick-out 500ms var(--m-ease) both;
}

/* Overall-average gauge: the arc draws, the number counts up (JS). */
.m-on .gauge-arc {
  transition: stroke-dashoffset var(--m-draw) cubic-bezier(0.16, 1, 0.3, 1);
}
.gauge-num {
  font-variant-numeric: tabular-nums;
}

/* Refresh ↻ — spins while working, then morphs into a ✓ that pops. */
.m-on #screen-courses #refresh.m-working {
  animation: spin 0.7s linear infinite;
}
.m-on #screen-courses #refresh.m-done {
  animation: m-pop var(--m-slow) var(--m-ease) both;
}
/* Centre the glyph so the ↻ → ✓ swap cannot shift it. No min-width here:
   mobile.css gives this button a 44px tap target and this rule outranks it. */
#screen-courses #refresh {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Dream card — sparkles orbit the ✧ mark. */
.m-on .dream-preview .dream-orbit {
  position: relative;
}
.m-orbit-ring {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.m-on .m-orbit-ring {
  animation: m-orbit 30s linear infinite;
}
/* The arm holds the sparkle out at its radius and never animates … */
.m-orbit-arm {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 12px;
  height: 12px;
  margin: -6px 0 0 -6px;
  transform: rotate(var(--m-a)) translateY(calc(var(--m-r, 46px) * -1));
}
/* … so the glyph is free to counter-rotate (staying upright as the ring
   turns) while its opacity twinkles on a separate, staggered cycle. */
.m-orbit-arm > i {
  display: block;
  width: 100%;
  height: 100%;
  font-size: 11px;
  font-style: normal;
  line-height: 12px;
  text-align: center;
  color: var(--accent);
  opacity: 0.6;
}
.m-on .m-orbit-arm > i {
  animation:
    m-orbit-counter 30s linear infinite,
    m-twinkle 3.2s ease-in-out infinite;
  animation-delay: 0s, calc(var(--m-i, 0) * 0.5s);
}
.m-on .dream-preview .btn {
  transition: transform var(--m-base) var(--m-ease);
}
@media (hover: hover) {
  .m-on .dream-preview .btn:hover {
    transform: translate(3px, -3px);
  }
}

/* Course cards. */
.m-on .course-grid > .course-card {
  animation: m-fade-up 460ms var(--m-ease) both;
  animation-delay: calc(var(--m-i, 0) * 0.05s);
}
.cc-bar > i {
  transform-origin: left center;
}
.m-on .course-grid .cc-bar > i {
  animation: m-bar-grow 900ms var(--m-ease) both;
  animation-delay: calc(0.1s + var(--m-i, 0) * 0.05s);
}
.m-on .course-card {
  transition:
    transform var(--m-base) var(--m-ease),
    box-shadow var(--m-base) var(--m-ease),
    border-color var(--m-base) var(--m-ease);
}
.m-on .course-card .chevron {
  transition: transform var(--m-base) var(--m-ease);
}
@media (hover: hover) {
  .m-on .course-card:hover {
    transform: translateY(-3px);
    box-shadow: var(--shadow-lg);
  }
  .m-on .course-card:hover .chevron {
    transform: translateX(3px);
  }
}
.m-on .course-card:active {
  transform: scale(0.98);
}

/* A one-time shine across the bar of a mark at 90 % or better. */
.m-on .cc-bar.m-high {
  position: relative;
}
.m-on .cc-bar.m-high::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    100deg,
    transparent 30%,
    color-mix(in srgb, var(--accent-contrast) 55%, transparent) 50%,
    transparent 70%
  );
  transform: translateX(-100%);
  animation: m-shine 1s var(--m-ease) 1 both;
  animation-delay: calc(0.9s + var(--m-i, 0) * 0.05s);
  pointer-events: none;
}

/* "✦ Ask the assistant" */
.m-ask-star {
  display: inline-block;
}
.m-on .m-ask-star {
  animation:
    m-orbit 9s linear infinite,
    m-twinkle 3s ease-in-out infinite;
}

/* ========================== 3 · Course detail ============================ */
.m-on .screen.m-from-right {
  animation: m-screen-right 280ms var(--m-ease) both;
}
.m-on .screen.m-from-left {
  animation: m-screen-left 280ms var(--m-ease) both;
}
.m-on #screen-detail .midterm-pill {
  animation: m-pop var(--m-slow) var(--m-ease) both;
  animation-delay: 900ms;
}

/* The Evaluations / Breakdown segmented control gets a sliding thumb; the
   Grade / Trend / Info buttons are opaque cards separated by gaps, so a pill
   would slide out of sight behind them in the dark themes — those crossfade
   between states instead. */
.carousel {
  scroll-snap-type: x mandatory;
}
.m-on .carousel {
  scroll-behavior: smooth;
}
.segmented {
  position: relative;
}
.m-on .carousel-controls button {
  transition:
    background var(--m-base) var(--m-ease),
    border-color var(--m-base) var(--m-ease),
    color var(--m-base) var(--m-ease),
    font-weight var(--m-base) var(--m-ease);
}
.m-thumb {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  border-radius: 12px;
  background: var(--accent-tint);
  pointer-events: none;
  z-index: 0;
  opacity: 0;
}
.m-thumb.m-thumb-ready {
  opacity: 1;
}
.m-on .m-thumb {
  transition:
    transform 300ms var(--m-ease),
    width 300ms var(--m-ease),
    opacity var(--m-fast) ease;
}
.segmented button {
  position: relative;
  z-index: 1;
}
.m-on .segmented.m-has-thumb {
  isolation: isolate;
}
.m-on .segmented.m-has-thumb button.active {
  background: transparent;
  box-shadow: none;
}
.m-on .segmented.m-has-thumb .m-thumb {
  background: var(--card-soft);
  box-shadow: var(--shadow-sm);
  border-radius: 9px;
  top: 3px;
  height: calc(100% - 6px);
}

/* Evaluation rows, category pills and teacher feedback. */
.m-on #seg-content .eval-row {
  animation: m-fade-up 380ms var(--m-ease) both;
  animation-delay: calc(var(--m-i, 0) * 0.04s);
}
.m-on #seg-content .eval-row .cat-pill {
  animation: m-pop-soft 300ms var(--m-ease) both;
  animation-delay: calc(0.12s + var(--m-i, 0) * 0.04s);
}
.m-on #seg-content .eval-row .eval-feedback {
  animation: m-fade-up 360ms var(--m-ease) both;
  animation-delay: calc(0.18s + var(--m-i, 0) * 0.04s);
}

/* Breakdown bars grow from the left, staggered. */
.bar-fill {
  transform-origin: left center;
}
.m-on #seg-content .bar-fill {
  animation: m-bar-grow 900ms var(--m-ease) both;
  animation-delay: calc(0.08s + var(--m-i, 0) * 0.07s);
}
.m-on #seg-content .strand-row {
  animation: m-fade-up 360ms var(--m-ease) both;
  animation-delay: calc(var(--m-i, 0) * 0.05s);
}

/* ============================== 4 · Dreams =============================== */
.m-on #screen-dreams .screen-header,
.m-on #screen-dreams .dream-form,
.m-on #screen-dreams .section-label,
.m-on #screen-dreams > section > p {
  animation: m-fade-up var(--m-slow) var(--m-ease) both;
  animation-delay: calc(var(--m-i, 0) * 0.08s);
}
.m-on #screen-dreams .goal-row {
  animation: m-fade-up 380ms var(--m-ease) both;
  animation-delay: calc(var(--m-i, 0) * 0.05s);
}
.m-on .field input,
.m-on .field select,
.m-on .goal-row input {
  transition:
    border-color var(--m-base) var(--m-ease),
    box-shadow var(--m-base) var(--m-ease),
    transform var(--m-base) var(--m-ease);
}

/* Current-vs-target progress for each course goal. */
.goal-meter {
  height: 5px;
  border-radius: 3px;
  background: var(--track);
  overflow: hidden;
  margin-top: 8px;
}
.goal-meter > i {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: var(--accent);
  transform-origin: left center;
  transition: width var(--m-slow) var(--m-ease);
}
.m-on .goal-meter > i {
  animation: m-bar-grow 900ms var(--m-ease) both;
  animation-delay: calc(0.1s + var(--m-i, 0) * 0.05s);
}

/* One-time sparkle burst when a target is newly reached. */
.m-burst {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: visible;
}
.m-burst > i {
  position: absolute;
  top: 50%;
  left: 50%;
  font-size: 11px;
  font-style: normal;
  line-height: 1;
  color: var(--accent);
  opacity: 0;
}
.m-on .m-burst > i {
  animation: m-burst 800ms var(--m-ease) 1 both;
  animation-delay: calc(var(--m-i, 0) * 0.02s);
}

/* "Saved ✓" confirmation label. */
.m-on .m-saved {
  animation: m-pop 420ms var(--m-ease) both;
}

/* ============================= 5 · Assistant ============================= */
.m-on #screen-assistant .screen-header,
.m-on #screen-assistant .assistant-intro {
  animation: m-fade-up var(--m-slow) var(--m-ease) both;
  animation-delay: calc(var(--m-i, 0) * 0.08s);
}
.m-on #screen-assistant .badge {
  animation: m-fade-up 360ms var(--m-ease) both;
  animation-delay: 120ms;
}
.m-on .prompt-chips button {
  animation: m-fade-up 340ms var(--m-ease) both;
  animation-delay: calc(0.18s + var(--m-i, 0) * 0.05s);
  transition:
    transform var(--m-base) var(--m-ease),
    border-color var(--m-base) var(--m-ease),
    color var(--m-base) var(--m-ease);
}
@media (hover: hover) {
  .m-on .prompt-chips button:hover {
    transform: translateY(-2px);
    border-color: var(--accent);
    color: var(--accent);
  }
}
.m-on .prompt-chips button:active {
  transform: scale(0.96);
}

/* Chat: the student's message arrives from the right, answers from the left. */
@keyframes m-msg-right {
  from {
    opacity: 0;
    transform: translate(16px, 8px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes m-msg-left {
  from {
    opacity: 0;
    transform: translate(-16px, 8px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
.m-on .chat-log .chat-message.from-user {
  animation: m-msg-right 360ms var(--m-ease) both;
}
.m-on .chat-log .chat-message:not(.from-user) {
  animation: m-msg-left 360ms var(--m-ease) both;
}

/* Three-dot typing indicator while an answer is being worked out. */
.m-typing {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-height: 18px;
}
.m-typing > i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--text-secondary);
  opacity: 0.45;
}
.m-on .m-typing > i {
  animation: m-typing-dot 1.1s ease-in-out infinite;
  animation-delay: calc(var(--m-i, 0) * 0.16s);
}

/* Quick calculator result. */
.m-on .calc-result.m-reveal {
  animation: m-fade-up 380ms var(--m-ease) both;
}
.m-on .calc-result.m-shake {
  animation: m-shake var(--m-slow) ease both;
}
.calc-result.m-impossible {
  color: var(--danger);
}

/* ============================= 6 · Guidance ============================== */
.m-on #screen-guidance .section-label {
  animation: m-fade-up 380ms var(--m-ease) both;
  animation-delay: calc(var(--m-i, 0) * 0.04s);
}
.m-on #screen-guidance .rows .row {
  animation: m-fade-up 340ms var(--m-ease) both;
  animation-delay: calc(var(--m-i, 0) * 0.04s);
  transition: background var(--m-base) var(--m-ease);
}
.m-arrow {
  display: inline-block;
}
.m-on .m-arrow {
  transition: transform var(--m-base) var(--m-ease);
}
@media (hover: hover) {
  .m-on #screen-guidance .rows .row:hover .m-arrow {
    transform: translate(3px, -3px);
  }
}
.m-on #screen-guidance .rows .row:focus-visible .m-arrow {
  transform: translate(3px, -3px);
}

/* ==================== 7 · Tab bar & screen transitions =================== */
.m-tab-indicator {
  position: absolute;
  top: 9px;
  left: 0;
  width: 50px;
  height: 30px;
  border-radius: 12px;
  background: var(--accent-tint);
  pointer-events: none;
  opacity: 0;
  z-index: 0;
}
.m-tab-indicator.m-thumb-ready {
  opacity: 1;
}
.m-on .m-tab-indicator {
  transition:
    transform 350ms var(--m-ease),
    width 350ms var(--m-ease),
    opacity var(--m-fast) ease;
}
/* With the sliding pill on, the per-button highlight would draw twice. */
.m-on .tabbar.m-has-indicator button.active::before {
  background: transparent;
}
.tabbar button svg {
  position: relative;
  z-index: 1;
}
@keyframes m-tab-bounce {
  0%,
  100% {
    transform: translateY(0);
  }
  45% {
    transform: translateY(-3px);
  }
}
.m-on .tabbar button.m-bounce svg {
  animation: m-tab-bounce 340ms var(--m-ease) both;
}

/* The top bar lifts off the content once the screen scrolls past 8px. */
.app-topbar {
  transition: box-shadow var(--m-base) ease;
}
.app-topbar.m-scrolled {
  box-shadow: 0 6px 18px color-mix(in srgb, var(--text) 8%, transparent);
}

/* DEMO pill — a slow pulsing ring dot. */
.m-on .demo-pill {
  position: relative;
  padding-left: 18px;
}
.m-on .demo-pill::before {
  content: "";
  position: absolute;
  left: 8px;
  top: 50%;
  width: 6px;
  height: 6px;
  margin-left: -3px;
  border-radius: 50%;
  background: var(--accent);
  transform: translateY(-50%);
  animation: m-ring-pulse 2.4s ease-out infinite;
}

/* ============================= 8 · Settings ============================== */
.m-on #screen-settings > * {
  animation: m-fade-up 380ms var(--m-ease) both;
  animation-delay: calc(var(--m-i, 0) * 0.04s);
}

/* Toggle knob: slides with a gentle overshoot, track colour crossfades. */
.m-on .preference-row input {
  transition: background var(--m-base) ease;
}
.m-on .preference-row input::after {
  transition: transform 320ms cubic-bezier(0.34, 1.42, 0.64, 1);
}

/* Theme swatches: the selected one grows and an accent ring draws in. */
.m-on .swatch {
  transition:
    transform 260ms var(--m-ease),
    box-shadow 260ms var(--m-ease);
}
.m-on .swatch.active {
  transform: scale(1.1);
}
.swatch::before {
  content: "";
  position: absolute;
  inset: -5px;
  border-radius: 50%;
  border: 2px solid var(--accent);
  opacity: 0;
  transform: scale(0.75);
  pointer-events: none;
}
.m-on .swatch::before {
  transition:
    opacity 260ms var(--m-ease),
    transform 260ms var(--m-ease);
}
.m-on .swatch.active::before {
  opacity: 1;
  transform: scale(1);
}

/* Changing theme crossfades the surfaces instead of flashing. */
.m-on.m-theming body,
.m-on.m-theming .shell,
.m-on.m-theming .card,
.m-on.m-theming .tabbar,
.m-on.m-theming .app-topbar,
.m-on.m-theming .rows {
  transition:
    background-color 300ms ease,
    background-image 300ms ease,
    color 300ms ease,
    border-color 300ms ease;
}

/* "Hide grades" blurs marks in and out rather than snapping. */
.m-on .cc-mark,
.m-on .overall-gauge svg,
.m-on .private-mark,
.m-on .cc-bar,
.m-on .update-trend,
.m-on .delta-pill {
  transition:
    filter 250ms ease,
    opacity 250ms ease;
}
/* The blur itself is style.css's; the transition above is all that is new,
   so the concealed appearance is unchanged. */

/* "Larger text" / "Compact layout" settle instead of snapping. */
.m-on .course-card,
.m-on .preference-row,
.m-on .eval-row {
  transition:
    padding 260ms var(--m-ease),
    min-height 260ms var(--m-ease),
    font-size 260ms var(--m-ease),
    transform var(--m-base) var(--m-ease),
    box-shadow var(--m-base) var(--m-ease);
}

/* ====================== 9 · Empty and loading states ===================== */
.m-on .empty .empty-icon {
  animation:
    empty-icon 1.8s ease-in-out infinite,
    m-float 5s ease-in-out infinite;
}
.m-on .m-content-in {
  animation: m-fade-in 220ms ease both;
}

/* ========================================================================
   Motion OFF — the Settings toggle, or `prefers-reduced-motion: reduce`.
   js/motion.js removes `.m-on` (so nothing above applies) and adds
   `.no-motion`; these rules are the belt-and-braces backstop that also
   catches anything rendered before the module runs.
   ======================================================================== */
.no-motion *,
.no-motion *::before,
.no-motion *::after,
[data-motion="false"] *,
[data-motion="false"] *::before,
[data-motion="false"] *::after {
  animation: none !important;
  transition: none !important;
  scroll-behavior: auto !important;
}
.no-motion .m-ticker > span:not(.m-tick-current),
[data-motion="false"] .m-ticker > span:not(.m-tick-current) {
  display: none;
}
.no-motion .m-burst,
.no-motion .m-orbit-ring,
.no-motion .m-typing > i,
[data-motion="false"] .m-burst,
[data-motion="false"] .m-orbit-ring {
  opacity: 1;
}
.no-motion .m-burst,
[data-motion="false"] .m-burst {
  display: none;
}

/* ========================================================================
   "Hide grades" — a concealed mark is never revealed by motion. The helpers
   in js/motion.js already write final values straight away; these rules are
   the CSS half, stopping any bar or arc from growing in behind the blur.
   ======================================================================== */
[data-hide-marks="true"] .cc-bar > i,
[data-hide-marks="true"] .cc-bar.m-high::after,
[data-hide-marks="true"] .gauge-arc,
[data-hide-marks="true"] .goal-meter > i,
[data-hide-marks="true"] #seg-content .bar-fill {
  animation: none !important;
  transition: none !important;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }
  .m-ticker > span:not(.m-tick-current) {
    display: none;
  }
  .m-burst {
    display: none;
  }
}
