/* Cookiezz motion system, one place for every micro-interaction.
 *
 * The tokens below are NOT new: they are the timings and curves already used
 * across the product (0.1s/0.12s transitions, expo-out for entrances, a slight
 * back-out overshoot for playful bits). Pulling them into variables means a
 * page opts into the house feel instead of hand-rolling its own.
 *
 * Nothing here changes layout, colour, spacing or type. Motion only.
 * Everything degrades to "no movement, fully visible" under
 * prefers-reduced-motion, and reveals fail OPEN if JS never runs.
 */
:root {
  /* durations, the product is snappy; resist making these longer */
  --mo-fast: 0.1s;    /* press / hover feedback */
  --mo-base: 0.16s;   /* state changes, toggles */
  --mo-enter: 0.42s;  /* things arriving on screen */

  /* curves, both already in use */
  --mo-out: cubic-bezier(0.16, 1, 0.3, 1);              /* expo-out: decisive arrival */
  --mo-back: cubic-bezier(0.175, 0.885, 0.32, 1.275);   /* slight overshoot */

  --mo-rise: 10px;    /* how far a revealing element travels */
}

/* ---------- 1. reveal on scroll ----------
 * Elements are only hidden once motion.js has confirmed it is running AND
 * motion is wanted (html.mo-on). Without JS, without this file, or with
 * reduced motion, content renders normally, it is never trapped at opacity 0. */
.mo-on [data-reveal] {
  opacity: 0;
  transform: translateY(var(--mo-rise));
  transition: opacity var(--mo-enter) var(--mo-out),
              transform var(--mo-enter) var(--mo-out);
  transition-delay: calc(var(--mo-i, 0) * 60ms); /* stagger inside a group */
  will-change: opacity, transform;
}
.mo-on [data-reveal].is-in {
  opacity: 1;
  transform: none;
}
/* Once it has arrived: stop claiming compositor memory, and hand the transition
 * back to whatever interaction rule owns the element. Without this, a card that
 * is both [data-reveal] and .mo-lift keeps the 0.42s entrance timing for its
 * hover, the lift would crawl and box-shadow would not animate at all, because
 * the reveal selector outranks .mo-lift on specificity. */
.mo-on [data-reveal].is-done {
  will-change: auto;
  transition: none;
}
.mo-on [data-reveal].is-done.mo-lift {
  transition: transform var(--mo-fast) var(--mo-out),
              box-shadow var(--mo-fast) var(--mo-out);
}
.mo-on [data-reveal].is-done.mo-press {
  transition: transform var(--mo-fast) var(--mo-out),
              box-shadow var(--mo-fast) var(--mo-out),
              background-color var(--mo-fast) linear;
}
.mo-on [data-reveal].is-done.mo-soft {
  transition: background-color var(--mo-base) linear,
              border-color var(--mo-base) linear,
              color var(--mo-base) linear,
              opacity var(--mo-base) linear;
}

/* ---------- 2. cards: lift on hover, sink on press ----------
 * Mirrors the existing button language, the hard offset shadow grows as the
 * card lifts off the page, then collapses when pressed into it. */
.mo-lift {
  transition: transform var(--mo-fast) var(--mo-out),
              box-shadow var(--mo-fast) var(--mo-out);
}
@media (hover: hover) {
  .mo-lift:hover { transform: translate(-2px, -2px); }
}
.mo-lift:active { transform: translate(1px, 1px); }

/* ---------- 3. buttons ----------
 * Formalises what .btn already does so new buttons inherit it instead of
 * re-declaring. Applied via .mo-press on elements that lack their own. */
.mo-press {
  transition: transform var(--mo-fast) var(--mo-out),
              box-shadow var(--mo-fast) var(--mo-out),
              background-color var(--mo-fast) linear;
}
@media (hover: hover) {
  .mo-press:hover { transform: translate(-1px, -1px); }
}
.mo-press:active { transform: translate(2px, 2px); }
.mo-press:disabled,
.mo-press[aria-disabled="true"] { transform: none; }

/* keyboard focus should feel as immediate as a click, never animated away */
.mo-press:focus-visible,
.mo-lift:focus-visible { transition-duration: 0s; }

/* ---------- 4. modals / sheets ----------
 * Scales up from just under full size so it reads as arriving from the page,
 * not flying in from nowhere. Backdrop fades independently. */
.mo-backdrop {
  opacity: 0;
  transition: opacity var(--mo-base) linear;
}
.mo-backdrop.is-open { opacity: 1; }

.mo-modal {
  opacity: 0;
  transform: translateY(8px) scale(0.98);
  transition: opacity var(--mo-base) var(--mo-out),
              transform var(--mo-base) var(--mo-out);
}
.mo-modal.is-open {
  opacity: 1;
  transform: none;
}

/* The dashboard/console overlays toggle a `.hidden { display: none }` class.
 * A transition cannot run across a display change, but an animation re-fires
 * every time the element becomes displayed, so these hook the existing
 * markup with no change to the show/hide JS. */
@keyframes mo-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes mo-modal-in {
  from { opacity: 0; transform: translateY(8px) scale(0.98); }
  to   { opacity: 1; transform: none; }
}
/* Three overlay conventions exist in the product, all display-toggled:
 *   .modal-overlay + .hidden   (dashboard, console)
 *   .overlay       + .show     (add-links icon picker)
 *   .modal-bg      + .show     (event vote)
 * They get identical motion so a modal feels the same wherever it opens. */
.mo-on .modal-overlay:not(.hidden),
.mo-on .overlay.show,
.mo-on .modal-bg.show {
  animation: mo-fade-in var(--mo-base) linear both;
}
.mo-on .modal-overlay:not(.hidden) > .modal,
.mo-on .overlay.show > *,
.mo-on .modal-bg.show > .modal {
  animation: mo-modal-in var(--mo-base) var(--mo-out) both;
}

/* Closing: a page adds .closing for one beat when it hides an overlay, so the
 * overlay stays displayed long enough to play the reverse of the entrance.
 * Pages that never add the class keep the old instant hide. */
@keyframes mo-fade-out { from { opacity: 1; } to { opacity: 0; } }
@keyframes mo-modal-out {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(8px) scale(0.98); }
}
@keyframes mo-dock-out {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateX(-24px); }
}
.mo-on .modal-overlay.hidden.closing { display: flex !important; pointer-events: none; animation: mo-fade-out var(--mo-base) linear both; }
.mo-on .modal-overlay.hidden.closing > .modal { animation: mo-modal-out var(--mo-base) var(--mo-out) both; }
.mo-on .modal-overlay.docked.hidden.closing > .modal { animation: mo-dock-out var(--mo-base) var(--mo-out) both; }

/* ---------- 5. idle motion ----------
 * A slow, shallow breath. Deliberately almost-subliminal: it should read as
 * "alive", never as "animating". Pauses on hover so it does not fight a
 * deliberate interaction. */
@keyframes mo-idle-float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-3px); }
}
.mo-idle {
  animation: mo-idle-float 4.5s ease-in-out infinite;
}
.mo-idle:hover { animation-play-state: paused; }

/* ---------- 6. state transitions ----------
 * For content that swaps in place (tab panels, loaded lists, empty→filled). */
.mo-swap {
  animation: mo-swap-in var(--mo-base) var(--mo-out) both;
}
@keyframes mo-swap-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: none; }
}

/* colour/number changes that should not snap */
.mo-soft {
  transition: background-color var(--mo-base) linear,
              border-color var(--mo-base) linear,
              color var(--mo-base) linear,
              opacity var(--mo-base) linear;
}

/* ---------- 7. reduced motion ----------
 * The class is removed by motion.js too, but this block is the real guarantee:
 * it holds even if the script is cached, blocked or broken. */
@media (prefers-reduced-motion: reduce) {
  .mo-on [data-reveal],
  .mo-on [data-reveal].is-in {
    opacity: 1;
    transform: none;
    transition: none;
    transition-delay: 0s;
  }
  .mo-lift,
  .mo-press,
  .mo-backdrop,
  .mo-modal,
  .mo-soft {
    transition-duration: 0.01ms;
  }
  .mo-lift:hover,
  .mo-lift:active,
  .mo-press:hover,
  .mo-press:active {
    transform: none;
  }
  .mo-idle,
  .mo-swap {
    animation: none;
  }
  .mo-modal { opacity: 1; transform: none; }
}
