/* ==========================================================================
   Soul Sounds Academy (SSA) — motion & interaction layer
   Built by ETANC for Soul Sounds Academy (SSA).
   Loaded last, so it refines the shell without editing it.

   PRESENTATION ONLY. Nothing here changes markup, ids, data-* hooks or state.
   Every rule keys off classes and attributes the app already sets.

   Principles held to throughout:
   - Transitions (interruptible) for interactive state; keyframes only for
     staged entrances that run once.
   - Named properties only. Never `transition: all`.
   - Motion is never the only feedback channel; colour or shape always moves too.
   - High-frequency interactions get 150ms or less. Entrances may take longer.
   - Everything collapses under prefers-reduced-motion at the end of the file.
   ========================================================================== */


/* ==========================================================================
   1. SCREEN ENTRANCES
   .screen[hidden] is display:none, so removing [hidden] restarts any animation
   on its children. That gives a staged entrance with no JS: the router already
   toggles the attribute. Direct children are the semantic chunks (hero, stat
   row, toolbar, table), so they rise in sequence.
   ========================================================================== */
@keyframes rise-in {
  from { opacity: 0; transform: translate3d(0, var(--rise-dist), 0); }
  to   { opacity: 1; transform: none; }
}

.screen:not([hidden]) > * {
  animation: rise-in var(--dur-rise) var(--ease-rise) both;
}
.screen:not([hidden]) > *:nth-child(1) { animation-delay: 0ms; }
.screen:not([hidden]) > *:nth-child(2) { animation-delay: calc(var(--stagger) * 1); }
.screen:not([hidden]) > *:nth-child(3) { animation-delay: calc(var(--stagger) * 2); }
.screen:not([hidden]) > *:nth-child(4) { animation-delay: calc(var(--stagger) * 3); }
.screen:not([hidden]) > *:nth-child(5) { animation-delay: calc(var(--stagger) * 4); }
.screen:not([hidden]) > *:nth-child(n+6) { animation-delay: calc(var(--stagger) * 5); }

/* The hero contents cascade after the hero itself has landed */
.screen:not([hidden]) .hero__label,
.screen:not([hidden]) .hero__figure,
.screen:not([hidden]) .hero__note,
.screen:not([hidden]) .hero__actions {
  animation: rise-in var(--dur-rise) var(--ease-rise) both;
}
.screen:not([hidden]) .hero__label   { animation-delay: 80ms; }
.screen:not([hidden]) .hero__figure  { animation-delay: 140ms; }
.screen:not([hidden]) .hero__note    { animation-delay: 210ms; }
.screen:not([hidden]) .hero__actions { animation-delay: 270ms; }

/* Stat tiles arrive left to right, after the row itself */
.screen:not([hidden]) .statrow .stat {
  animation: rise-in 420ms var(--ease-rise) both;
}
.screen:not([hidden]) .statrow .stat:nth-child(1) { animation-delay: 140ms; }
.screen:not([hidden]) .statrow .stat:nth-child(2) { animation-delay: 200ms; }
.screen:not([hidden]) .statrow .stat:nth-child(3) { animation-delay: 260ms; }
.screen:not([hidden]) .statrow .stat:nth-child(4) { animation-delay: 320ms; }


/* ==========================================================================
   2. CADENCE BARS — grow from the baseline
   Animating height from 0 leaves the implicit to-frame as the element own
   height, so the JS-set --h is preserved exactly. The current-month dot is
   positioned against the column bottom edge, so it stays anchored.
   ========================================================================== */
@keyframes bar-grow {
  from { height: 0; min-height: 0; }
}

.screen:not([hidden]) .cadence__col {
  animation: bar-grow var(--dur-bar) var(--ease-rise) both;
}
.screen:not([hidden]) .cadence__col:nth-child(1)  { animation-delay: calc(var(--stagger-bar) * 1); }
.screen:not([hidden]) .cadence__col:nth-child(2)  { animation-delay: calc(var(--stagger-bar) * 2); }
.screen:not([hidden]) .cadence__col:nth-child(3)  { animation-delay: calc(var(--stagger-bar) * 3); }
.screen:not([hidden]) .cadence__col:nth-child(4)  { animation-delay: calc(var(--stagger-bar) * 4); }
.screen:not([hidden]) .cadence__col:nth-child(5)  { animation-delay: calc(var(--stagger-bar) * 5); }
.screen:not([hidden]) .cadence__col:nth-child(6)  { animation-delay: calc(var(--stagger-bar) * 6); }
.screen:not([hidden]) .cadence__col:nth-child(7)  { animation-delay: calc(var(--stagger-bar) * 7); }
.screen:not([hidden]) .cadence__col:nth-child(8)  { animation-delay: calc(var(--stagger-bar) * 8); }
.screen:not([hidden]) .cadence__col:nth-child(9)  { animation-delay: calc(var(--stagger-bar) * 9); }
.screen:not([hidden]) .cadence__col:nth-child(10) { animation-delay: calc(var(--stagger-bar) * 10); }
.screen:not([hidden]) .cadence__col:nth-child(11) { animation-delay: calc(var(--stagger-bar) * 11); }
.screen:not([hidden]) .cadence__col:nth-child(12) { animation-delay: calc(var(--stagger-bar) * 12); }

/* Hero bars: hovering one column lifts its segments and dims the rest, so the
   month being read separates from the year around it. */
.cadence--hero .cadence__col { transition: opacity 180ms var(--ease-out); }
.cadence--hero .cadence__columns:hover .cadence__col { opacity: 0.45; }
.cadence--hero .cadence__columns:hover .cadence__col:hover,
.cadence--hero .cadence__col.is-hot { opacity: 1; }
.cadence--hero .cadence__seg {
  transform-origin: center;
  transition: filter 150ms var(--ease-out), border-color 150ms var(--ease-out), transform 180ms var(--ease-emphasis), box-shadow 180ms var(--ease-out);
}
.cadence--hero .cadence__col:hover .cadence__seg,
.cadence--hero .cadence__col.is-hot .cadence__seg { transform: scaleX(1.12); }
.cadence--hero .cadence__col:hover .cadence__seg--paid,
.cadence--hero .cadence__col.is-hot .cadence__seg--paid { box-shadow: var(--glow-brass-soft); }

/* Expense bars already lift on hover; give the press a beat and the active
   month a steadier glow. */
.cadence--expenses .cadence__col { transition: background 150ms var(--ease-out), transform 150ms var(--ease-out), box-shadow 150ms var(--ease-out); }
.cadence--expenses .cadence__col:hover { box-shadow: var(--glow-brass-soft); }
.cadence--expenses .cadence__col:active { transform: translateY(0) scaleY(0.94); transition-duration: var(--dur-btn-press); }
.cadence--expenses .cadence__months span { transition: color 120ms ease, background 120ms ease; }


/* ==========================================================================
   3. BUTTONS — the tactile system, extended
   The press scale and shadow flattening already live in shell.css. Added here:
   a sheen that crosses the solid button on hover, a brass glow to match, and
   fills that wipe in from the leading edge on the outline variants.
   ========================================================================== */
.btn { overflow: hidden; isolation: isolate; }

/* Sheen: a narrow diagonal highlight that sweeps once per hover. Sits under
   the label and never intercepts pointer events. */
.btn--primary::after {
  content: "";
  position: absolute; top: 0; bottom: 0; left: 0;
  width: 55%;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(100deg, transparent, var(--sheen), transparent);
  transform: translateX(-180%) skewX(-18deg);
  transition: transform var(--dur-sheen) var(--ease-emphasis);
}
.btn--primary:hover:not(:disabled)::after { transform: translateX(320%) skewX(-18deg); }
.btn--primary:hover:not(:disabled) {
  box-shadow: var(--shadow-btn-hover), var(--highlight-btn), var(--glow-brass-soft);
}
/* The press resets the sheen so a click always reads as a click */
.btn--primary:active:not(:disabled)::after { transition-duration: 0ms; transform: translateX(-180%) skewX(-18deg); }

/* Outline buttons fill from the leading edge instead of sweeping */
.btn--ghost::after,
.btn--danger::after {
  content: "";
  position: absolute; inset: 0;
  z-index: 0;
  pointer-events: none;
  background: rgb(var(--paper-rgb) / 0.07);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--dur-accent) var(--ease-emphasis);
}
.btn--danger::after { background: rgb(var(--ember-rgb) / 0.12); }
.btn--ghost:hover:not(:disabled)::after,
.btn--danger:hover:not(:disabled)::after { transform: scaleX(1); }
.btn--ghost:hover:not(:disabled) { box-shadow: var(--shadow-lift); transform: translateY(var(--lift)); }
.btn--ghost:active:not(:disabled) { transform: translateY(0) scale(var(--btn-press-scale)); }

/* Keep labels above both pseudo-elements */
.btn > * { position: relative; z-index: 1; }

/* Quiet row actions: the underline grows from the leading edge, so the colour
   change is not the only cue. */
.link-btn { position: relative; }
.link-btn::after {
  content: "";
  position: absolute; left: var(--s-2); right: var(--s-2); bottom: 2px;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--dur-accent) var(--ease-emphasis);
}
.link-btn:hover:not(:disabled)::after { transform: scaleX(1); }

/* Icon buttons grow into the hover and compress into the press */
.iconbtn:hover:not(:disabled) { transform: scale(1.06); }
.iconbtn:active:not(:disabled) { transform: scale(0.92); }

/* Preset buttons (tax rates) */
.preset {
  position: relative; overflow: hidden;
  transition:
    border-color var(--dur-btn-hover) var(--ease-btn),
    background var(--dur-btn-hover) var(--ease-btn),
    box-shadow var(--dur-lift) var(--ease-btn),
    transform var(--dur-btn-release) var(--ease-btn);
}
.preset:hover { box-shadow: var(--shadow-lift); transform: translateY(var(--lift)); }
.preset:active { transform: translateY(0) scale(var(--btn-press-scale)); }
.preset[aria-pressed="true"] { box-shadow: var(--glow-brass-soft); }


/* ==========================================================================
   4. TABS & WORDMARK
   ========================================================================== */
/* No lift on the tabs — the underline sweep in shell.css is the whole cue */
.tab { transition: color var(--dur-fast) var(--ease-out); }

/* The five cadence cells in the wordmark run like an equaliser on hover */
.wordmark__mark i { transition: height 260ms var(--ease-emphasis), background 260ms var(--ease-out); }
.wordmark:hover .wordmark__mark i { background: rgb(var(--brass-rgb) / 0.55); }
.wordmark:hover .wordmark__mark i:nth-child(1) { height: 18px; transition-delay: 0ms; }
.wordmark:hover .wordmark__mark i:nth-child(2) { height: 8px;  transition-delay: 40ms; }
.wordmark:hover .wordmark__mark i:nth-child(3) { height: 14px; transition-delay: 80ms; background: var(--brass); }
.wordmark:hover .wordmark__mark i:nth-child(4) { height: 22px; transition-delay: 120ms; }
.wordmark:hover .wordmark__mark i:nth-child(5) { height: 16px; transition-delay: 160ms; }


/* ==========================================================================
   5. SURFACES — stat tiles, panels, rosters
   Elevation comes from layered shadow; the hairline borders that carry
   structure are left alone.
   ========================================================================== */
.stat {
  position: relative;
  transition: background var(--dur-lift) var(--ease-out);
}
/* A brass rule wipes across the top edge of the hovered tile */
.stat::after {
  content: "";
  position: absolute; left: 0; right: 0; top: -1px;
  height: 1px;
  background: var(--brass);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--dur-accent) var(--ease-emphasis);
}
.stat:hover::after { transform: scaleX(1); }
.stat:hover { background: rgb(var(--paper-rgb) / 0.02); }
.stat__value { transition: color var(--dur-lift) var(--ease-out); }
.stat:hover .stat__value { color: var(--brass); }

.panel--raised {
  transition: border-color var(--dur-lift) var(--ease-out), box-shadow var(--dur-lift) var(--ease-out);
}
.panel--raised:hover {
  border-color: var(--line-strong);
  box-shadow: var(--shadow-lift);
}

/* Class rosters read as rows, so they get the row treatment, not a card lift */
.roster { position: relative; transition: background var(--dur-lift) var(--ease-out); }
.roster::before {
  content: "";
  position: absolute; left: calc(-1 * var(--s-3)); top: var(--s-4); bottom: var(--s-4);
  width: 2px; border-radius: var(--r-cell);
  background: var(--brass);
  transform: scaleY(0);
  transition: transform var(--dur-accent) var(--ease-emphasis);
}
.roster:hover::before { transform: scaleY(1); }
.roster:hover { background: rgb(var(--paper-rgb) / 0.02); }
.roster__name { transition: color var(--dur-lift) var(--ease-out); }
.roster:hover .roster__name { color: var(--brass); }
/* Taken seats fill brass as the roster is read */
.seats i { transition: background 180ms var(--ease-out), border-color 180ms var(--ease-out), transform 180ms var(--ease-emphasis); }
.roster:hover .seats i.is-taken { background: var(--brass); transform: scaleY(1.25); }


/* ==========================================================================
   6. TABLE & LIST ROWS
   ========================================================================== */
.table tbody tr { transition: background var(--dur-fast) var(--ease-out); }
.table tbody tr > td:first-child { position: relative; }
/* Leading-edge brass rule, the same cue the rosters and stat tiles use */
.table tbody tr > td:first-child::before {
  content: "";
  position: absolute; left: calc(-1 * var(--s-3)); top: 4px; bottom: 4px;
  width: 2px; border-radius: var(--r-cell);
  background: var(--brass);
  transform: scaleY(0);
  transition: transform var(--dur-accent) var(--ease-emphasis);
}
.table tbody tr:hover > td:first-child::before { transform: scaleY(1); }

/* Pending balances: the owed figure is the point of the row, so it responds */
.pending__row { position: relative; transition: background var(--dur-lift) var(--ease-out); }
.pending__row::before {
  content: "";
  position: absolute; left: calc(-1 * var(--s-3)); top: var(--s-3); bottom: var(--s-3);
  width: 2px; border-radius: var(--r-cell);
  background: var(--ember);
  transform: scaleY(0);
  transition: transform var(--dur-accent) var(--ease-emphasis);
}
.pending__row:hover::before { transform: scaleY(1); }
.pending__row:hover { background: rgb(var(--paper-rgb) / 0.02); }
.pending__owed { transition: transform var(--dur-lift) var(--ease-emphasis); }
.pending__row:hover .pending__owed { transform: scale(1.015); }

/* Mini strips: individual months lift under the cursor */
.ministrip i { transition: transform 150ms var(--ease-emphasis), filter 150ms var(--ease-out); }
.ministrip:hover i { filter: brightness(1.15); }
.ministrip i:hover { transform: scaleY(1.35); }


/* ==========================================================================
   7. MATRIX CELLS — the highest-frequency interaction in the app
   Kept at 150ms and paired with a shape change, never colour alone.
   ========================================================================== */
.mcell { transition: background var(--dur-btn-hover) var(--ease-btn); }
.mcell .cell {
  transition:
    transform 150ms var(--ease-emphasis),
    box-shadow 150ms var(--ease-out),
    background var(--dur-btn-hover) var(--ease-btn),
    border-color var(--dur-btn-hover) var(--ease-btn);
}
.mcell:hover:not(:disabled) .cell { transform: scale(1.22); }
.mcell:hover:not(:disabled) .cell--paid { box-shadow: var(--glow-brass); }
.mcell:hover:not(:disabled) .cell--unpaid { box-shadow: 0 0 0 1px rgb(var(--ember-rgb) / 0.4), 0 6px 16px -8px rgb(var(--ember-rgb) / 0.5); }
.mcell:hover:not(:disabled) .cell--upcoming { border-color: rgb(var(--paper-rgb) / 0.6); }
/* No press compression on the boxes. This also neutralises the scale(0.88) in
   screens.css, so clicking a month changes only its status mark. */
.mcell:active:not(:disabled) .cell { transform: none; }
.mcell:hover:active:not(:disabled) .cell { transform: scale(1.22); }
/* The current month column reads warmer under the cursor */
.matrix td.is-current, .matrix th.is-current { transition: background var(--dur-fast) var(--ease-out); }
.matrix tbody tr:hover td.is-current { background: rgb(var(--brass-rgb) / 0.09); }


/* ==========================================================================
   8. FORM CONTROLS
   ========================================================================== */
/* Text fields lift their surface on focus as well as warming the border */
.field input:focus-visible, .field select:focus-visible,
.form input:focus-visible, .form select:focus-visible, .form textarea:focus-visible {
  background: rgb(var(--paper-rgb) / 0.055);
}

/* Custom select: options cascade in behind the opening list */
@keyframes opt-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}
.sel__list.is-open .sel__opt { animation: opt-in 200ms var(--ease-emphasis) both; }
.sel__list.is-open .sel__opt:nth-child(1) { animation-delay: 20ms; }
.sel__list.is-open .sel__opt:nth-child(2) { animation-delay: 45ms; }
.sel__list.is-open .sel__opt:nth-child(3) { animation-delay: 70ms; }
.sel__list.is-open .sel__opt:nth-child(4) { animation-delay: 95ms; }
.sel__list.is-open .sel__opt:nth-child(n+5) { animation-delay: 115ms; }
.sel__opt { transition: background var(--dur-fast) var(--ease-out), padding-left var(--dur-fast) var(--ease-emphasis); }
.sel__opt:hover { background: rgb(var(--paper-rgb) / 0.05); padding-left: calc(var(--s-4) + 3px); }
.sel__opt[aria-selected="true"]::before { transition: height var(--dur-base) var(--ease-emphasis); }

/* Switches: the thumb travels on a rising curve, the track glows when live */
.switch__track { transition: background var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out); }
.switch__track::after { transition: transform 260ms var(--ease-rise), background var(--dur-base), width var(--dur-btn-press) var(--ease-out); }
.switch input:checked + .switch__track { box-shadow: var(--glow-brass-soft); }
.switch:hover .switch__track { background: rgb(var(--paper-rgb) / 0.2); }
.switch:hover input:checked + .switch__track { background: var(--brass-hover); }
.switch:active .switch__track::after { width: 17px; }

/* Slider: the thumb grows under the cursor and while dragging */
.slider::-webkit-slider-thumb { transition: transform 150ms var(--ease-emphasis), box-shadow 150ms var(--ease-out); }
.slider:hover::-webkit-slider-thumb { transform: scaleY(1.12); box-shadow: 0 2px 10px rgb(0 0 0 / 0.55), var(--glow-brass-soft); }
.slider:active::-webkit-slider-thumb { transform: scaleY(1.2) scaleX(0.9); }
.slider::-moz-range-thumb { transition: transform 150ms var(--ease-emphasis), box-shadow 150ms var(--ease-out); }
.slider:hover::-moz-range-thumb { transform: scaleY(1.12); box-shadow: 0 2px 10px rgb(0 0 0 / 0.55), var(--glow-brass-soft); }

/* Checklist boxes: the tick scales in from 0.25 rather than flicking on */
.check__box { transition: background var(--dur-fast), border-color var(--dur-fast), box-shadow var(--dur-fast); }
.check:hover .check__box { border-color: rgb(var(--brass-rgb) / 0.6); }
.check input:checked + .check__box { box-shadow: var(--glow-brass-soft); }
.check input + .check__box::after { transition: opacity 120ms var(--ease-out), transform 200ms var(--ease-rise); }
.check input:not(:checked) + .check__box::after {
  content: ""; position: absolute; left: 50%; top: 46%;
  width: 4px; height: 8px; border: solid var(--brass-ink); border-width: 0 2px 2px 0;
  opacity: 0;
  transform: translate(-50%, -55%) rotate(45deg) scale(0.25);
}
.check input:checked + .check__box::after { opacity: 1; transform: translate(-50%, -55%) rotate(45deg) scale(1); }

/* Chips: the instrument marker warms to brass on hover */
.chip { transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out), transform var(--dur-lift) var(--ease-emphasis); }
.chip:hover { border-color: rgb(var(--brass-rgb) / 0.5); transform: translateY(-1px); }
.chip::before { transition: background var(--dur-fast) var(--ease-out); }
.chip:hover::before { background: var(--brass); }
.cat-pill { transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out), transform var(--dur-lift) var(--ease-emphasis); }
.cat-pill:hover { border-color: rgb(var(--brass-rgb) / 0.5); background: rgb(var(--paper-rgb) / 0.1); transform: translateY(-1px); }


/* ==========================================================================
   9. MODAL & COMMAND PALETTE
   ========================================================================== */
/* The backdrop blur ramps rather than snapping on */
@keyframes overlay-in {
  from { opacity: 0; backdrop-filter: blur(0px); -webkit-backdrop-filter: blur(0px); }
  to   { opacity: 1; backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); }
}
.modal__overlay { animation: overlay-in var(--dur-slow) var(--ease-out) both; }

/* The twelve-cell strip along the dialog top edge draws itself in */
@keyframes cadence-cell-in {
  from { opacity: 0; transform: scaleX(0); }
  to   { opacity: 1; transform: none; }
}
.modal__cadence i {
  transform-origin: left center;
  animation: cadence-cell-in 300ms var(--ease-emphasis) both;
}
.modal__cadence i:nth-child(1)  { animation-delay: 120ms; }
.modal__cadence i:nth-child(2)  { animation-delay: 145ms; }
.modal__cadence i:nth-child(3)  { animation-delay: 170ms; }
.modal__cadence i:nth-child(4)  { animation-delay: 195ms; }
.modal__cadence i:nth-child(5)  { animation-delay: 220ms; }
.modal__cadence i:nth-child(6)  { animation-delay: 245ms; }
.modal__cadence i:nth-child(7)  { animation-delay: 270ms; }
.modal__cadence i:nth-child(8)  { animation-delay: 295ms; }
.modal__cadence i:nth-child(9)  { animation-delay: 320ms; }
.modal__cadence i:nth-child(10) { animation-delay: 345ms; }
.modal__cadence i:nth-child(11) { animation-delay: 370ms; }
.modal__cadence i:nth-child(12) { animation-delay: 395ms; }

/* Dialog contents settle after the panel itself */
.modal__panel .modal__head,
.modal__panel .modal__body,
.modal__panel .modal__foot {
  animation: rise-in 360ms var(--ease-rise) both;
}
.modal__panel .modal__head { animation-delay: 90ms; }
.modal__panel .modal__body { animation-delay: 140ms; }
.modal__panel .modal__foot { animation-delay: 190ms; }

/* Palette results cascade; hovering nudges the row toward its marker */
.cmdk__list .cmdk__item { animation: opt-in 220ms var(--ease-emphasis) both; }
.cmdk__list .cmdk__item:nth-child(1) { animation-delay: 10ms; }
.cmdk__list .cmdk__item:nth-child(2) { animation-delay: 35ms; }
.cmdk__list .cmdk__item:nth-child(3) { animation-delay: 60ms; }
.cmdk__list .cmdk__item:nth-child(4) { animation-delay: 85ms; }
.cmdk__list .cmdk__item:nth-child(n+5) { animation-delay: 105ms; }
.cmdk__item { transition: background var(--dur-fast) var(--ease-out), padding-left var(--dur-fast) var(--ease-emphasis); }
.cmdk__item:hover { background: rgb(var(--paper-rgb) / 0.05); padding-left: calc(var(--s-4) + 3px); }
.cmdk__item.is-active::before { transition: height var(--dur-base) var(--ease-emphasis); }


/* ==========================================================================
   10. AMBIENT LIFE
   Three very slow, very low-amplitude loops. All sit behind content and none
   competes with anything a person is reading.
   ========================================================================== */
/* The hero brass wash drifts, so the panel is never perfectly static */
@keyframes hero-drift {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50%      { transform: translate3d(-1.5%, 1%, 0) scale(1.06); }
}
.hero::before {
  content: "";
  position: absolute; inset: -12%;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(45% 45% at 80% 15%, rgb(var(--brass-rgb) / 0.14), transparent 70%);
  animation: hero-drift 24s ease-in-out infinite;
}

/* The grain shifts by exactly one tile over a long cycle, so it never seams */
@keyframes grain-shift {
  from { background-position: 0 0; }
  to   { background-position: 180px 180px; }
}
.hero__grain { animation: grain-shift 32s linear infinite; }

/* Empty-state strips breathe, so a blank screen still looks switched on */
@keyframes strip-breathe {
  0%, 100% { opacity: 0.45; transform: translateY(0); }
  50%      { opacity: 1; transform: translateY(-2px); }
}
.empty__strip i { animation: strip-breathe 2.4s ease-in-out infinite; }
.empty__strip i:nth-child(1) { animation-delay: 0ms; }
.empty__strip i:nth-child(2) { animation-delay: 120ms; }
.empty__strip i:nth-child(3) { animation-delay: 240ms; }
.empty__strip i:nth-child(4) { animation-delay: 360ms; }
.empty__strip i:nth-child(5) { animation-delay: 480ms; }


/* ==========================================================================
   11. FIGURES
   ========================================================================== */
/* The hero numeral carries a faint brass bloom, stronger while inspected */
.hero__int { text-shadow: 0 0 34px rgb(var(--brass-rgb) / 0.22); transition: text-shadow var(--dur-slow) var(--ease-out); }
.hero__figure:hover .hero__int { text-shadow: 0 0 44px rgb(var(--brass-rgb) / 0.4); }

/* Worksheet rows */
.ws__row { transition: background var(--dur-lift) var(--ease-out), opacity var(--dur-base) var(--ease-out); }
.ws__row:hover { background: rgb(var(--paper-rgb) / 0.02); }
.ws__row dd { transition: color var(--dur-base) var(--ease-out); }
.ws__row--hero dd { text-shadow: 0 0 30px rgb(var(--brass-rgb) / 0.2); transition: color var(--dur-base) var(--ease-out), text-shadow var(--dur-slow) var(--ease-out); }

/* The status peek keeps its existing reveal; the bar fill is what animates */
.peek__bar i { transition: width var(--dur-slow) var(--ease-rise); }


/* ==========================================================================
   12. REDUCED MOTION
   Movement off, state cues kept. Colour, shadow and the leading-edge rules all
   still change on hover, so nothing added above is motion-only feedback.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .screen:not([hidden]) > *,
  .screen:not([hidden]) .hero__label,
  .screen:not([hidden]) .hero__figure,
  .screen:not([hidden]) .hero__note,
  .screen:not([hidden]) .hero__actions,
  .screen:not([hidden]) .statrow .stat,
  .screen:not([hidden]) .cadence__col,
  .sel__list.is-open .sel__opt,
  .cmdk__list .cmdk__item,
  .modal__cadence i,
  .modal__panel .modal__head,
  .modal__panel .modal__body,
  .modal__panel .modal__foot,
  .hero::before,
  .hero__grain,
  .empty__strip i {
    animation: none;
  }
  .empty__strip i { opacity: 1; transform: none; }

  .btn--primary::after,
  .btn--ghost::after,
  .btn--danger::after,
  .link-btn::after {
    transition: none;
  }
  /* Outline fills and underlines still appear, they just do not travel */
  .btn--ghost:hover:not(:disabled)::after,
  .btn--danger:hover:not(:disabled)::after,
  .link-btn:hover:not(:disabled)::after { transform: none; }
  .btn--primary:hover:not(:disabled)::after { transform: translateX(-180%) skewX(-18deg); }

  .stat::after,
  .roster::before,
  .pending__row::before,
  .table tbody tr > td:first-child::before {
    transition: none;
  }

  .tab:hover,
  .chip:hover,
  .cat-pill:hover,
  .preset:hover,
  .btn--ghost:hover:not(:disabled),
  .pending__row:hover .pending__owed,
  .iconbtn:hover:not(:disabled) { transform: none; }

  .mcell:hover:not(:disabled) .cell,
  .cadence--hero .cadence__col:hover .cadence__seg,
  .cadence--hero .cadence__col.is-hot .cadence__seg,
  .ministrip i:hover,
  .roster:hover .seats i.is-taken { transform: none; }

  .mcell .cell,
  .switch__track::after,
  .check input + .check__box::after,
  .peek__bar i,
  .wordmark__mark i,
  .hero__int,
  .ws__row--hero dd { transition: none; }

  .cadence--hero .cadence__columns:hover .cadence__col { opacity: 1; }
}


/* ==========================================================================
   13. NEW STATUS MARK: ONLINE PAYMENT
   Lives in this layer so the shell stays untouched. Not motion, but the same
   additive-override arrangement applies.

   Reading of the motif: an online payment IS settled, so it keeps the brass
   ground and the dark check that every paid month carries. What separates it
   is a notch cut from the top-right corner, in the surface colour, so the
   square reads as a digital receipt rather than a second colour in the
   palette. The notch survives down to the 8px mini-strip, and status is never
   carried by colour alone.

   Requires the app to emit `cell--online` / `m-online`; until the data layer
   supplies a channel, nothing renders with these classes.
   ========================================================================== */
.cell--online {
  background: var(--brass);
}
/* The check, identical to cell--paid so the paid family stays consistent */
.cell--online::after {
  content: "";
  position: absolute; left: 50%; top: 46%;
  width: 26%; height: 50%;
  border: solid var(--brass-ink);
  border-width: 0 2px 2px 0;
  transform: translate(-50%, -55%) rotate(45deg);
}
/* The notch: a triangle in the surface colour clipped out of the top-right */
.cell--online::before {
  content: "";
  position: absolute; top: 0; right: 0;
  width: 40%; height: 40%;
  background: var(--ink-800);
  clip-path: polygon(100% 0, 100% 100%, 0 0);
}
/* On surfaces that are not the table ground, the notch follows the surface */
.legend .cell--online::before,
.hpanel .cell--online::before { background: var(--ink-800); }
.toast .cell--online::before { background: var(--float-solid); }
/* Smaller instances get a proportionally lighter check, as cell--paid does */
.legend .cell--online::after,
.hpanel .cell--online::after,
.toast .cell--online::after { border-width: 0 1.5px 1.5px 0; }

/* Mini-strip variant (dashboard pending balances) */
.ministrip .m-online {
  position: relative;
  background: rgb(var(--brass-rgb) / 0.8);
}
.ministrip .m-online::after {
  content: "";
  position: absolute; top: 0; right: 0;
  width: 45%; height: 45%;
  background: var(--ink-800);
  clip-path: polygon(100% 0, 100% 100%, 0 0);
}

/* Matrix hover parity with the other statuses */
.mcell:hover:not(:disabled) .cell--online { box-shadow: var(--glow-brass); }
