/* Motion.
   Every reveal is gated behind `.js` on <html>, set by an inline script in
   <head>. Without JavaScript — and for every crawler — content is simply
   visible. Nothing here can ever hide content from a reader. */

/* --------------------------------------------------------- scroll reveal */

.js [data-reveal] {
  opacity: 0;
  transform: translateY(18px);
  transition:
    opacity 0.62s cubic-bezier(0.22, 0.61, 0.36, 1),
    transform 0.62s cubic-bezier(0.22, 0.61, 0.36, 1);
  will-change: opacity, transform;
}

.js [data-reveal].is-revealed {
  opacity: 1;
  transform: none;
  will-change: auto;
}

/* ------------------------------------------------------ workflow canvas */

/* One canvas laid over the WHOLE document — it scrolls with the page.
   Nodes fill it from the header to the footer, every node is linked to its
   neighbours, and pulses travel through the mesh. The header graph and the
   page background are literally the same object. */
body { position: relative; }

.site-bg {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  inline-size: 100%;
  block-size: 100%;
  z-index: -1;
  pointer-events: none;
  opacity: var(--canvas-alpha);
}

/* Content sections carry a translucent veil, so the graph shines through
   strongest in the hero and the gaps, and stays quiet behind reading. */
.section,
.site-footer {
  background: var(--veil);
}

@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .section,
  .site-footer {
    -webkit-backdrop-filter: blur(2px);
    backdrop-filter: blur(2px);
  }
}

/* ------------------------------------------------------------ page spine */

/* The pipeline continues out of the hero and down the page. The fill height
   is scroll-driven (set by initSpine); every .section carries a node on the
   line that lights once the section has been read past. */

main { position: relative; }

.page-spine {
  position: absolute;
  inset-block: 0;
  inset-inline-start: calc(max(var(--gutter), (100% - var(--container)) / 2) / 2);
  inline-size: 2px;
  background: var(--line);
  pointer-events: none;
}

.page-spine__fill {
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  block-size: calc(var(--spine-progress, 0) * 100%);
  background: linear-gradient(180deg, var(--mint), var(--lime));
  transition: block-size 0.15s linear;
}

/* the data pulse riding the tip of the fill */
.page-spine__fill::after {
  content: '';
  position: absolute;
  inset-block-end: -4px;
  inset-inline-start: 50%;
  inline-size: 8px;
  block-size: 8px;
  margin-inline-start: -4px;
  border-radius: 50%;
  background: var(--lime);
  box-shadow: 0 0 12px var(--lime);
}

/* every section is a node on the spine */
.section::before {
  content: '';
  position: absolute;
  inset-block-start: calc(var(--section-pad) + 8px);
  inset-inline-start: calc(max(var(--gutter), (100% - var(--container)) / 2) / 2 - 5px);
  inline-size: 12px;
  block-size: 12px;
  border-radius: 50%;
  background: var(--bg);
  border: 2px solid var(--line-strong);
  transition: border-color 0.3s ease, background-color 0.3s ease, box-shadow 0.3s ease;
  pointer-events: none;
}

.section.is-passed::before {
  background: var(--mint);
  border-color: var(--mint);
  box-shadow: 0 0 10px color-mix(in srgb, var(--mint) 55%, transparent);
}

/* On phones the margin is too narrow for the spine — the drawer nav and
   hero variants carry the motif there instead. */
@media (max-width: 860px) {
  .page-spine { display: none; }
  .section::before { display: none; }
}

/* ---------------------------------------------------------- header lift */

.site-header {
  transition: box-shadow 0.3s ease, background-color 0.3s ease;
}

.site-header.is-stuck {
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.24);
}

/* ------------------------------------------------------ micro-interaction */

.btn {
  position: relative;
  overflow: hidden;
  transition: transform 0.18s cubic-bezier(0.34, 1.4, 0.64, 1), box-shadow 0.18s ease;
}

.btn:active { transform: translateY(0); }

.card {
  transition:
    border-color 0.24s ease,
    transform 0.24s cubic-bezier(0.22, 0.61, 0.36, 1),
    box-shadow 0.24s ease;
}

.badge { transition: color 0.2s ease, border-color 0.2s ease, transform 0.2s ease; }

/* Lifts belong to a real pointer. A tap leaves :hover applied, so on touch
   these would stick — the card stays raised until you tap somewhere else. */
@media (hover: hover) {
  .btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 22px rgba(0, 0, 0, 0.22);
  }

  .card:hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow);
  }

  .badge:hover { color: var(--fg); border-color: var(--line-strong); transform: translateY(-1px); }
}

/* Timeline dots pulse gently while a role is current. */
.timeline__item--current::before {
  animation: nt-ping 2.6s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

@keyframes nt-ping {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--mint) 45%, transparent); }
  50% { box-shadow: 0 0 0 7px transparent; }
}

/* Gradient sweep across the proof numbers. */
.proof__value {
  background-size: 220% 100%;
  animation: nt-sweep 7s ease-in-out infinite;
}

@keyframes nt-sweep {
  0%, 100% { background-position: 0% 50%; }
  50% { background-position: 100% 50%; }
}

/* Hero entrance — plays once, immediately, without waiting for scroll. */
.js .hero__name,
.js .hero__headline,
.js .hero__intro,
.js .hero__actions,
.js .hero__eyebrow {
  animation: nt-rise 0.7s cubic-bezier(0.22, 0.61, 0.36, 1) both;
}

.js .hero__eyebrow { animation-delay: 0.02s; }
.js .hero__name { animation-delay: 0.08s; }
.js .hero__headline { animation-delay: 0.16s; }
.js .hero__intro { animation-delay: 0.24s; }
.js .hero__actions { animation-delay: 0.32s; }

@keyframes nt-rise {
  from { opacity: 0; transform: translateY(20px); }
  to { opacity: 1; transform: none; }
}

/* ------------------------------------------------------- reduced motion */

@media (prefers-reduced-motion: reduce) {
  .js [data-reveal] {
    opacity: 1;
    transform: none;
    transition: none;
  }
  .js .hero__name,
  .js .hero__headline,
  .js .hero__intro,
  .js .hero__actions,
  .js .hero__eyebrow,
  .timeline__item--current::before,
  .proof__value {
    animation: none;
  }
  .page-spine__fill { transition: none; }
  .btn:hover,
  .card:hover { transform: none; }
}

/* ---------------------------------------------------- cost-flow animation */

/* The branch does not just sit there being a diagram — it runs. A document
   arrives, the IF node decides, and a pulse takes the cheap route. One
   staggered cycle, so the eye follows the same path the data does. */

.flow { --cycle: 5s; }

.js .flow__node--trigger {
  animation: flow-lit var(--cycle) ease-in-out infinite;
  animation-delay: 0s;
}

.js .flow__node--decision {
  animation: flow-decide var(--cycle) ease-in-out infinite;
  animation-delay: 0.9s;
}

/* a pulse riding the stem from the webhook down to the decision */
.js .flow__stem::after {
  content: '';
  position: absolute;
  inset-inline-start: 50%;
  margin-inline-start: -4px;
  inline-size: 8px;
  block-size: 8px;
  border-radius: 50%;
  background: var(--lime);
  box-shadow: 0 0 10px var(--lime);
  animation: flow-travel var(--cycle) ease-in-out infinite;
  animation-delay: 0.25s;
}

.flow__stem { position: relative; }

/* each branch stem carries its own pulse, the cheap one brighter */
.js .flow__branch::after {
  content: '';
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 50%;
  margin-inline-start: -4px;
  inline-size: 8px;
  block-size: 8px;
  border-radius: 50%;
  animation: flow-travel-short var(--cycle) ease-in-out infinite;
  animation-delay: 1.5s;
}

.js .flow__branch--cheap::after {
  background: var(--lime);
  box-shadow: 0 0 10px var(--lime);
}

/* the expensive route is taken rarely — its pulse stays faint */
.js .flow__branch--costly::after {
  background: var(--dim);
  opacity: 0.35;
}

.js .flow__branch--cheap .flow__node {
  animation: flow-lit var(--cycle) ease-in-out infinite;
  animation-delay: 2.1s;
}

.js .flow__fallback {
  animation: flow-fallback var(--cycle) ease-in-out infinite;
  animation-delay: 3s;
}

@keyframes flow-lit {
  0%, 12%, 100% { border-color: var(--line-strong); box-shadow: none; }
  4%, 8% {
    border-color: var(--lime);
    box-shadow: 0 0 0 5px color-mix(in srgb, var(--lime) 18%, transparent);
  }
}

@keyframes flow-decide {
  0%, 100% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--lime) 12%, transparent); }
  20%, 30% {
    box-shadow: 0 0 0 9px color-mix(in srgb, var(--lime) 26%, transparent);
    transform: scale(1.03);
  }
}

@keyframes flow-travel {
  0%, 4% { inset-block-start: -4px; opacity: 0; }
  8% { opacity: 1; }
  22% { inset-block-start: calc(100% - 4px); opacity: 1; }
  26%, 100% { inset-block-start: calc(100% - 4px); opacity: 0; }
}

@keyframes flow-travel-short {
  0%, 30% { transform: translateY(-4px); opacity: 0; }
  34% { opacity: 1; }
  44% { transform: translateY(var(--space-4)); opacity: 1; }
  48%, 100% { transform: translateY(var(--space-4)); opacity: 0; }
}

@keyframes flow-fallback {
  0%, 55%, 100% { border-color: var(--line-strong); color: var(--dim); }
  62%, 72% {
    border-color: color-mix(in srgb, var(--mint) 60%, transparent);
    color: var(--fg);
  }
}

/* The figures count up in place; the diagram is the only moving part. */
@media (prefers-reduced-motion: reduce) {
  .js .flow__node--trigger,
  .js .flow__node--decision,
  .js .flow__stem::after,
  .js .flow__branch::after,
  .js .flow__branch--cheap .flow__node,
  .js .flow__fallback {
    animation: none;
  }
  .js .flow__stem::after,
  .js .flow__branch::after { display: none; }
}
