/* ---------- Local fonts — no CDNs ---------- */
@font-face { font-family: 'Inter'; font-weight: 400; font-style: normal; font-display: swap; src: url('/fonts/inter-400.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-weight: 500; font-style: normal; font-display: swap; src: url('/fonts/inter-500.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-weight: 600; font-style: normal; font-display: swap; src: url('/fonts/inter-600.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-weight: 700; font-style: normal; font-display: swap; src: url('/fonts/inter-700.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-weight: 800; font-style: normal; font-display: swap; src: url('/fonts/inter-800.woff2') format('woff2'); }
@font-face { font-family: 'JetBrains Mono'; font-weight: 400; font-style: normal; font-display: swap; src: url('/fonts/jbm-400.woff2') format('woff2'); }
@font-face { font-family: 'JetBrains Mono'; font-weight: 500; font-style: normal; font-display: swap; src: url('/fonts/jbm-500.woff2') format('woff2'); }
@font-face { font-family: 'JetBrains Mono'; font-weight: 700; font-style: normal; font-display: swap; src: url('/fonts/jbm-700.woff2') format('woff2'); }

/* ---------- Base — the editorial ground rules, on the app's real canvas ---------- */
html {
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}
body {
  background-color: #0D1117; /* canvas */
  color: #FFFFFF;            /* bright */
}
::selection { background-color: #00BFFF; color: #0D1117; } /* blue-glow / canvas */
:focus-visible { outline: 2px solid #00BFFF; outline-offset: 3px; } /* blue-glow */
h1, h2, h3, h4, h5, h6 { text-wrap: balance; overflow-wrap: break-word; }
p { text-wrap: pretty; }

/* The credential rule — accent line that opens sections */
.accent-rule { display: block; height: 2px; width: 3rem; background-color: #00BFFF; }

/* Mono eyebrow — section labels */
.eyebrow {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 0.8125rem;
  line-height: 1;
  letter-spacing: 0.18em;
  font-weight: 700;
  text-transform: uppercase;
  color: #00BFFF;
}

/* Editorial link — underline grows on hover */
.link-editorial { position: relative; display: inline-block; transition: color 0.3s; }
.link-editorial::after {
  content: '';
  position: absolute; left: 0; right: 0; bottom: -2px;
  height: 1px; background: currentColor;
  transform: scaleX(0); transform-origin: left;
  transition: transform 0.3s;
}
.link-editorial:hover::after { transform: scaleX(1); }

/* ---------- Scroll reveals — no-JS and reduced-motion safe ---------- */
/* Elements are fully visible by default; only when JS adds .js-reveal to <html>
   (and the user allows motion) do they start hidden and animate in. */
.js-reveal [data-reveal] { opacity: 0; }
.js-reveal [data-reveal].is-visible {
  animation: fadeInUp 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
/* Hero entrance choreography — staggered delays on the above-the-fold reveals */
.js-reveal .d-100.is-visible { animation-delay: 100ms; }
.js-reveal .d-200.is-visible { animation-delay: 200ms; }
.js-reveal .d-300.is-visible { animation-delay: 300ms; }
.js-reveal .d-400.is-visible { animation-delay: 400ms; }
.js-reveal .d-500.is-visible { animation-delay: 500ms; }
@keyframes fadeInUp {
  0% { opacity: 0; transform: translateY(12px); }
  100% { opacity: 1; transform: translateY(0); }
}
@keyframes ruleGrow {
  0% { transform: scaleX(0); transform-origin: left; }
  100% { transform: scaleX(1); transform-origin: left; }
}

/* ---------- The state machine — a quiet editorial diagram ---------- */
/* Default (no JS): the job reads as complete. Every state settled, every
   count accounted for. When JS runs and motion is allowed, the animation
   replays the job from NEW to DELIVERED, with a long dwell at QC.
   Under prefers-reduced-motion, JS instead sets a STATIC frame held at QC —
   the stop point is the product's differentiator, and the cautious users
   most likely to run reduced motion are exactly who it must reach.
   Contrast note: dimmed pipeline TEXT uses muted (#8B949E, AA on canvas);
   hint is reserved for non-text chrome. Notes fade visually during the
   replay but remain in the accessibility tree — screen readers always get
   the complete-job reading plus the <ol> aria-label.
   Motif rhyme with the app icon (a glowing verified slot): the ACTIVE step
   glows blue-glow, exactly like the icon's lit slot; a DONE step shows a
   green check, exactly like the icon's check mark. */
.sm-row { position: relative; }
.sm-bar {
  position: absolute; top: -1px; left: 0;
  height: 2px; width: 100%;
  background-color: #00BFFF; /* blue-glow */
  transform: scaleX(0); transform-origin: left;
}
.sm-idx { color: #00BFFF; transition: color 0.5s; } /* blue-glow */
.sm-name { color: #FFFFFF; transition: color 0.5s; } /* bright */
.sm-note { color: #8B949E; opacity: 1; transition: opacity 0.5s, color 0.5s; } /* muted */
.sm-check { opacity: 0; transition: opacity 0.5s; }

.sm-animate .sm-row.is-pending .sm-idx { color: #8B949E; } /* muted */
.sm-animate .sm-row.is-pending .sm-name { color: #8B949E; } /* muted */
.sm-animate .sm-row.is-pending .sm-note { opacity: 0; }
.sm-animate .sm-row.is-active .sm-idx { color: #00BFFF; } /* blue-glow */
.sm-animate .sm-row.is-active .sm-name { color: #FFFFFF; } /* bright */
.sm-animate .sm-row.is-active .sm-note { opacity: 1; color: #00BFFF; } /* blue-glow */
.sm-animate .sm-row.is-active .sm-bar {
  animation: ruleGrow 1.5s linear forwards;
}
.sm-animate .sm-row.is-done .sm-idx { color: #00BFFF; } /* blue-glow */
.sm-animate .sm-row.is-done .sm-name { color: #8B949E; } /* muted */
.sm-animate .sm-row.is-done .sm-note { opacity: 1; color: #8B949E; } /* muted */
.sm-animate .sm-row.is-done .sm-check { opacity: 1; }

/* True no-JS baseline (neither .sm-animate nor .sm-static-qc present — JS
   never ran at all): every row already renders in its full, settled color
   as "the job reads as complete," so every check shows too, DELIVERED
   included. The instant JS DOES run, this rule stops applying and checks
   fall back to being gated purely by each row's real is-done state — a
   check must never claim a step that the running animation hasn't reached
   yet, or it lies about progress mid-replay (DELIVERED cannot look done
   while QC is still the active row). */
#state-machine:not(.sm-animate):not(.sm-static-qc) .sm-check { opacity: 1; }

/* Static held-at-QC frame — reduced motion, applied by JS, zero animation.
   QC is deliberately ACTIVE (not done) here — the check must NOT appear on
   a state that hasn't actually passed. */
.sm-static-qc .sm-row.is-done .sm-name { color: #8B949E; } /* muted */
.sm-static-qc .sm-row.is-done .sm-check { opacity: 1; }
.sm-static-qc .sm-row.is-active .sm-idx { color: #00BFFF; } /* blue-glow */
.sm-static-qc .sm-row.is-active .sm-name { color: #FFFFFF; } /* bright */
.sm-static-qc .sm-row.is-active .sm-note { color: #00BFFF; } /* blue-glow */
.sm-static-qc .sm-row.is-active .sm-bar { transform: scaleX(0.6); }
.sm-static-qc .sm-row.is-pending .sm-name { color: #8B949E; } /* muted */

/* FAQ — details/summary, editorial treatment */
details summary { cursor: pointer; list-style: none; }
details summary::-webkit-details-marker { display: none; }
details .faq-marker { transition: transform 0.3s; }
details[open] .faq-marker { transform: rotate(45deg); }

/* Blanket reduced-motion kill: no transitions, no animations, anywhere.
   JS-driven motion is separately gated; this covers the CSS-only rest
   (faq marker rotate, link underline grow, hover translates, smooth scroll). */
@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;
  }
}

/* Screen-reader-only utility */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Support contact lightbox — hand-written rather than Tailwind's
   backdrop: variant, so the overlay looks right even if the Play CDN's
   in-browser JIT is ever slow to pick up a ::backdrop rule. Tailwind
   utility classes on the dialog element itself (bg-card, border-edge,
   etc.) still apply normally on top of this. */
dialog#support-dialog { margin: auto; }
dialog#support-dialog::backdrop { background: rgba(13, 17, 23, 0.8); backdrop-filter: blur(4px); }
