/* ============================================================
   BINGE BUILT - shared blueprint grammar (bp.css)
   Cut from index.html's inline style on 13 Jul 2026 so all six
   pages draw from ONE copy (Pricing keeps its pp2 idiom and
   consumes only these shared atoms). Keyframes are document
   global once this sheet loads, so page-local CSS may reference
   gridDraw / specRise / numPop / specGridFloat / specTwinkle /
   specBlockFloat by name.
   Refinements vs the index original, all deliberate:
   1. .bp-rise pair generalised to .js-anim / .is-building
   2. .bp__bg drift duration is var(--bp-drift, 23s)
   3. .bb-node base gains pointer-events: none
   Motion is opt-in: base styles are the settled end state, so
   reduced-motion / JS-off get the finished page for free.
   ============================================================ */

/* ---- chrome: the datum gauge. A 2px paper tape along the header's bottom
   edge grows with scroll depth, measuring how far down the sheet you have
   read. Lives INSIDE the ink chrome (bottom: 0, above the ink-line border)
   so it is always legible and never over page content. Declared wholly
   inside the double gate: non-supporting browsers and reduced-motion never
   instantiate the pseudo, leaving today's header byte for byte. ---- */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: scroll()) {
    .site-header::after {
      content: "";
      position: absolute;
      left: 0;
      right: 0;
      bottom: 0;
      height: 2px;
      background: color-mix(in srgb, var(--paper) 40%, transparent);
      transform: scaleX(0);
      transform-origin: 0 50%;
      pointer-events: none;
      animation: gaugeSweep linear both;
      animation-timeline: scroll(root);
    }

    @keyframes gaugeSweep {
      to { transform: scaleX(1); }
    }
  }
}

/* ---- chrome: drawn nav underlines. Interaction-tied only; currentColor is
   orange only in the hover state of an actual action, so the orange rule
   holds. Reduced-motion users get an instant, finished underline (the
   transition sits inside the motion gate; the state still conveys). ---- */
.nav a:not(.btn) { position: relative; }

.nav a:not(.btn)::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -6px;
  height: 2px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
}

.nav a:not(.btn):hover::after,
.nav a:not(.btn):focus-visible::after { transform: scaleX(1); }

@media (prefers-reduced-motion: no-preference) {
  .nav a:not(.btn)::after { transition: transform .18s ease; }
}

/* ---- chrome: the footer title block. Two register crosses sit in the far
   corners of every footer, the only .bb-node instances on the site that do
   NOT twinkle: the title block of a drawing is finished. Static, CSS only,
   so the zero-script legal pages carry them too. ---- */
.site-footer { position: relative; overflow: hidden; }
.site-footer .wrap { position: relative; z-index: 1; }
.site-footer .bb-node path { stroke: rgba(247, 245, 240, .3); }

/* ---- primitives: crosshair node + drafting block ---- */
/* MOVED from index.html: .bb-node, .bb-node path, and the 880px hide
   (refinement 3: base gains pointer-events: none). */
.bb-node {
  position: absolute;
  left: var(--bx);
  top: var(--by);
  width: 16px;
  height: 16px;
  opacity: .26;
  pointer-events: none;
}

.bb-node path {
  stroke: rgba(22, 29, 43, .5);
  stroke-width: 1.25;
}

@media (max-width: 880px) {
  .bb-node {
    display: none;
  }
}

/* keep mobile calm */

/* MOVED from index.html: .bb-block, .bb-block--edge, .bb-block--edge-strong,
   and the 880px hide. */
/* Tints lifted a notch on 5 Aug 2026: the grid strokes came DOWN to half and the
   block field came UP, so the lower bands carry the hero's drifting-block life
   instead of a dominant ruled grid. Still well under the hero's contrast. */
.bb-block {
  position: absolute;
  left: var(--bx);
  top: var(--by);
  width: var(--s);
  height: var(--s);
  border-radius: 5px;
  background: rgba(22, 29, 43, .075);
  /* per-block dimming, the same --o the hero field and the pricing blocks
     already take. Nothing set it until the sitewide field pass, where the
     legal pages needed their squares fainter still than a paper band's. */
  opacity: var(--o, 1);
}

.bb-block--edge {
  background: transparent;
  border: 1.5px solid rgba(22, 29, 43, .2);
}

.bb-block--edge-strong {
  background: transparent;
  border: 1.5px solid rgba(22, 29, 43, .32);
}

@media (max-width: 880px) {
  .bb-block {
    display: none;
  }
}

/* keep mobile calm */

/* ---- the shear: the field has mass ----
   Scroll SPEED, never scroll position, shears the block fields apart and tips
   each square a few degrees against the motion; the damped spring in bp.js
   trues the drawing back square the moment the visitor stops. Position-scrubbed
   scroll choreography was prototyped alongside this and rejected: it reads as
   the page lagging behind the thumb rather than reacting to it.

   The whole interface is ONE unitless number, --shear, written on the document
   root by bp.js. Everything here multiplies it by its own depth or its own
   seed, so the feel of the entire site is tuned in exactly two places: the
   spring constants in bp.js, and the two amplitudes below. A new band or a new
   page picks the effect up by using the shared vocabulary, with no new wiring.

   Registered so the property has a real initial value, and every consumer still
   carries var(--shear, 0) on top of that: a browser that ignores @property, a
   visitor with JavaScript off and a reduced-motion visitor all land on zero,
   which is today's finished page. ---- */
@property --shear {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}

/* Every square needs its own lean or the field tips as one slab. The fields
   already carry a hand-authored desync index, so reuse it: sin() turns that
   sequence into a scatter that never marches, and any new square inherits a
   believable seed from the --i it needs anyway. Set --rs inline to overrule it.
   This is the tilt knob: seed times clamp is the widest angle a square can ever
   reach, so 0.08 against the engine's limit of 18 caps it near 1.4 degrees. */
.bb-block,
.pp2-block,
.hero__block {
  --rs: calc(sin((var(--i, 0) + 0.7) * 1rad) * 0.08);
}

@media (prefers-reduced-motion: no-preference) {

  /* The tilt rides `rotate`, which nothing else on these squares claims. The
     family's composition rule is already spoken for here: the passive drift
     owns `translate` and the one-shot build-in owns `transform`, so a third
     property is the only way all three can run at once without fighting. */
  .bb-block,
  .pp2-block,
  .hero__block {
    rotate: calc(var(--shear, 0) * var(--rs, 0) * 1deg);
  }

  /* And the field itself lifts and dips as a sheet: the offset knob. The
     bespoke pre-.bp field containers are named here on purpose. The shear is
     ONE engine with one tuning point, so its amplitude does not get a second
     copy in index.html or on the pricing page. --zf lets a field sit deeper or
     shallower, the same depth factor the hero's layers already use. */
  .bp__blocks,
  .specsheet__blocks,
  .portability__blocks,
  .pp2-bp__deco,
  .work-row .row-deco {
    translate: 0 calc(var(--shear, 0) * var(--zf, 1) * 0.38px);
  }
}

/* ---- family keyframes ---- */
/* MOVED from index.html: gridDraw, specRise, numPop. (specSheen stays in
   index.html with its only consumers; the sheen family is frozen.) */
@keyframes gridDraw {
  from {
    stroke-dashoffset: 1;
  }

  to {
    stroke-dashoffset: 0;
  }
}

@keyframes specRise {
  from {
    opacity: 0;
    transform: translate3d(0, 22px, 0);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes numPop {
  0% {
    opacity: 0;
    transform: scale(.5);
  }

  70% {
    opacity: 1;
    transform: scale(1.12);
  }

  100% {
    opacity: 1;
    transform: none;
  }
}

/* MOVED from index.html: specGridFloat, specTwinkle, specBlockFloat. */
@keyframes specGridFloat {
  0% {
    translate: 0 0;
  }

  34% {
    translate: 4px -6px;
  }

  67% {
    translate: -3px -3px;
  }

  100% {
    translate: 0 0;
  }
}

@keyframes specTwinkle {

  0%,
  100% {
    opacity: .12;
  }

  50% {
    opacity: .5;
  }
}

/* drift animates `translate` so it composes with the blocks' build-in `transform` */
@keyframes specBlockFloat {
  0% {
    translate: 0 0;
  }

  30% {
    translate: 3px -7px;
  }

  60% {
    translate: -2px -4px;
  }

  100% {
    translate: 0 0;
  }
}

/* ---- passive life: always-on, pure CSS (runs JS-off too) ---- */
@media (prefers-reduced-motion: no-preference) {

  /* a few telemetry crosshairs breathe, each on its own slow, offset cycle */
  .bb-node {
    animation: specTwinkle calc(4s + var(--bi, 0) * 0.6s) ease-in-out infinite;
    animation-delay: calc(var(--bi, 0) * -1.3s);
  }

  /* blocks drift forever, desynced, just like the hero block field */
  .bb-block {
    animation: specBlockFloat calc(9s + var(--i, 0) * 0.5s) ease-in-out infinite;
    animation-delay: calc(var(--i, 0) * -0.8s);
  }

  /* NEW: the footer's register crosses hold still (a finished title block).
     Placed AFTER the twinkle rule because the two selectors tie on
     specificity, so order is load-bearing. */
  .bb-node--static { animation: none; }
}

/* ---- the shared blueprint backdrop (.bp) ---- */
/* MOVED from index.html: the whole block, banner comment included, with
   refinement 1 (.js-anim .bp-rise / .is-building .bp-rise) and refinement 2
   (drift duration var(--bp-drift, 23s) on .bp__bg) applied. */

/* =========================================================================
 SHARED BLUEPRINT BACKDROP (.bp) - the spec-sheet / pricing motif distilled
 into one reusable layer so the later content sections (the work, about,
 FAQ, contact) all carry the same faint grid + drifting blocks + telemetry
 and the same one-shot reveal, without re-declaring it per section.
 Light by default; add .bp--dark on ink surfaces. Reuses the global
 .bb-block / .bb-node primitives and the gridDraw / specRise / specGridFloat
 keyframes already defined above (so blocks drift + crosshairs twinkle for
 free via the global passive rules). Motion is opt-in - base styles are the
 settled end-state, so reduced-motion / JS-off get the finished section.
 (The earlier sections keep their bespoke namespaced versions; this is the
 shared distillation used from "The Work" down, not a retrofit of those.)
 ========================================================================= */
.bp {
  position: relative;
  overflow: hidden;
}

.bp>.wrap {
  position: relative;
  z-index: 1;
}

.bp__bg,
.bp__telemetry,
.bp__blocks {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

/* faint grid, masked to fade top + bottom so it never hits the band edges */
.bp__bg {
  -webkit-mask: linear-gradient(to bottom, transparent, #000 18%, #000 86%, transparent);
  mask: linear-gradient(to bottom, transparent, #000 18%, #000 86%, transparent);
}

/* Halved on 5 Aug 2026: the ruled grid was reading louder than the content it
   sits behind. Present, but only just, which is the whole point of a datum. */
.bp__bg line {
  stroke: rgba(22, 29, 43, .04);
  stroke-width: 1;
  stroke-dasharray: 1;
}

/* dark variant: paper strokes on ink, re-tinted blocks + crosshairs */
.bp--dark .bp__bg line {
  stroke: rgba(247, 245, 240, .035);
}

.bp--dark .bb-node path {
  stroke: rgba(247, 245, 240, .42);
}

.bp--dark .bb-block {
  background: var(--ink-line);
}

.bp--dark .bb-block--edge {
  background: transparent;
  border-color: var(--c-ink-600);
}

.bp--dark .bb-block--edge-strong {
  background: transparent;
  border-color: color-mix(in srgb, var(--paper) 22%, transparent);
}

@media (prefers-reduced-motion: no-preference) {

  /* passive: the whole grid drifts slowly (blocks + nodes inherit the
     global .bb-block / .bb-node drift + twinkle defined with the spec-sheet);
     per-section desync via the inline --bp-drift custom property */
  .bp__bg {
    animation: specGridFloat var(--bp-drift, 23s) ease-in-out infinite;
  }

  /* hidden / undrawn until the section scrolls in, so nothing flashes pre-reveal */
  .bp.js-anim .bp__bg {
    opacity: 0;
    transition: opacity .9s ease;
  }

  .bp.js-anim .bp__bg line {
    stroke-dashoffset: 1;
  }

  .bp.js-anim .bb-block {
    opacity: 0;
    transform: translateY(16px);
    transition: opacity .6s ease, transform .6s cubic-bezier(.2, .75, .25, 1);
    transition-delay: calc(var(--i, 0) * 90ms);
  }

  .js-anim .bp-rise {
    opacity: 0;
  }

  .bp.is-building .bp__bg {
    opacity: 1;
  }

  /* grid lines draw themselves on, left-to-right stagger (calm echo of the hero) */
  .bp.is-building .bp__bg line {
    animation: gridDraw .7s ease-out backwards;
    animation-delay: calc(var(--gi, 0) * 55ms);
    stroke-dashoffset: 0;
  }

  .bp.is-building .bb-block {
    opacity: var(--o, 1);
    transform: none;
  }

  /* content rises in, gentle stagger driven by --r on each element */
  .is-building .bp-rise {
    animation: specRise .6s cubic-bezier(.2, .75, .25, 1) backwards;
    animation-delay: calc(var(--r, 0) * 120ms);
    opacity: 1;
  }
}

/* ---- mobile identity: a quiet edge field + travelling keystone ------------
   Desktop can afford a full drawing field. Phones keep only two faint squares
   per shared band and a 10px progress rail outside the content gutter. The rail
   is inserted and positioned by bp.js, uses only transforms, and never accepts
   pointer input. Reduced-motion visitors get neither treatment. */
.mobile-build-rail { display: none; }

@media (max-width: 880px) and (prefers-reduced-motion: no-preference) {
  .bp__blocks .bb-block:nth-child(-n + 2),
  .specsheet__blocks .bb-block:nth-child(-n + 2),
  .portability__blocks .bb-block:nth-child(-n + 2),
  .work-row .row-deco .bb-block:nth-child(-n + 2) {
    display: block;
    scale: .72;
    opacity: min(var(--o, 1), .34);
  }

  .mobile-build-rail {
    display: block;
    position: fixed;
    z-index: 49;
    top: 76px;
    right: max(6px, env(safe-area-inset-right));
    bottom: max(14px, env(safe-area-inset-bottom));
    width: 10px;
    pointer-events: none;
    opacity: .9;
    contain: layout paint;
  }

  .mobile-build-rail::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 4px;
    width: 1px;
    background: linear-gradient(to bottom,
      transparent,
      rgba(242, 97, 45, .18) 8%,
      rgba(242, 97, 45, .18) 92%,
      transparent);
  }

  .mobile-build-rail__keystone {
    position: absolute;
    top: 0;
    left: 0;
    width: 9px;
    height: 9px;
    border-radius: 3px;
    background: var(--orange);
    box-shadow:
      0 calc(-12px - var(--shear, 0) * .18px) 0 rgba(242, 97, 45, .28),
      0 calc(12px + var(--shear, 0) * .18px) 0 rgba(242, 97, 45, .18);
    transform: translate3d(0, var(--rail-y, 0px), 0);
    rotate: calc(var(--shear, 0) * .08deg);
    will-change: transform;
  }
}

@media (max-width: 520px) and (prefers-reduced-motion: no-preference) {
  .mobile-build-rail { top: 72px; }
}

/* ---- rails variant (NEW): the grid for reading columns. Verticals at the
   sheet edges only; no rails variant may ever contain a horizontal line
   (horizontals cross a centred column by definition). Hidden below 1100px:
   the inner rail sits at 8% of the 1200 viewBox, and 0.08 x viewport only
   clears the centred 860px column's gutter above roughly 1024px. The
   breakpoint is arithmetic, not taste. ---- */
@media (max-width: 1100px) {
  .bp__bg--rails { display: none; }
}
