/* OWNER: Charlie. The shell's motion, release six, September 21 2026.
   Loaded after css/motion-tokens.css (Sophie), which owns every duration, every
   curve and the one reduced motion kill switch. Nothing here invents a token and
   nothing here repeats that kill switch.

   Scope, after the September 21 lane split: the PIN gate, the screen change, the
   bottom sheet and its scrim, the toast, press feedback on .btn .card .chip, the
   chip colour morph, and the light and dark cross fade. The Book, the Register,
   Today, the lists, the converter and the nav bars each carry their own motion in
   their own lane's file.

   Law: transform and opacity only, nothing delays input. The two colour rules here
   (chip state, theme cross fade) are paint only, never layout. */

/* ------------------------------------------------------------ PRESS FEEDBACK
   The whole product is judged by this. A press answers in 90ms and the release
   settles over 240ms, so a tap feels answered and a lift feels expensive. Only
   elements that are actually pressable move: a static .card or a .chip used as a
   label must never wobble under a finger that was only selecting text. */
.btn,
.pin-key,
a.card, button.card, .card[role="button"],
a.chip, button.chip, .chip[role="button"] {
  transition: transform var(--mo-base) var(--mo-ease);
}
.btn:active:not(:disabled):not([disabled]),
.pin-key:active,
a.card:active, button.card:active, .card[role="button"]:active,
a.chip:active, button.chip:active, .chip[role="button"]:active {
  transition-duration: var(--mo-instant);
  transform: scale(.975);
}
/* A key on a numeric pad is small, so it takes a deeper press than a wide button. */
.pin-key:active { transform: scale(.94); }

/* ------------------------------------------------------- STATE COLOUR MORPHS
   A chip changing meaning (booked to arrived, waiting to offered) crossfades its
   colours instead of snapping. Paint only, no layout, no transform. */
.chip,
.btn {
  transition:
    transform var(--mo-base) var(--mo-ease),
    background-color var(--mo-fast) var(--mo-ease),
    border-color var(--mo-fast) var(--mo-ease),
    color var(--mo-fast) var(--mo-ease);
}
.btn:active:not(:disabled):not([disabled]) { transition-duration: var(--mo-instant); }

/* ------------------------------------------------------------ THE PIN GATE
   The gate repaints itself on every key, so nothing here may animate the card or
   the keypad: every digit would replay it. Only the newest dot carries the pop,
   which is why the selector is the last filled dot rather than all filled dots.
   :has() is unsupported on an old iPad; the whole rule is then dropped and the
   dots simply fill, which is the correct degradation. */
.pin-dot { transition: background-color var(--mo-instant) var(--mo-ease), border-color var(--mo-instant) var(--mo-ease); }
.pin-dot[data-on="1"]:has(+ .pin-dot:not([data-on])),
.pin-dot[data-on="1"]:last-child {
  animation: mo-dot-pop var(--mo-fast) var(--mo-spring);
}
@keyframes mo-dot-pop {
  from { transform: scale(.55); }
  60%  { transform: scale(1.18); }
  to   { transform: scale(1); }
}

/* A wrong PIN. js/motion.js sets the attribute off the failed try counter, never
   off the note's wording. Small travel, no rotation: a refusal, not a tantrum. */
.pin-card[data-mo-shake] { animation: mo-shake var(--mo-base) var(--mo-ease-out); }
@keyframes mo-shake {
  0%, 100% { transform: translateX(0); }
  15% { transform: translateX(-7px); }
  35% { transform: translateX(6px); }
  55% { transform: translateX(-4px); }
  78% { transform: translateX(2px); }
}

/* The right PIN. js/motion.js re anchors the gate the shell just discarded and
   this lifts it off the app. pointer-events stays off, so the first tap on the
   revealed screen lands even while it is still fading. */
/* The lifting ghost keeps the gate's own root layout (css/design.css .pin) without being a .pin. */
.pin-ghost { flex: 1 1 auto; width: 100%; min-height: 100dvh; display: flex; align-items: center; justify-content: center; padding: 24px; background: var(--bg); }
.mo-gate-out {
  position: fixed; inset: 0; z-index: 70; pointer-events: none;
  animation: mo-gate-out var(--mo-base) var(--mo-ease-in) forwards;
}
@keyframes mo-gate-out {
  from { opacity: 1; transform: scale(1); }
  to   { opacity: 0; transform: scale(1.035); }
}

/* ---------------------------------------------------------- THE SCREEN CHANGE
   The View Transition hook lives in js/app.js at the route mount. Naming .screen
   pulls the screen out of the root snapshot, so the nav holds still and only the
   screen crossfades, with the new one rising the same 10px every entrance in this
   product uses. Without the API the screens simply swap, which is what a browser
   that has never heard of this does anyway. */
.screen { view-transition-name: mo-screen; }
::view-transition-old(root), ::view-transition-new(root) { animation-duration: var(--mo-fast); }
::view-transition-old(mo-screen) {
  animation: mo-fade-out var(--mo-fast) var(--mo-ease-in) both;
}
::view-transition-new(mo-screen) {
  animation: mo-screen-in var(--mo-base) var(--mo-ease-out) both;
}
@keyframes mo-fade-out { to { opacity: 0; } }
@keyframes mo-screen-in {
  from { opacity: 0; transform: translateY(var(--mo-rise)); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ------------------------------------------------------- THE SHEET AND SCRIM
   The scrim fades, the sheet springs up under it. .sheet is the panel, never the
   wrapper, so the scrim does not travel with it. An exit needs the owning screen
   to keep the node in the DOM for a beat; today every screen removes it on the
   spot, so there is no exit to animate. Noted to Sophie as a hook request. */
.sheet-scrim { animation: mo-fade-in var(--mo-fast) var(--mo-ease) both; }
.sheet { animation: mo-sheet-up var(--mo-base) var(--mo-ease-out); }
/* A centered sheet settles in place instead of rising from the bottom. */
.sheet-center .sheet { animation: mo-sheet-settle var(--mo-base) var(--mo-ease-out); }
@keyframes mo-sheet-settle { from { opacity: 0; transform: translateY(var(--mo-rise)) scale(.98); } to { opacity: 1; transform: none; } }
@keyframes mo-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes mo-sheet-up {
  from { transform: translateY(100%); }
  to   { transform: translateY(0); }
}

/* -------------------------------------------------------------------- TOAST */
.toast { animation: mo-toast-in var(--mo-base) var(--mo-ease-out); }
@keyframes mo-toast-in {
  from { opacity: 0; transform: translateX(-50%) translateY(14px) scale(.96); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
}

/* ---------------------------------------------------- THE MARSHMALLOW WASH
   The one ambient element in the whole product, Marshmallow only. #app's own
   pink and sky pools already live on its background in css/design.css;
   animating that gradient would repaint the whole screen every frame. This is
   a second, purely decorative layer that only ever moves a transform. #app
   gets a local stacking context (isolation, no z-index number to coordinate
   with any other lane's overlay) so the wash sits between #app's own
   background and its real content, and nothing outside marshmallow ever sees
   it. 52s a cycle, opacity fixed at .3 (never animated), transform only. */
:root[data-theme="marshmallow"] #app {
  position: relative;
  isolation: isolate;
  /* Sophie, integration: the drift layer is wider than the app and moves; clip it at the app's own edges so it never
     widens the page (iPhone Safari pans into hidden overflow). clip, not hidden, so #app never becomes a scroll box;
     hidden stays as the fallback for older Safari. Fixed sheets and toasts are not clipped by it. */
  overflow: hidden;
  overflow: clip;
}
:root[data-theme="marshmallow"] #app::before {
  content: '';
  position: absolute;
  inset: -20%;
  z-index: -1;
  pointer-events: none;
  opacity: .3;
  background:
    radial-gradient(36% 30% at 22% 20%, #F9DEE5 0%, rgba(249, 222, 229, 0) 70%),
    radial-gradient(32% 28% at 80% 76%, #C0E6F0 0%, rgba(192, 230, 240, 0) 70%);
  animation: mo-marshmallow-drift 52s var(--mo-ease) infinite alternate;
}
@keyframes mo-marshmallow-drift {
  from { transform: translate(-2.5%, -2%) scale(1); }
  to   { transform: translate(2.5%, 2%) scale(1.02); }
}

/* --------------------------------------------------------- LIGHT AND DARK
   Every surface crossfades instead of snapping. This is the one place a broad
   selector is right, and it is only live for the length of the change: js/motion.js
   puts the attribute on for one beat when a theme control is pressed, so nothing
   here costs anything during ordinary work. Paint only. */
:root[data-mo-theming] *,
:root[data-mo-theming] *::before,
:root[data-mo-theming] *::after {
  transition:
    background-color var(--mo-base) var(--mo-ease),
    border-color var(--mo-base) var(--mo-ease),
    color var(--mo-base) var(--mo-ease),
    fill var(--mo-base) var(--mo-ease),
    stroke var(--mo-base) var(--mo-ease),
    box-shadow var(--mo-base) var(--mo-ease) !important;
}
