/* ─────────────────────────────────────────────────────────────────────────
   page-marketing.css — /contact/ and /404, the small amount of shared
   machinery both of them need, and one closing block of /pricing/ polish.

   It EXTENDS page.css, it does not replace it. `.page-main`, `.page-head`,
   `.page-section`, `.page-lede`, `.rule-list`, `.cta` and `.ghost-link` all
   come from there and are used verbatim; everything below either adds a
   construction those two pages need or narrows one of them for a specific
   block. Load AFTER tokens.css, base.css and page.css — and, on /pricing/
   only, AFTER page-services.css, because the /pricing/ block at the foot of
   this file overrides two of its equal-specificity rules by source order.

   Four prefixes, so nothing here can be mistaken for someone else's:
     .mk-   shared by /contact/ and /404 (the reveal, the CTA row, the board)
     .ct-   /contact/ only
     .nf-   /404 only
     .sv-   /pricing/ only, and ONLY inside the block at the foot of this
            file — those selectors deliberately reach page-services.css's
            comparison table, and nothing else here may use the prefix

   THE LAW, as it applies here:
     · HAIRLINES, NOT CARDS. There is not a single filled panel, rounded
       corner, drop shadow or gradient in this file — except the packet's
       radial gradient, which is copied verbatim from `.orbit-packet` /
       `.dp-packet` because it is a LIGHT SOURCE, not a surface.
     · GREEN MEANS CURRENT IS FLOWING. --leaf appears exactly twice: on the
       empty bay's travelling packet, and on the confirmation tick beside the
       one value on /contact/ that arrives. Nowhere else, on either page.
     · ENORMOUS NEGATIVE SPACE, and type left with the moving element right
       or below it.
     · Every value is a var(). There is not a raw hex in this file.

   MOTION. Two mechanisms and no third:
     1. `--mk-in`, a 0..1 number written by the DIRECTOR as a pure function of
        scroll. It drives opacity and one 10px rise. It is never transitioned
        — a scrubbed property and a transition on the same property are two
        loops fighting over one value, and the result stutters.
     2. Hover and focus transitions, which are input-driven, 180ms, and touch
        transform and colour only.
   No keyframes are declared in this file. Nothing here animates width,
   height, top, left or box-shadow.
   ───────────────────────────────────────────────────────────────────────── */

/* ══ SHARED ═══════════════════════════════════════════════════════════════ */

/* The reveal. `--mk-in` defaults to 1, so a page with no JavaScript — or one
   whose module failed — renders fully composed rather than invisible. That
   default is load-bearing: it is the difference between a degraded page and
   a blank one. */
.mk-rise {
  opacity: var(--mk-in, 1);
  transform: translate3d(0, calc((1 - var(--mk-in, 1)) * 10px), 0);
}

/* A row of actions under a section. Flex rather than inline elements with
   margins, so the ghost link's baseline aligns with the CTA's box. */
.mk-cta-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s4);
  margin-top: var(--s4);
}

/* page.css gives the ghost link its hover colours but no transition, so the
   swap lands in one frame. Here — scoped to the CTA row, never sitewide —
   it eases on the site's one input-driven timing, matching the CTA beside
   it, which already transitions in page.css. Colour only; nothing moves. */
.mk-cta-row .ghost-link {
  transition: border-color 180ms var(--ease-out), color 180ms var(--ease-out);
}

/* The last section on a page owns no closing rule: the footer's own top
   hairline is directly below it and two rules 64px apart read as a mistake. */
.mk-last { border-bottom: 0; }

/* ── the master wall's slot ────────────────────────────────────────────── */

/* THE BOX IS RESERVED BEFORE THE MODULE LANDS. `scenes/dash/system.js` sizes
   the wall from its lane count rather than from an aspect ratio, so the
   height is arithmetic rather than a guess:

     head 48 + (9 lanes × 56) + foot 44 + --s4 of sheet padding + a 1px edge

   and below the wall's own 719px CONTAINER query it restructures to a 44px
   head and 64px lanes. Inside `.page-main` the container is the viewport
   less 2 × --s5 of padding, so 719px of container is 799px of viewport —
   which is where the second rule below switches over.

   The foot's own --s3 of padding-top is INSIDE its 44px floor, not on top of
   it; both figures below are measured against the mounted wall rather than
   read off the stylesheet, which is the only way to get this right.

   IF system.js's chassis metrics change, these two numbers change with them.
   Getting it wrong costs layout shift, not correctness. */
.mk-board {
  min-height: calc(48px + 9 * 56px + 44px + var(--s4) + 1px);
  margin-bottom: var(--s4);
}

@media (max-width: 799px) {
  .mk-board { min-height: calc(44px + 9 * 64px + 44px + var(--s4) + 1px); }
}

/* ══ /contact/ ════════════════════════════════════════════════════════════ */

/* ── the agenda, against a 00 / 30 time spine ──────────────────────────── */

.ct-agenda {
  display: grid;
  grid-template-columns: 4rem minmax(0, 1fr);
  column-gap: var(--s4);
  align-items: stretch;
}

/* The spine. `align-content: space-between` puts 00 at the top and 30 at the
   bottom with nothing in between, which is what a duration looks like when
   the copy states no intermediate times — and this page states none, because
   none exist. */
.ct-spine {
  position: relative;
  display: grid;
  align-content: space-between;
  justify-items: end;
  padding-right: var(--s3);
  color: var(--ink-60);
}

/* The rule DRAWS, top to bottom, on --mk-in. A 1px background rect rather
   than a border, because a border cannot be scaled from one end. */
.ct-spine::after {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  width: 1px;
  height: 100%;
  background: var(--graphite);
  transform: scaleY(var(--mk-in, 1));
  transform-origin: top center;
}

.ct-agenda-list li {
  display: grid;
  grid-template-columns: 9rem minmax(0, 1fr);
  column-gap: var(--s4);
  align-items: baseline;
}

/* FIRST VERSION is the width constraint: 13 characters of JetBrains Mono at
   13px with 0.18em tracking measures about 121px, so 9rem holds it without
   wrapping and without stranding the sentence beside it. */
.ct-tag { color: var(--ink-60); }
.ct-said { color: var(--ink); }

/* ── what happens next ─────────────────────────────────────────────────── */

/* 62ch is a reading measure and these rows are not prose: row 01 carries the
   brief's three legends on one line, which is 71 characters. */
.ct-steps li {
  max-width: 78ch;
  display: grid;
  grid-template-columns: 3rem minmax(0, 1fr);
  grid-template-areas:
    'n t'
    '. v';
  column-gap: var(--s4);
  row-gap: var(--s1);
}

/* THE STEPS' RAIL. A vertical hairline the three numbers hang off, drawn
   top to bottom on --mk-in as the section arrives — the same scaleY-on-a-
   rail construction as `.ct-spine::after` above and as §5's spine at home.
   It runs through the number column at the digits' own midline; the numbers
   themselves carry the page's background (below), so the rail reads as
   passing BEHIND each number rather than striking through it — a rail
   threading three stations, which is what the section is saying. */
.ct-steps { position: relative; }

.ct-steps-rail {
  position: absolute;
  /* The digits are 12px mono in a 3rem column; 9px is their midline. */
  left: 9px;
  top: var(--s3);
  bottom: var(--s3);
  width: 1px;
  background: var(--graphite);
  transform: scaleY(var(--mk-in, 1));
  transform-origin: top center;
}

.ct-step-n {
  grid-area: n;
  color: var(--ink-60);
  /* Positioned, and later in the DOM than the rail, so it paints above it;
     the background is the mask that keeps the digits legible. */
  position: relative;
  justify-self: start;
  padding-bottom: 4px;
  background: var(--page);
}

.ct-step-t {
  grid-area: t;
  font-weight: 500;              /* Inter 500 — the site carries no 700 */
  color: var(--ink);
}

.ct-step-v {
  grid-area: v;
  color: var(--ink-60);
  font-size: 15px;
}

/* Step 02's value arrives AFTER its own row, on its own `--mk-in` written by
   contact.js. It is the one value on the page that lands rather than simply
   being there, and the tick beside it draws on the same curve. The rise is
   6px rather than the row's 10px: a value settling into a row that has
   already arrived should move less than the row did. */
.ct-step-late {
  opacity: var(--mk-in, 1);
  transform: translate3d(0, calc((1 - var(--mk-in, 1)) * 6px), 0);
}

/* THE ONE GREEN ON THIS PAGE OUTSIDE THE WALL. Step 02's value is the only
   thing on /contact/ that arrives, and rule 1 permits green on a value in
   the moment after it changed. The tick is drawn by stroke-dashoffset — the
   same path, the same pathLength="1" and the same construction as kit.js's,
   so the site has one tick shape and not two. Default offset 0: with no
   JavaScript the tick is simply drawn. */
.ct-tick {
  width: 14px;
  height: 14px;
  vertical-align: -2px;
  margin-right: var(--s2);
  stroke: var(--leaf);
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 0;
}

/* ── channels (present only when site.js has a real one) ───────────────── */

.ct-channels a {
  border-bottom: 1px solid var(--graphite);
  padding-bottom: 2px;
  transition: border-color 180ms var(--ease-out), color 180ms var(--ease-out);
}

.ct-channels a:hover { border-bottom-color: var(--ink); color: var(--ink); }

/* ══ /404 ═════════════════════════════════════════════════════════════════ */

/* ── the empty bay ─────────────────────────────────────────────────────── */

/* The slot reserves the composition's box before `notfound.js` mounts into
   it. 104px is APPROACH (40) + LANE_H (64) from that module, plus the
   sheet's own 1px top edge. Those two constants live in the JS because the
   JS also computes the junction from them; this is the one place the number
   is repeated, and it is repeated rather than measured because measuring it
   would mean a layout read on every mount. */
.nf-empty-slot { min-height: 105px; }

.nf-empty {
  position: relative;
  background: var(--page);
  color: var(--ink);
  font-family: var(--font-body);
  border-top: var(--hairline);      /* the sheet's own top edge */
  min-width: 0;
}

/* `clip`, not `hidden` and never `auto`: it trims the current's entrance and
   exit without creating a scroll container — the same choice `.dash` and
   `.dash-wall` already make. The packet starts and ends ABOVE this crop,
   which is the whole of its "appear" and "disappear": no opacity is written
   for it in any frame. */
.nf-empty-body {
  position: relative;
  height: var(--nf-body-h, 104px);
  overflow: clip;
}

/* The spine is SOLID: it is a real route. The lane it arrives at is not. */
.nf-empty-spine {
  position: absolute;
  left: var(--s4);
  top: 0;
  bottom: 0;
  width: 0;
  display: block;
  border-left: var(--hairline);
}

/* Stretched down the spine, so a percentage translate resolves against the
   body's own height and the junction is a pure fraction — no ResizeObserver,
   no getBoundingClientRect, nothing to re-measure at any width. Identical
   mechanism to `.dash-wall .dash-spine-run`. */
.nf-empty-run {
  position: absolute;
  inset: 0;
  will-change: transform;
}

/* Copied verbatim from `.dp-packet` / `.orbit-packet`. It stays verbatim:
   this is the one shape a returning visitor already knows. The box-shadow is
   STATIC — a halo on a light source, never animated. */
.nf-empty-packet {
  position: absolute;
  left: 0;
  top: 0;
  width: 8px;
  height: 8px;
  margin-left: -4px;
  margin-top: -4px;
  border-radius: 50%;
  background: radial-gradient(circle at 36% 30%, var(--leaf-hot), var(--leaf) 55%, var(--leaf-ink));
  box-shadow: 0 0 0 3px var(--leaf-14);
}

/* THE DASHED RULE IS THE WHOLE MESSAGE. Every other rule on this site is
   solid, so one dashed hairline reads immediately as "drawn but not built".
   The lane has no name, because there is nothing here to name. */
.nf-empty-lane {
  position: absolute;
  left: 0;
  right: 0;
  top: var(--nf-approach, 40px);
  height: var(--nf-lane-h, 64px);
  display: grid;
  grid-template-columns: 5rem minmax(0, 1fr) 5.5rem;
  align-items: center;
  gap: var(--s3);
  padding-left: calc(var(--s4) + var(--s5));
  border-top: 1px dashed var(--graphite);
}

/* The junction, hollow. Structure, not signal — graphite in every frame,
   because no current ever reaches past it. Geometry is
   `.dash-wall .dash-lane::before`'s, verbatim. */
.nf-empty-lane::before {
  content: '';
  position: absolute;
  left: calc(var(--s4) - 2px);
  top: 50%;
  width: 5px;
  height: 5px;
  margin-top: -2.5px;
  border-radius: 50%;
  background: var(--page);
  border: 1px solid var(--graphite);
}

.nf-empty-bay,
.nf-empty-status {
  font-size: 11px;
  letter-spacing: 0.16em;
  color: var(--ink-60);
}

.nf-empty-status { text-align: right; }

/* A DEAD TRACE. Forty-four bars, all parked at the floor, written once by
   this rule and never touched again — no per-frame work, and the only honest
   reading an empty bay can give. The floor is `system.js`'s MIN_H so the two
   traces sit at the same height when they are on screen together. */
.nf-empty-trace {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  height: 22px;
  min-width: 0;
  contain: layout paint;
}

.nf-empty-trace i {
  flex: 0 0 1px;
  width: 1px;
  height: 100%;
  background: var(--graphite);
  transform: scaleY(0.06);
  transform-origin: bottom;
}

/* Nothing CSS owns may move underneath the settled frame the module paints. */
.nf-empty[data-static] *,
.nf-empty[data-static] *::before,
.nf-empty[data-static] *::after { animation: none; transition: none; }

/* ── the nine ──────────────────────────────────────────────────────────── */

.nf-nine-lede { margin-bottom: var(--s5); }
.nf-nine .mk-board { margin-bottom: var(--s6); }

/* The anchor carries the padding, not the <li>, so the whole ruled band is
   the hit target rather than a line of text inside it. */
.nf-index li {
  max-width: none;
  padding: 0;
  transition: border-color 180ms var(--ease-out);
}

.nf-row {
  display: grid;
  grid-template-columns: 3rem 9rem minmax(0, 1fr) auto 1.5rem;
  gap: var(--s3);
  align-items: baseline;
  min-height: 44px;                 /* the hit-target floor, at every width */
  padding: var(--s3) 0;
}

.nf-row-bay { color: var(--ink-60); }

.nf-row-name {
  font-family: var(--font-display);
  font-weight: 800;
  letter-spacing: var(--display-tracking);
}

.nf-row-line { color: var(--ink-60); }
.nf-row-handles { color: var(--ink-60); }

.nf-row-go {
  justify-self: end;
  color: var(--ink-60);
  transition: transform 180ms var(--ease-out), color 180ms var(--ease-out);
}

/* HOVER CHANGES TWO THINGS AND NOTHING ELSE. No card, no lift, no shadow:
   the row's own top hairline commits from graphite to ink, and the chevron
   moves 4px. A hairline belongs to the region below it, so darkening the top
   rule is darkening THIS row's own edge. */
.nf-index li:hover,
.nf-index li:focus-within { border-top-color: var(--ink); }

.nf-index li:hover .nf-row-go,
.nf-index li:focus-within .nf-row-go {
  transform: translate3d(4px, 0, 0);
  color: var(--ink);
}

/* The last row's closing rule is the list's, not a row's — commit it too, or
   hovering the ninth row darkens one of its two edges. */
.nf-index li:last-child:hover,
.nf-index li:last-child:focus-within { border-bottom-color: var(--ink); }

/* base.css already sets :focus-visible to 1px --ink at 3px offset, sitewide
   and never browser blue. Nothing to add — stated so nobody adds it twice. */

/* ══ BREAKPOINTS ══════════════════════════════════════════════════════════ */

/* The nine rows RESTRUCTURE rather than compress: handles are the least
   load-bearing text on a row, so they go first and give their width back to
   the headline. */
@media (max-width: 1080px) {
  .nf-row { grid-template-columns: 3rem 9rem minmax(0, 1fr) 1.5rem; }
  .nf-row-handles { display: none; }
}

@media (max-width: 720px) {
  .nf-row {
    grid-template-columns: 2.6rem minmax(0, 1fr) 1.5rem;
    grid-template-areas:
      'bay name go'
      '.   line go';
    gap: 2px var(--s3);
    align-items: center;
  }
  .nf-row-bay { grid-area: bay; }
  .nf-row-name { grid-area: name; }
  .nf-row-line { grid-area: line; }
  .nf-row-go { grid-area: go; align-self: center; }

  /* The agenda's spine folds away: at this width a 4rem gutter costs the
     sentences a third of their measure, and the tag above the sentence is
     the same information in the space available. */
  .ct-agenda { grid-template-columns: minmax(0, 1fr); }
  .ct-spine { display: none; }
  .ct-agenda-list li { grid-template-columns: minmax(0, 1fr); row-gap: var(--s1); }

  .ct-steps li {
    grid-template-columns: 2.6rem minmax(0, 1fr);
    column-gap: var(--s3);
  }

  /* The empty bay tightens to the wall's own narrow spine offset. Halving
     the trace keeps its full width and doubles its pitch — crisper texture,
     not a denser one. Same choice `system.js` makes at the same width. */
  .nf-empty-lane {
    grid-template-columns: 4.5rem minmax(0, 1fr) 4.5rem;
    gap: var(--s2);
    padding-left: calc(var(--s3) + var(--s4));
  }
  .nf-empty-spine { left: var(--s3); }
  .nf-empty-lane::before { left: calc(var(--s3) - 2px); }
  .nf-empty-trace i:nth-child(even) { display: none; }
}

/* Below 420px the trace is the first thing that can go: the two mono labels
   and the dashed rule are the composition's meaning, and a 60px strip of
   texture between them is not. */
@media (max-width: 420px) {
  .nf-empty-lane { grid-template-columns: 4.5rem minmax(0, 1fr); }
  .nf-empty-trace { display: none; }
}

/* ══ /pricing/ ════════════════════════════════════════════════════════════

   Two overrides of page-services.css and nothing else, both by source order
   against equal specificity — which is why this sheet loads last on that
   route. Nothing in this block may appear on /contact/ or /404: neither
   page has a `.sv-` element for these selectors to reach.
   ═══════════════════════════════════════════════════════════════════════ */

/* THE MODULE CELLS ARRIVE, not merely appear. page-services.css fades each
   filled cell in on its own window of --sv-in, one cell after another; this
   adds the arrival's weight — each fill lands from scale 0.6, so the strip
   reads as nine quiet ticks rather than a wash. The cadence is the existing
   0.02 of --sv-in per cell, which at a steady scroll is the design's
   40ms-apart tick; scroll-driven, so scrubbing back un-ticks them in the
   same order, as the director's purity contract requires. The ::after is an
   inset-0 fill inside a hairline outline that never moves, so the scale
   costs no layout and the standing structure stays put. */
.sv-glyph i.is-on::after {
  transform: scale(calc(0.6 + 0.4 * var(--sv-p, 1)));
}

/* COLUMN HOVER — the hairline-to-ink shift. pricing.js writes the hovered
   column's index onto the table as `data-hover-col`; committing every left
   rule in that column is committing the ONE vertical hairline the column
   owns, top to bottom, which is the same gesture the nine rows on /404 make
   with their top rule. Colour only, 180ms, input-driven.

   Gated to hover-capable pointers — on touch the attribute is never written
   and the transition would be dead weight — and to widths where the table
   still has columns: below 861px it collapses to ruled blocks whose cells
   carry no left rule to shift. */
@media (hover: hover) and (min-width: 861px) {
  .sv-compare thead th,
  .sv-compare tbody td {
    transition: border-color 180ms var(--ease-out);
  }

  .sv-compare[data-hover-col='0'] [data-c='0'],
  .sv-compare[data-hover-col='1'] [data-c='1'],
  .sv-compare[data-hover-col='2'] [data-c='2'] {
    border-left-color: var(--ink);
  }
}

/* ══ REDUCED MOTION ═══════════════════════════════════════════════════════ */

/* The modules already paint their settled frames and skip the director, so
   all this does is stop the input-driven transitions and settle the two
   /pricing/ overrides above. The reveal itself needs no rule here: it is a
   scrubbed custom property with no transition on it, and the modules write
   it to 1 once. The steps' rail is likewise settled by contact.js writing
   --mk-in to 1, but `transform: none` is restated so the rail's rest state
   never depends on a module having run. */
@media (prefers-reduced-motion: reduce) {
  .nf-index li,
  .nf-row-go,
  .ct-channels a,
  .mk-cta-row .ghost-link { transition: none; }

  .ct-steps-rail { transform: none; }

  /* This sheet loads after page-services.css on /pricing/, so its own
     reduced-motion settling of the module cells (`transform: none`) would
     lose to the scale rule above by source order. Restated here — after it
     — so the cells sit landed, and the column rules commit with no ease. */
  .sv-glyph i.is-on::after { transform: none; }
  .sv-compare thead th,
  .sv-compare tbody td { transition: none; }
}
