/* ==========================================================================
   site.css — shared chrome and components, v2 "Signal Grid"

   Class names match ../site/site.css where a component survives (.wrap .btn
   .card .label .sitenav .sitefoot .reveal) so CSS copied from v1 keeps
   working and the QA scripts' selector lists still resolve.
   ========================================================================== */

/* --- Layout -------------------------------------------------------------- */

.wrap {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.u-sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip {
  position: absolute;
  left: var(--space-sm);
  top: -100%;
  z-index: 100;
  padding: var(--space-2xs) var(--space-sm);
  background: var(--primary);
  color: var(--primary-foreground);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 700;
  letter-spacing: var(--track-widest);
  text-transform: uppercase;
  text-decoration: none;
}
.skip:focus { top: var(--space-sm); }

/* --- Circuit ground -------------------------------------------------------
   The page's ground. Descending traces with travelling heads, drawn by
   initCircuit() in motion.js, replacing the 48px violet grid that used to be
   a background-image on <body>.

   z-index -1, not 0. <canvas> is a replaced element and a fixed one at
   z-index 0 would create a stacking context that paints OVER the page's
   unpositioned content. At -1 it sits under the content but still above
   body's background-color, which propagates to the viewport canvas.

   width/height are NOT redundant next to inset: 0 - for a replaced element
   width:auto resolves to the intrinsic 300x150 and the insets are ignored,
   so the canvas renders postage-stamp sized in the corner. site/home.css
   sets both on .hero__bg for the same reason.

   It is left in the markup with no .js gate: with JS off it is an empty
   transparent canvas over the page ground, which is exactly the old
   no-background state.
   ------------------------------------------------------------------------ */

.circuitbg {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
  pointer-events: none;
}

/* --- Mono label devices --------------------------------------------------
   The concept leans on three: the tracked mono label, the numbered section
   marker (01 // ABOUT THE OPERATOR), and the slash-joined stack string
   (N8N // NOTION // DRIVE). The slash is always the accent.
   ------------------------------------------------------------------------ */

.label {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  line-height: 1.4;
  color: var(--muted-foreground);
}

.slash { color: var(--signal); }

.seclabel {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 700;
  letter-spacing: var(--track-widest);
  text-transform: uppercase;
  color: var(--signal);
}
.seclabel__n { color: var(--muted-foreground); }

/* --- HUD panel -----------------------------------------------------------
   The concept's one surface: hairline border, translucent fill, a blur so
   the grid ground softens behind it, and a very low cyan bloom that reads as
   emitted light rather than a drop shadow.
   ------------------------------------------------------------------------ */

.hud {
  position: relative;
  border: 1px solid var(--border);
  background: var(--panel);
  backdrop-filter: blur(16px);
  box-shadow: var(--glow);
}

/* Corner registration marks. Real elements rather than pseudo-elements, so a
   panel can carry both corners and still use ::before/::after for its own
   purposes. They sit inside the border box, flush to the corner. */
.corner {
  position: absolute;
  width: 2rem;
  height: 2rem;
  pointer-events: none;
}
.corner--tl {
  top: 0; left: 0;
  border-top: 2px solid var(--signal);
  border-left: 2px solid var(--signal);
}
.corner--br {
  right: 0; bottom: 0;
  border-right: 2px solid var(--signal);
  border-bottom: 2px solid var(--signal);
}

/* v1's name, kept so pasted markup lands somewhere sane. */
.card {
  position: relative;
  border: 1px solid var(--border);
  background: var(--panel);
}

/* --- Hairline grids ------------------------------------------------------
   A 1px gap over a border-coloured background collapses every cell edge into
   one shared rule instead of doubling them up. Used by the stat row and the
   capability cards - and by the testimonial pair, which is dormant (see the
   note over .quotes in home.css), so do not read this rule as having only two
   live consumers.
   ------------------------------------------------------------------------ */

.hairgrid {
  display: grid;
  gap: 1px;
  border: 1px solid var(--border);
  background: var(--border);
  list-style: none;
}
.hairgrid > * { background: var(--background); }

/* --- Buttons ------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  padding: var(--space-xs) var(--space-md);
  border: 1px solid transparent;
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 700;
  letter-spacing: var(--track-widest);
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition:
    background-color var(--dur-fast) var(--ease-in-out),
    border-color var(--dur-fast) var(--ease-in-out),
    color var(--dur-fast) var(--ease-in-out);
}

.btn--primary {
  border-color: var(--primary);
  background: var(--primary);
  color: var(--primary-foreground);
}
.btn--primary:hover { background: var(--draft-deep); border-color: var(--draft-deep); }

.btn--secondary {
  border-color: var(--border);
  background: var(--secondary);
  color: var(--foreground);
}
.btn--secondary:hover { border-color: var(--signal); color: var(--signal); }

/* The nav CTA is a step smaller than a hero button. */
.btn--sm {
  padding: var(--space-2xs) var(--space-sm);
  font-size: var(--text-micro);
  letter-spacing: var(--track-label);
}

/* For the two CTAs the page is actually built around: the hero's and the
   closer's. At the base .btn size those were 12px uppercase mono — the
   smallest text in their own blocks, and smaller than the body copy asking
   you to click them. The ask should not be the quietest thing on screen.

   A modifier rather than a change to .btn, because .btn is shared with v1 and
   every chip-sized button on this page depends on the current size. */
.btn--lg {
  padding: var(--space-sm) var(--space-lg);
  font-size: var(--text-sm);
}

/* .btn--primary sets its own background and border at class specificity,
   which outbids the bare :focus-visible rule in tokens.css — without this the
   ring silently vanishes on the page's primary control. */
.btn:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

/* --- Chips --------------------------------------------------------------- */

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs);
  list-style: none;
}

.chip {
  display: inline-block;
  padding: 2px var(--space-2xs);
  border: 1px solid var(--border);
  background: var(--secondary);
  font-family: var(--font-mono);
  font-size: var(--text-micro);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--secondary-foreground);
  line-height: 1.7;
  white-space: nowrap;
}

/* --- Nav ------------------------------------------------------------------ */

.sitenav {
  position: sticky;
  top: 0;
  z-index: 50;
  border-bottom: 1px solid var(--border);
  background: color-mix(in oklab, var(--background) 90%, transparent);
  backdrop-filter: blur(12px);
}

.sitenav__inner {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  height: var(--nav-h);
}

.wordmark {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.125rem;
  letter-spacing: -.01em;
  text-transform: uppercase;
  color: var(--foreground);
  text-decoration: none;
  white-space: nowrap;
}
.wordmark__slash {
  color: var(--signal);
  transition: opacity var(--dur-base) var(--ease-in-out);
}
.wordmark:hover .wordmark__slash { opacity: .7; }

.navend {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.navlinks {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  list-style: none;
  /* With JS off the links stay in normal flow at every width, so they have to
     be allowed to wrap — six mono links in a rigid row overflow a phone and
     scroll the whole page sideways. */
  flex-wrap: wrap;
}

.navlinks a {
  font-family: var(--font-mono);
  font-size: var(--text-micro);
  font-weight: 500;
  letter-spacing: var(--track-widest);
  text-transform: uppercase;
  text-decoration: none;
  color: var(--muted-foreground);
  transition: color var(--dur-fast) var(--ease-in-out);
}
.navlinks a:hover { color: var(--signal); }

.navtoggle, .js .navtoggle { display: none; }

/* Below 768 the link row collapses to a disclosure. With JS off the links
   stay in normal flow and the button stays hidden, so the nav still works. */
@media (max-width: 767px) {
  .js .navtoggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    padding: 0;
    border: 1px solid var(--border);
    background: transparent;
    color: var(--foreground);
    cursor: pointer;
    transition:
      border-color var(--dur-fast) var(--ease-in-out),
      color var(--dur-fast) var(--ease-in-out);
  }
  .js .navtoggle:hover { border-color: var(--signal); color: var(--signal); }

  .navcta { display: none; }

  .js .navlinks {
    position: absolute;
    top: calc(100% + 1px);
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    min-width: 15rem;
    padding: var(--space-2xs);
    border: 1px solid var(--border);
    background: var(--panel-strong);
  }
  /* display:flex above outbids the hidden attribute's UA display:none, so the
     panel must be closed explicitly or it renders open before any script. */
  .js .navlinks[hidden] { display: none; }
  .js .navlinks a { display: block; padding: var(--space-xs) var(--space-sm); }
  .js .navlinks a:hover { background: var(--secondary); color: var(--signal); }
}

/* --- Footer --------------------------------------------------------------- */

.sitefoot { border-top: 1px solid var(--border); }

.sitefoot__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding-block: var(--space-lg);
}

/* --- Reveal ---------------------------------------------------------------
   Gated on .js so that with JavaScript off every section is simply visible
   rather than stuck at opacity 0.
   ------------------------------------------------------------------------ */

.js .reveal {
  opacity: 0;
  transform: translateY(12px);
  transition:
    opacity var(--dur-slow) var(--ease-out),
    transform var(--dur-slow) var(--ease-out);
}
.js .reveal.is-in { opacity: 1; transform: none; }

/* --- Sequenced reveal -----------------------------------------------------
   The same arrival, handed to the children instead of the block, so a group
   of lines assembles as ONE move rather than appearing all at once.

   Used on the closer. It is deliberately the only place on the page that has
   it: a stagger applied everywhere is just the plain reveal with extra steps,
   and the uniform version of that is what made the page feel flat.

   The element itself is neutralised rather than left out of `.reveal`, because
   initReveals() finds its targets by that class. It is now the only instance
   of that neutralise-and-delegate trick; the capabilities carousel used to be
   the other one, and it went with the pin.
   -------------------------------------------------------------------------- */

.js .reveal--seq { opacity: 1; transform: none; transition: none; }

/* 95ms against a 300ms transition. The first pass used 70ms against 380ms, an
   overlap ratio of .18 — every line was already moving before the one above it
   had arrived, so six staggered fades read as one slightly slow fade. Shorter
   per-line and further apart is what makes it legible as a sequence, and it
   finishes sooner overall (775ms, against 830ms) despite looking slower. */
.js .reveal--seq > * {
  opacity: 0;
  transform: translateY(10px);
  transition:
    opacity 300ms var(--ease-out),
    transform 300ms var(--ease-out);
  transition-delay: calc(var(--i, 0) * 95ms);
}
.js .reveal--seq.is-in > * { opacity: 1; transform: none; }

.js .reveal--seq > :nth-child(2) { --i: 1; }
.js .reveal--seq > :nth-child(3) { --i: 2; }
.js .reveal--seq > :nth-child(4) { --i: 3; }
.js .reveal--seq > :nth-child(5) { --i: 4; }
.js .reveal--seq > :nth-child(6) { --i: 5; }

@media (prefers-reduced-motion: reduce) {
  .js .reveal { opacity: 1; transform: none; }
  .js .reveal--seq > * {
    opacity: 1;
    transform: none;
    transition: none;
    transition-delay: 0s;
  }
}

/* --- Word flow ------------------------------------------------------------
   The page's text arrives a word at a time: each word rises and sharpens out
   of a blur, a beat behind the one before it. motion.js splits the words and
   flips one class; this rule animates every one of them on the page.

   The rise is 0.45em, NOT a pixel value. A word in the 6rem hero headline
   travels 43px and a word in a 1rem lede travels 7px, from this one
   declaration, with no per-element tuning anywhere.

   The stagger is --i, set inline per word, multiplied inside the delay. One
   transition, one delay expression, any number of words.
   ------------------------------------------------------------------------ */

.js [data-flow] .fw {
  display: inline-block;   /* required, or the transform does not apply */
  opacity: 0;
  transform: translate3d(0, 0.45em, 0);
  filter: blur(6px);
  transition:
    opacity 820ms var(--ease-out),
    transform 820ms var(--ease-out),
    filter 820ms var(--ease-out);
  transition-delay: calc(var(--flow-delay, 0ms) + var(--i, 0) * var(--flow-step, 42ms));
}
.js [data-flow].is-flowed .fw { opacity: 1; transform: none; filter: none; }

/* A block settles as one piece — for things that are a single object rather
   than a sentence, like a pair of buttons. */
.js [data-flow="block"] {
  opacity: 0;
  transform: translate3d(0, 14px, 0);
  transition:
    opacity 940ms var(--ease-out),
    transform 940ms var(--ease-out);
  transition-delay: var(--flow-delay, 0ms);
}
.js [data-flow="block"].is-flowed { opacity: 1; transform: none; }

/* A moving blur re-rasterises the glyphs every frame, which a phone GPU feels
   and a desktop one does not. Drop the blur on touch and shorten the move;
   the rise and the fade carry it perfectly well on their own. */
@media (pointer: coarse) {
  .js [data-flow] .fw {
    filter: none;
    transition:
      opacity 720ms var(--ease-out),
      transform 720ms var(--ease-out);
  }
}

@media (prefers-reduced-motion: reduce) {
  .js [data-flow] .fw,
  .js [data-flow="block"] {
    opacity: 1;
    transform: none;
    filter: none;
    transition: none;
    transition-delay: 0s;
  }
}

/* --- Scroll progress ------------------------------------------------------
   A 2px lit line along the top edge of the window, above the nav, with a
   small bright head riding its leading edge. It answers one question the
   reader of a single long page keeps asking: how much of this is left.

   Deliberately NOT transitioned. The transform is written straight from the
   scroll position, so the line tracks the reader exactly and retreats at the
   same rate when they scroll back up. A transition here would make every
   frame chase the last one by its own duration.

   This is the one piece of motion on the page that survives reduced motion,
   because it is an indicator rather than an effect.
   ------------------------------------------------------------------------ */

.progress {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 60;              /* above .sitenav's 50 */
  height: 2px;
  pointer-events: none;
}

.progress__bar {
  position: absolute;
  inset: 0;
  transform-origin: 0 50%;  /* grows left to right */
  transform: scaleX(0);
  background: linear-gradient(
    90deg,
    color-mix(in oklab, var(--signal) 55%, transparent),
    var(--signal) 60%,
    var(--signal-deep)
  );
  box-shadow: 0 0 12px color-mix(in oklab, var(--signal) 55%, transparent);
  will-change: transform;
}

.progress__head {
  position: absolute;
  top: -3px;
  left: -4px;
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  background: var(--signal-deep);
  box-shadow: 0 0 10px 3px color-mix(in oklab, var(--signal) 60%, transparent);
  opacity: 0;
  transform: translateX(0);
  transition: opacity var(--dur-base) var(--ease-out);   /* opacity only */
  will-change: transform;
}

/* Hidden at the top of the page, where a progress bar reading zero is just a
   line of decoration across the masthead. */
.progress.is-on .progress__head { opacity: 1; }

/* --- Confidence marker ---------------------------------------------------
   Ported verbatim from ../site/site.css. A filled dot is a counted figure, a
   half-filled one an estimate, hollow means untracked. The half uses a
   hard-stop gradient rather than an opacity step so it stays legible at 8px.
   Saying which numbers are counted and which are inferred is the point.
   ------------------------------------------------------------------------ */

.conf {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3xs);
  font-family: var(--font-mono);
  font-size: var(--text-micro);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  line-height: 1;
  color: var(--muted-foreground);
  white-space: nowrap;
}
.conf::before {
  content: "";
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  border: 1.5px solid var(--signal);
}
.conf--measured::before { background: var(--signal); }
.conf--estimated::before {
  background: linear-gradient(90deg, var(--signal) 50%, transparent 50%);
}
.conf--untracked::before { background: transparent; }
