/* Habitus Mind motion system: redesign 2026 (DESIGN.md "Motion").
 *
 * Shared by the public site and the app. Load after tokens.css. Nothing
 * else loads it yet (R1a); /brand/ demonstrates every utility.
 *
 * Rules this file keeps, and every rule built on it must keep:
 *  - Only transform, opacity, clip-path and background-position animate.
 *    One named exception: stroke-dashoffset, for SVG line-draws (the logo
 *    and module icons). It repaints one small SVG and moves no layout.
 *  - No transition: all. Properties are always named.
 *  - Transitions, not keyframes, for anything a user triggers, so a
 *    second input reverses from wherever the first one got to.
 *  - Hidden-before-reveal states only exist once motion.js has armed the
 *    page (html.hm-armed) or the browser runs scroll timelines. With no
 *    JS and no scroll timelines, everything is simply visible.
 *  - Reduced motion (the OS setting, or data-motion="reduce" on <html>,
 *    which /brand/ uses to preview it; data-motion="full" forces motion
 *    on for the same preview): every entrance becomes a 150ms opacity
 *    change and every loop stops on its final frame. The final frame is
 *    the base style of every loop, so "stop" is just animation: none.
 */

:root {
  --hm-dur-1: 120ms;
  --hm-dur-2: 240ms;
  --hm-dur-3: 420ms;
  --hm-dur-4: 800ms;
  --hm-ease-out: cubic-bezier(.2, .8, .2, 1);
  --hm-ease-inout: cubic-bezier(.65, 0, .35, 1);
  --hm-ease-spring: cubic-bezier(.34, 1.56, .64, 1);
  --hm-stagger: 60ms;

  /* What an entrance starts from. Reduced motion zeroes these, which is
     what turns every entrance below into an opacity change. */
  --hm-rise: 16px;
  --hm-rise-sm: 8px;
  --hm-scale-from: 0.94;
  --hm-pop-from: 0.6;
  --hm-wipe-from: inset(0 100% 0 0);
  --hm-mask-from: inset(0 0 100% 0);
  --hm-draw-from: 1;
  --hm-enter-dur: var(--hm-dur-3);
  --hm-enter-ease: var(--hm-ease-out);
  --hm-fill-dur: var(--hm-dur-4);
  --hm-m: 1;
}

:root[data-motion="reduce"] {
  --hm-rise: 0px;
  --hm-rise-sm: 0px;
  --hm-scale-from: 1;
  --hm-pop-from: 1;
  --hm-wipe-from: inset(0 0 0 0);
  --hm-mask-from: inset(0 0 0 0);
  --hm-draw-from: 0;
  --hm-enter-dur: 150ms;
  --hm-enter-ease: linear;
  --hm-fill-dur: 150ms;
  --hm-m: 0;
}
@media (prefers-reduced-motion: reduce) {
  :root:not([data-motion="full"]) {
    --hm-rise: 0px;
    --hm-rise-sm: 0px;
    --hm-scale-from: 1;
    --hm-pop-from: 1;
    --hm-wipe-from: inset(0 0 0 0);
    --hm-mask-from: inset(0 0 0 0);
    --hm-draw-from: 0;
    --hm-enter-dur: 150ms;
    --hm-enter-ease: linear;
    --hm-fill-dur: 150ms;
    --hm-m: 0;
  }
}

/* ---------- Entrances (class-triggered: .is-in) ----------
   motion.js adds .is-in when the element enters the viewport (one
   IntersectionObserver per page) or when a page calls HMMotion.play(). */

.hm-armed .hm-reveal,
.hm-armed .hm-reveal-scale,
.hm-armed .hm-reveal-wipe,
.hm-armed .hm-reveal-mask,
.hm-armed .hm-pop {
  transition-property: opacity, transform, clip-path;
  transition-duration: var(--hm-enter-dur);
  transition-timing-function: var(--hm-enter-ease);
  transition-delay: calc(var(--hm-i, 0) * var(--hm-stagger));
}
.hm-armed .hm-pop { transition-timing-function: var(--hm-ease-spring); }

.hm-armed .hm-reveal:not(.is-in)       { opacity: 0; transform: translateY(var(--hm-rise)); }
.hm-armed .hm-reveal-scale:not(.is-in) { opacity: 0; transform: scale(var(--hm-scale-from)); }
.hm-armed .hm-reveal-wipe:not(.is-in)  { opacity: 0; clip-path: var(--hm-wipe-from); }
.hm-armed .hm-reveal-mask:not(.is-in)  { opacity: 0; clip-path: var(--hm-mask-from); transform: translateY(calc(var(--hm-rise) * 0.5)); }
.hm-armed .hm-pop:not(.is-in)          { opacity: 0; transform: scale(var(--hm-pop-from)); }

.hm-reveal-wipe, .hm-reveal-mask { clip-path: inset(0 0 0 0); }

/* Stagger: children of .hm-stagger take their index from position, so
   markup never carries inline delays. Twelve is the most any list here
   animates; the rest arrive with the twelfth. */
.hm-stagger > :nth-child(2)  { --hm-i: 1; }
.hm-stagger > :nth-child(3)  { --hm-i: 2; }
.hm-stagger > :nth-child(4)  { --hm-i: 3; }
.hm-stagger > :nth-child(5)  { --hm-i: 4; }
.hm-stagger > :nth-child(6)  { --hm-i: 5; }
.hm-stagger > :nth-child(7)  { --hm-i: 6; }
.hm-stagger > :nth-child(8)  { --hm-i: 7; }
.hm-stagger > :nth-child(9)  { --hm-i: 8; }
.hm-stagger > :nth-child(10) { --hm-i: 9; }
.hm-stagger > :nth-child(11) { --hm-i: 10; }
.hm-stagger > :nth-child(n+12) { --hm-i: 11; }

/* ---------- Entrances (scroll-linked: .hm-scroll) ----------
   Where the browser has scroll timelines, add .hm-scroll next to an
   entrance class and the entrance is tied to scroll position instead of
   a one-shot class: no observer, no stuck-invisible state, reversible.
   motion.js leaves these elements alone when the timeline runs. */
@supports (animation-timeline: view()) {
  @keyframes hm-scroll-rise  { from { opacity: 0; transform: translateY(var(--hm-rise)); } }
  @keyframes hm-scroll-scale { from { opacity: 0; transform: scale(var(--hm-scale-from)); } }
  @keyframes hm-scroll-wipe  { from { opacity: 0; clip-path: var(--hm-wipe-from); } }

  .hm-scroll.hm-reveal       { animation: hm-scroll-rise linear both; }
  .hm-scroll.hm-reveal-scale { animation: hm-scroll-scale linear both; }
  .hm-scroll.hm-reveal-wipe  { animation: hm-scroll-wipe linear both; }
  .hm-scroll {
    animation-timeline: view();
    animation-range: entry calc(5% + var(--hm-i, 0) * 3%) cover calc(28% + var(--hm-i, 0) * 3%);
  }
  .hm-armed .hm-scroll { transition: none; }
  .hm-armed .hm-scroll:not(.is-in) { opacity: 1; transform: none; clip-path: inset(0 0 0 0); }

  /* Blog covers and similar: drift 8px against the scroll, transform only */
  @keyframes hm-drift { from { transform: translateY(-8px) scale(1.04); } to { transform: translateY(8px) scale(1.04); } }
  .hm-parallax { animation: hm-drift linear both; animation-timeline: view(); animation-range: cover; }
}

/* ---------- Line draw (SVG) ----------
   Put pathLength="1" on every path so one dash covers any length. */
.hm-draw :is(path, line, polyline, circle, rect) {
  stroke-dasharray: 1;
  stroke-dashoffset: 0;
}
.hm-armed .hm-draw :is(path, line, polyline, circle, rect) {
  transition: stroke-dashoffset var(--hm-dur-4) var(--hm-ease-inout);
  transition-delay: calc(var(--hm-i, 0) * var(--hm-stagger));
}
.hm-armed .hm-draw:not(.is-in) :is(path, line, polyline, circle, rect) { stroke-dashoffset: var(--hm-draw-from); }
.hm-draw.hm-draw-hover:is(:hover, :focus-visible) :is(path, line, polyline, circle, rect),
:is(:hover, :focus-visible) > .hm-draw.hm-draw-hover :is(path, line, polyline, circle, rect) {
  animation: hm-redraw var(--hm-dur-4) var(--hm-ease-inout);
}
@keyframes hm-redraw { from { stroke-dashoffset: var(--hm-draw-from); } to { stroke-dashoffset: 0; } }

/* ---------- Feedback (user-triggered) ---------- */
.hm-press {
  transition: transform var(--hm-dur-1) var(--hm-ease-out);
}
.hm-press:active { transform: scale(0.98); }

.hm-tilt {
  transition: transform var(--hm-dur-2) var(--hm-ease-spring);
}
.hm-tilt:active { transform: rotate(-2deg) scale(0.99); }

.hm-lift {
  transition: transform var(--hm-dur-2) var(--hm-ease-out);
}
@media (hover: hover) {
  .hm-lift:hover { transform: translateY(-3px); }
}

/* ---------- Meters and numbers ---------- */
.hm-num { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }

.hm-fill {
  transform-origin: left center;
  transform: scaleX(var(--hm-fill, 1));
}
.hm-armed .hm-fill {
  transition-property: transform, opacity;
  transition-duration: var(--hm-fill-dur);
  transition-timing-function: var(--hm-ease-out);
}
/* Motion: grows from zero. Reduced (--hm-m: 0): already full, fades in. */
.hm-armed .hm-fill:not(.is-in) {
  transform: scaleX(calc(var(--hm-fill, 1) * (1 - var(--hm-m))));
  opacity: var(--hm-m);
}

/* ---------- App surfaces ---------- */
@keyframes hm-enter { from { opacity: 0; transform: translateY(var(--hm-rise-sm)); } }
.hm-enter { animation: hm-enter var(--hm-dur-2) var(--hm-enter-ease) both; }

.hm-toast {
  transition-property: transform, opacity;
  transition-duration: var(--hm-dur-2);
  transition-timing-function: var(--hm-ease-out);
}
.hm-toast:not(.is-in) { opacity: 0; transform: translateY(calc(var(--hm-rise) + 24px)); }

/* ---------- Loops (final frame is the base style) ---------- */
@keyframes hm-shimmer { from { background-position: 150% 0; } to { background-position: -50% 0; } }
.hm-skeleton {
  background-color: var(--hm-surface-2);
  background-image: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.06) 50%, transparent 100%);
  background-size: 200% 100%;
  background-position: -50% 0;
  background-repeat: no-repeat;
  border-radius: var(--hm-r-1);
  animation: hm-shimmer 1.4s var(--hm-ease-inout) infinite;
}
:root[data-theme="light"] .hm-skeleton { background-image: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.7) 50%, transparent 100%); }

@keyframes hm-blink { 0%, 45% { opacity: 1; } 55%, 100% { opacity: 0; } }
.hm-caret { animation: hm-blink 1s steps(1) infinite; }

@keyframes hm-breathe { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(0.82); opacity: 0.55; } }
.hm-pulse { animation: hm-breathe 1.6s var(--hm-ease-inout) infinite; }

:root[data-motion="reduce"] :is(.hm-skeleton, .hm-caret, .hm-pulse, .hm-parallax, .hm-enter),
:root[data-motion="reduce"] .hm-draw.hm-draw-hover :is(path, line, polyline, circle, rect) {
  animation: none;
}
:root[data-motion="reduce"] .hm-scroll { animation: none; }
:root[data-motion="reduce"] .hm-enter { animation: hm-fade 150ms linear both; }
@keyframes hm-fade { from { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  :root:not([data-motion="full"]) :is(.hm-skeleton, .hm-caret, .hm-pulse, .hm-parallax),
  :root:not([data-motion="full"]) .hm-draw.hm-draw-hover :is(path, line, polyline, circle, rect),
  :root:not([data-motion="full"]) .hm-scroll {
    animation: none;
  }
  :root:not([data-motion="full"]) .hm-enter { animation: hm-fade 150ms linear both; }
}
