body {
  margin: 0;
  font-family: "Hanken Grotesk", "Helvetica Neue", Helvetica, Arial, sans-serif;
  color: #1f1915;
  background: #ffffff;
  -webkit-font-smoothing: antialiased;
}
a {
  color: #4a6aff;
  text-decoration: none;
}
a:hover {
  color: #3a56db;
}
* {
  box-sizing: border-box;
}

/* ── Motion tokens ─────────────────────────────────────────────
   Entrances: Hardcut (shared/hardcut-motion). Hand-triggered: canon. */
:root {
  /* Hardcut easings, measured from the reel */
  --ease-settle: cubic-bezier(0.45, 0.05, 0.3, 1);
  --ease-fill: cubic-bezier(0.25, 0.1, 0.35, 1);
  --ease-rise: cubic-bezier(0.65, 0, 0.55, 1);
  --ease-snap: cubic-bezier(0.7, 0, 0.2, 1);
  /* canon UI-state pair */
  --ease-enter: cubic-bezier(0.05, 0.7, 0.1, 1);
  --ease-exit: cubic-bezier(0.3, 0, 0.8, 0.15);

  /* entrance durations — distinct names so canon's --dur-base stays 200ms */
  --dur-settle: 600ms;
  --dur-hero: 900ms;
  --dur-hold: 1900ms;
  /* canon UI-state scale, unchanged */
  --dur-fast: 160ms;
  --dur-base: 200ms;
  --dur-slow: 240ms;

  --stagger-char: 40ms;
  --stagger-row: 80ms;
  --stagger-object: 150ms;
  --d-enter: 24px;
  --d-slide: 48px;
}

/* ── Entrances: hero, then secondaries at +150ms, then detail ── */
@keyframes mRise {
  from {
    opacity: 0;
    transform: translateY(var(--d-slide));
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes mEnter {
  from {
    opacity: 0;
    transform: translateY(var(--d-enter));
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes mNum {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes mRow {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
/* Real bug found and fixed: this used to animate stroke-dashoffset from
   var(--dash, 0) to a hardcoded 0 - since none of the 4 real progress rings
   (Dashboard's Next refill/Refills remaining/Check-in due, Visits page's
   Request a follow-up) ever set a --dash custom property, `from` silently
   resolved to its `, 0` fallback and `to` was always 0 regardless, so every
   ring's "wipe reveal" animation settled on stroke-dashoffset: 0 (= fully
   drawn) no matter what fraction Blade had actually computed - a CSS
   animation's end state always wins over the plain SVG attribute Blade sets
   (same root cause already called out and fixed once for a different rule,
   see keen-custom.css's own .metric-ring-circle comment). Animating opacity
   instead keeps the "ring arrives rather than pops in" entrance feel
   without ever touching stroke-dashoffset, so the real per-ring value each
   page computes is what actually renders. */
@keyframes mRing {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
@keyframes mBar {
  from {
    width: 0;
  }
  to {
    width: var(--w, 100%);
  }
}

.m-rise {
  animation: mRise var(--dur-hero) var(--ease-rise) both;
}
.m-stagger > * {
  animation: mEnter var(--dur-settle) var(--ease-settle) both;
}
.m-stagger > *:nth-child(1) {
  animation-delay: 0ms;
}
.m-stagger > *:nth-child(2) {
  animation-delay: 150ms;
}
.m-stagger > *:nth-child(3) {
  animation-delay: 300ms;
}
.m-stagger > *:nth-child(4) {
  animation-delay: 450ms;
}
.m-stagger > *:nth-child(5) {
  animation-delay: 600ms;
}
.m-stagger > *:nth-child(6) {
  animation-delay: 750ms;
}
.m-stagger > *:nth-child(7) {
  animation-delay: 900ms;
}
.m-stagger > *:nth-child(8) {
  animation-delay: 1050ms;
}

/* numeral settle — the countdown number arrives after its card */
.m-num {
  animation: mNum var(--dur-settle) var(--ease-fill) both;
  animation-delay: 200ms;
}
/* wipe reveal — rings and bars draw on, they never pop in filled */
.m-ring circle:last-of-type {
  animation: mRing var(--dur-settle) var(--ease-fill) both;
  animation-delay: 250ms;
}
.m-bar > div {
  animation: mBar var(--dur-settle) var(--ease-fill) both;
  animation-delay: 250ms;
}
/* list rows tick in at the row stagger */
.m-rows > * {
  animation: mRow var(--dur-base) var(--ease-settle) both;
}
.m-rows > *:nth-child(1) {
  animation-delay: 400ms;
}
.m-rows > *:nth-child(2) {
  animation-delay: 480ms;
}
.m-rows > *:nth-child(3) {
  animation-delay: 560ms;
}
.m-rows > *:nth-child(4) {
  animation-delay: 640ms;
}
.m-rows > *:nth-child(5) {
  animation-delay: 720ms;
}

/* ── Hand-triggered state: canon durations, never the reel's ──── */
.m-card {
  transition:
    transform var(--dur-base) var(--ease),
    box-shadow var(--dur-base) var(--ease);
}
.m-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 24px rgba(31, 25, 21, 0.08);
}
.m-tap {
  transition:
    background var(--dur-base) var(--ease),
    color var(--dur-base) var(--ease),
    border-color var(--dur-base) var(--ease);
}
.m-press:active {
  transform: scale(0.985);
  transition: transform var(--dur-fast) var(--ease-snap);
}
:focus-visible {
  outline: 2px solid #4a6aff;
  outline-offset: 2px;
  transition: outline-color var(--dur-fast) var(--ease);
}

/* selectable pills: filters and tabs */
.m-pill {
  cursor: pointer;
  transition:
    background var(--dur-base) var(--ease),
    color var(--dur-base) var(--ease),
    border-color var(--dur-base) var(--ease);
}
.m-pill.on {
  background: #4a6aff;
  border-color: #4a6aff;
  color: #fff;
}
/* form fields read and behave as fields */
.m-field {
  transition:
    border-color var(--dur-fast) var(--ease),
    box-shadow var(--dur-fast) var(--ease);
}
.m-field.on {
  border-color: #4a6aff;
  box-shadow: 0 0 0 3px rgba(74, 106, 255, 0.18);
}

/* drawer and chat traverse the viewport — canon --dur-slow */
.m-drawer {
  transition: transform var(--dur-slow) var(--ease-enter);
}
.m-scrim {
  transition: opacity var(--dur-slow) var(--ease-enter);
}
.m-sheet {
  transition:
    transform var(--dur-slow) var(--ease-enter),
    opacity var(--dur-fast) var(--ease-enter);
}

/* ── Pointer affordance on hand-built controls ─────────────── */
.navitem,
.m-tap,
.m-pill,
.m-link,
[data-modal],
[data-modal-close] {
  cursor: pointer;
}

/* ── Hover states: nav items, tap targets, pills, text actions ── */
.navitem:not(.on) {
  transition:
    background var(--dur-base) var(--ease),
    color var(--dur-base) var(--ease);
}
.navitem:not(.on):hover {
  background: #f8f6f1;
}
.m-tap:hover {
  filter: brightness(0.96);
}
.m-pill:not(.on):hover {
  border-color: #4a6aff;
  color: #4a6aff;
}
.m-link {
  transition: color var(--dur-fast) var(--ease);
}
.m-link:hover {
  color: #3a56db !important;
}
.m-row {
  transition: background var(--dur-base) var(--ease);
}
.m-row:hover {
  background: #f8f6f1;
  border-radius: 12px;
}

/* ── Navigation enters with the page ────────────────────────── */
@keyframes mSide {
  from {
    opacity: 0;
    transform: translateX(-16px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
aside,
nav {
  animation: mSide var(--dur-settle) var(--ease-settle) both;
}

/* ── FAB pops in after the content settles ──────────────────── */
.m-fab {
  animation: mRise var(--dur-settle) var(--ease-settle) both;
  animation-delay: 600ms;
}

/* ── In-progress marker: the live dot breathes until it is done ── */
@keyframes mPulse {
  0%,
  100% {
    box-shadow: 0 0 0 0 rgba(74, 106, 255, 0.45);
  }
  50% {
    box-shadow: 0 0 0 7px rgba(74, 106, 255, 0);
  }
}
.m-pulse {
  animation: mPulse 1.6s var(--ease-settle) 19;
}

/* ── Lightbox: centered on desktop, bottom sheet on phone ────── */
.portal-modal {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(31, 25, 21, 0.45);
  overflow-y: auto;
  animation: mScrim var(--dur-fast) var(--ease) both;
}
.portal-modal[hidden] {
  display: none;
}
.portal-modal > * {
  animation: mRise var(--dur-settle) var(--ease-settle) both;
}
@keyframes mScrim {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
@media (max-width: 480px) {
  .portal-modal {
    padding: 0;
    align-items: flex-end;
    overscroll-behavior: contain;
  }
  .portal-modal > * {
    width: 100% !important;
    max-height: 92vh;
    overflow-y: auto !important;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    border-radius: 20px 20px 0 0 !important;
    animation-name: mSheetUp;
  }
  @keyframes mSheetUp {
    from {
      opacity: 0;
      transform: translateY(24px);
    }
    to {
      opacity: 1;
      transform: none;
    }
  }
}

/* ── Menu enters from the side the button lives on ──────────── */
@keyframes mMenuR {
  from {
    opacity: 0;
    transform: translateX(44px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes mMenuL {
  from {
    opacity: 0;
    transform: translateX(-44px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* ── Reduced motion: everything arrives, nothing moves ────────── */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0ms !important;
    transition-duration: 0.01ms !important;
  }
  .m-card:hover {
    transform: none;
  }
  .portal-modal > * {
    animation: none;
  }
}
