@charset "UTF-8";
/*!
 * Hash v1.0.0 — Motion Layer (CSS half of the animation + scroll system)
 *
 * Two ways to animate, same vocabulary:
 *   CSS only  — class="anim-2 anim-up"        (fires on load, zero JS)
 *   On scroll — data-reveal="up" data-delay="100"  (hash-scroll.js)
 *
 * The JS engine (hash.js) writes inline transforms, so it always beats
 * these classes while it is running. Everything here respects
 * prefers-reduced-motion.
 */

@layer hash.motion {
  :root {
    /* -- Duration -------------------------------------------------------- */
    --dur-1: 120ms;
    --dur-2: 220ms;
    --dur-3: 380ms;
    --dur-4: 600ms;
    --dur-5: 900ms;

    /* -- Easing — same curves as the JS easing table -------------------- */
    --ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);
    --ease-in: cubic-bezier(0.55, 0.06, 0.68, 0.19);
    --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
    --ease-back: cubic-bezier(0.34, 1.56, 0.64, 1);
    --ease-soft: cubic-bezier(0.4, 0, 0.2, 1);

    /* -- Reveal defaults ------------------------------------------------- */
    --reveal-shift: 3rem;
    --reveal-zoom: 0.94;
    --reveal-blur: 6px;
    --stagger: 80ms;
  }

  /*
  ====================================================================
    SCROLL REVEAL
    Markup:  <div class="inner n0-i1" data-reveal="up">
    JS adds .is-in when the element enters the viewport.
    No JS on the page? Add .no-js-visible to opt straight to the end state.
  ====================================================================
  */
  /*
    FAIL-SAFE REVEALS
    The hidden start state only applies once hash.js has booted and put
    .hash-ready on <html>. If JavaScript is blocked, fails to load, or throws
    before boot, every [data-reveal] element stays visible instead of leaving a
    blank page. The cost is a possible one-frame flash before the scripts at the
    end of <body> run; the alternative is invisible content, which is worse.
  */
  :where(.hash-ready) [data-reveal] {
    opacity: 0;
    will-change: transform, opacity;
    transition:
      opacity var(--reveal-dur, var(--dur-4)) var(--ease-out) var(--reveal-delay, 0ms),
      transform var(--reveal-dur, var(--dur-4)) var(--ease-out) var(--reveal-delay, 0ms),
      filter var(--reveal-dur, var(--dur-4)) var(--ease-out) var(--reveal-delay, 0ms);
  }
  :where(.hash-ready) [data-reveal="up"] { transform: translateY(var(--reveal-shift)); }
  :where(.hash-ready) [data-reveal="down"] { transform: translateY(calc(var(--reveal-shift) * -1)); }
  :where(.hash-ready) [data-reveal="left"] { transform: translateX(var(--reveal-shift)); }
  :where(.hash-ready) [data-reveal="right"] { transform: translateX(calc(var(--reveal-shift) * -1)); }
  :where(.hash-ready) [data-reveal="zoom"] { transform: scale(var(--reveal-zoom)); }
  :where(.hash-ready) [data-reveal="zoom-out"] { transform: scale(calc(2 - var(--reveal-zoom))); }
  :where(.hash-ready) [data-reveal="blur"] { filter: blur(var(--reveal-blur)); }
  :where(.hash-ready) [data-reveal="rise"] { transform: translateY(var(--reveal-shift)) scale(var(--reveal-zoom)); }
  :where(.hash-ready) [data-reveal="tilt"] { transform: translateY(var(--reveal-shift)) rotate(-3deg); }
  :where(.hash-ready) [data-reveal="fade"] { transform: none; }

  [data-reveal].is-in {
    opacity: 1;
    transform: none;
    filter: none;
  }

  /* Text split by the JS engine: data-reveal="words" / "chars" */
  .split-unit { display: inline-block; }
  :where(.hash-ready) .split-unit {
    opacity: 0;
    transform: translateY(0.6em);
    transition:
      opacity var(--dur-4) var(--ease-out) var(--split-delay, 0ms),
      transform var(--dur-4) var(--ease-out) var(--split-delay, 0ms);
  }
  .is-in .split-unit,
  .split-unit.is-in {
    opacity: 1;
    transform: none;
  }

  /* Masked line reveal — the clip that reads expensive and costs nothing */
  .mask-line {
    display: block;
    overflow: hidden;
  }
  .mask-line > * { display: block; }
  :where(.hash-ready) .mask-line > * {
    transform: translateY(100%);
    transition: transform var(--dur-4) var(--ease-out) var(--reveal-delay, 0ms);
  }
  .mask-line.is-in > * { transform: none; }

  /*
  ====================================================================
    LOAD-IN STAGGER  (.anim-1 .. .anim-8)
    Kept from our dashboard projects, now with a direction modifier.
    class="anim-3"            -> fade up, 0.14s in
    class="anim-3 anim-left"  -> same timing, slides from the left
  ====================================================================
  */
  [class*="anim-"] { animation-fill-mode: both; }
  .anim-1 { animation: hashFadeUp var(--dur-3) 0ms var(--ease-out) both; }
  .anim-2 { animation: hashFadeUp var(--dur-3) 70ms var(--ease-out) both; }
  .anim-3 { animation: hashFadeUp var(--dur-3) 140ms var(--ease-out) both; }
  .anim-4 { animation: hashFadeUp var(--dur-3) 210ms var(--ease-out) both; }
  .anim-5 { animation: hashFadeUp var(--dur-3) 280ms var(--ease-out) both; }
  .anim-6 { animation: hashFadeUp var(--dur-3) 350ms var(--ease-out) both; }
  .anim-7 { animation: hashFadeUp var(--dur-3) 420ms var(--ease-out) both; }
  .anim-8 { animation: hashFadeUp var(--dur-3) 490ms var(--ease-out) both; }

  .anim-fade[class*="anim-"] { animation-name: hashFadeIn; }
  .anim-down[class*="anim-"] { animation-name: hashFadeDown; }
  .anim-left[class*="anim-"] { animation-name: hashFadeLeft; }
  .anim-right[class*="anim-"] { animation-name: hashFadeRight; }
  .anim-zoom[class*="anim-"] { animation-name: hashZoomIn; }
  .anim-slow[class*="anim-"] { animation-duration: var(--dur-5); }

  /* Auto stagger for a whole list, no per-child classes */
  .anim-list > * { animation: hashFadeUp var(--dur-3) var(--ease-out) both; }
  .anim-list > *:nth-child(1) { animation-delay: 0ms; }
  .anim-list > *:nth-child(2) { animation-delay: 70ms; }
  .anim-list > *:nth-child(3) { animation-delay: 140ms; }
  .anim-list > *:nth-child(4) { animation-delay: 210ms; }
  .anim-list > *:nth-child(5) { animation-delay: 280ms; }
  .anim-list > *:nth-child(6) { animation-delay: 350ms; }
  .anim-list > *:nth-child(7) { animation-delay: 420ms; }
  .anim-list > *:nth-child(8) { animation-delay: 490ms; }
  .anim-list > *:nth-child(n + 9) { animation-delay: 560ms; }

  /*
  ====================================================================
    LOOPING / AMBIENT
  ====================================================================
  */
  .spin { animation: hashSpin 1s linear infinite; }
  .spin-slow { animation: hashSpin 8s linear infinite; }
  .pulse { animation: hashPulse 2s var(--ease-in-out) infinite; }
  .float { animation: hashFloat 4s var(--ease-in-out) infinite; }
  .blink { animation: hashBlink 1.2s steps(2, start) infinite; }

  /* Marquee — <div class="marquee"><div class="marquee-track">…dupe…</div></div> */
  .marquee { overflow: hidden; width: 100%; }
  .marquee-track {
    display: flex;
    width: max-content;
    gap: var(--marquee-gap, 4rem);
    animation: hashMarquee var(--marquee-dur, 30s) linear infinite;
  }
  .marquee:hover .marquee-track { animation-play-state: paused; }
  .marquee-reverse .marquee-track { animation-direction: reverse; }

  /*
  ====================================================================
    HOVER / INTERACTION
  ====================================================================
  */
  .hover-lift { transition: transform var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out); }
  .hover-lift:hover { transform: translateY(-4px); box-shadow: var(--shadow-2); }
  .hover-grow { transition: transform var(--dur-2) var(--ease-out); }
  .hover-grow:hover { transform: scale(1.03); }
  .hover-press { transition: transform var(--dur-1) var(--ease-out); }
  .hover-press:active { transform: scale(0.97); }
  .hover-zoom-img { overflow: hidden; }
  .hover-zoom-img img { transition: transform var(--dur-4) var(--ease-out); }
  .hover-zoom-img:hover img { transform: scale(1.06); }

  /* Underline that draws in */
  .link-draw { position: relative; }
  .link-draw::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: -2px;
    width: 100%;
    height: 1px;
    background: currentColor;
    transform: scaleX(0);
    transform-origin: right;
    transition: transform var(--dur-2) var(--ease-out);
  }
  .link-draw:hover::after { transform: scaleX(1); transform-origin: left; }

  /*
  ====================================================================
    SCROLL-DRIVEN HELPERS
  ====================================================================
  */
  /* Header state toggled by hash-scroll.js */
  .is-stuck { box-shadow: var(--shadow-1); }
  .is-hidden-up { transform: translateY(-100%); }
  .scroll-head {
    transition: transform var(--dur-3) var(--ease-out), box-shadow var(--dur-2) linear;
    will-change: transform;
  }

  /* Reading progress bar — width set by JS via --progress */
  .scroll-progress {
    position: fixed;
    inset: 0 auto auto 0;
    height: 3px;
    width: 100%;
    transform-origin: left;
    transform: scaleX(var(--progress, 0));
    background: var(--brand, currentColor);
    z-index: 99;
  }

  .parallax { will-change: transform; }
  .smooth-scroll { scroll-behavior: smooth; }

  /*
  ====================================================================
    KEYFRAMES
  ====================================================================
  */
  @keyframes hashFadeIn { from { opacity: 0; } to { opacity: 1; } }
  @keyframes hashFadeUp { from { opacity: 0; transform: translateY(var(--reveal-shift)); } to { opacity: 1; transform: none; } }
  @keyframes hashFadeDown { from { opacity: 0; transform: translateY(calc(var(--reveal-shift) * -1)); } to { opacity: 1; transform: none; } }
  @keyframes hashFadeLeft { from { opacity: 0; transform: translateX(calc(var(--reveal-shift) * -1)); } to { opacity: 1; transform: none; } }
  @keyframes hashFadeRight { from { opacity: 0; transform: translateX(var(--reveal-shift)); } to { opacity: 1; transform: none; } }
  @keyframes hashZoomIn { from { opacity: 0; transform: scale(var(--reveal-zoom)); } to { opacity: 1; transform: none; } }
  @keyframes hashSpin { to { transform: rotate(360deg); } }
  @keyframes hashPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }
  @keyframes hashFloat { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); } }
  @keyframes hashBlink { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }
  @keyframes hashMarquee { from { transform: translateX(0); } to { transform: translateX(calc(-50% - (var(--marquee-gap, 4rem) / 2))); } }

  /*
  ====================================================================
    ACCESSIBILITY
    One switch turns the whole system off. The JS engine reads the same
    media query and jumps straight to end values.
  ====================================================================
  */
  @media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important;
      scroll-behavior: auto !important;
    }
    [data-reveal],
    .split-unit {
      opacity: 1 !important;
      transform: none !important;
      filter: none !important;
    }
    .mask-line > * { transform: none !important; }
  }
}
