/* ═══════════════════════════════════════════════════════════════════════
   Target Node AD - home page stylesheet

   Shared by index.html and examples.html. The examples page loads this first
   and then examples.css, which holds only what that page adds.

   The system here follows the Precept reference: a light paper ground with
   near-black text, full-bleed dark panels for the instrument figures, one
   grotesque (Geist) for everything structural and one monospace (Space Mono)
   for labels. The one rule that carries most of the look is that display type
   gets negative tracking in proportion to its size, loosening to normal by the
   time it reaches body copy.
   ═══════════════════════════════════════════════════════════════════════ */

@font-face{
  font-family:'Geist';
  font-style:normal;
  font-weight:100 900;               /* one variable file covers 400 and 500 */
  font-display:swap;
  src:url(fonts/geist.woff2) format('woff2');
}
@font-face{
  font-family:'Space Mono';
  font-style:normal;
  font-weight:400;
  font-display:swap;
  src:url(fonts/space-mono.woff2) format('woff2');
}
@font-face{
  font-family:'Space Mono';
  font-style:normal;
  font-weight:700;
  font-display:swap;
  src:url(fonts/space-mono-700.woff2) format('woff2');
}

/* ══ tokens ══════════════════════════════════════════════════ */
:root{
  /* paper */
  --paper:#F4F4F2;                   /* warm off-white, never pure */
  --white:#FFFFFF;
  --ink:#171717;                     /* near-black, never pure */
  --ink-2:#333333;
  --ink-3:#6B6B6B;
  --rule:#E4E4E4;
  --rule-soft:rgba(23,25,27,0.06);

  /* the instrument panels keep the original dark palette */
  --dark:#0B0D10;
  --dark-2:#12151A;
  --dark-line:#232830;
  --dark-line-2:#333A45;
  --dark-text:#E9ECF1;
  --dark-dim:#A2ABB8;
  --dark-faint:#6E7885;

  --accent:#2E6BE6;                  /* blue, darkened to hold on paper */
  --accent-bright:#5B9DFF;           /* the original, used on dark only */
  --accent-soft:rgba(46,107,230,0.10);
  --signal:#D9820A;                  /* amber, darkened to hold on paper */
  --signal-bright:#F5A524;           /* the original, used on dark only */
  --signal-soft:rgba(217,130,10,0.10);

  /* status dots. Desaturated so they read as instrument lamps rather than
     traffic lights, and chosen to hold on both the paper and the ink cards. */
  --ok:#4A8F62;
  --ok-bright:#5FBF7F;              /* the green, lifted to hold on ink */
  --bad:#C0574F;
  --warn:var(--signal);

  --sans:'Geist',-apple-system,BlinkMacSystemFont,"Segoe UI",Inter,Helvetica,Arial,sans-serif;
  --mono:'Space Mono',ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;

  /* Precept's scale. Tracking is bound to each step and is the whole trick:
     -0.07em at display, easing to normal by body. */
  --d1:clamp(3rem,11.5vw,12rem);     /* hero        192px */
  --d2:clamp(2.3rem,5.6vw,4rem);     /* section      64px */
  --d3:clamp(1.85rem,3.6vw,3rem);    /* subsection   48px */
  --d4:clamp(1.5rem,2.4vw,2rem);     /* card         32px */
  --d5:1.5rem;                       /* item         24px */

  --tr1:-0.07em;
  --tr2:-0.06em;
  --tr3:-0.04em;
  --tr4:-0.04em;
  --tr5:-0.03em;

  --t-xs:0.6875rem;                  /* mono labels        11px */
  --t-sm:0.75rem;                    /* mono, larger       12px */
  --t-body:0.9375rem;                /* body               15px */
  --t-lead:1.125rem;                 /* lede               18px */

  --wrap:1360px;
  --gut:clamp(1.15rem,4vw,3.5rem);
  --r-card:8px;
  --r-pill:999px;

  --ease:cubic-bezier(.3,0,.1,1);
}

*{box-sizing:border-box;margin:0;padding:0;}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth;}
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto;}
  *{transition:none !important;animation:none !important;}
}
/* The script drives window.scrollTo frame by frame, so the browser's own
   smooth scrolling has to be out of the way -- left on, the two chase the same
   position and every frame lands late. .lerp is set by the script only once it
   has actually taken over, so without it (no JS, touch, reduced motion) the
   rule above still stands and anchors scroll smoothly the native way. */
html.lerp{scroll-behavior:auto;}
body{
  font-family:var(--sans);
  background:var(--paper);
  color:var(--ink);
  font-size:var(--t-body);
  line-height:1.6;
  letter-spacing:normal;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
img,svg,video,canvas{display:block;max-width:100%;}
a{color:inherit;text-decoration:none;}
:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:2px;}

.wrap{max-width:var(--wrap);margin:0 auto;padding:0 var(--gut);}
.mono{font-family:var(--mono);}

.skip{position:absolute;left:-9999px;top:0;background:var(--ink);color:var(--paper);
  padding:0.7rem 1.1rem;font-weight:500;z-index:200;}
.skip:focus{left:0.75rem;top:0.75rem;}

.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;}

/* ══ type ════════════════════════════════════════════════════ */
h1,h2,h3,h4{font-weight:500;line-height:1.08;text-wrap:balance;}
.d1{font-size:var(--d1);letter-spacing:var(--tr1);line-height:0.8;}
.d2{font-size:var(--d2);letter-spacing:var(--tr2);line-height:1.1;}
.d3{font-size:var(--d3);letter-spacing:var(--tr3);line-height:1.2;}
.d4{font-size:var(--d4);letter-spacing:var(--tr4);line-height:1.2;}
.d5{font-size:var(--d5);letter-spacing:var(--tr5);line-height:1.3;}

/* the mono label that sits above or beside almost everything */
.lab{
  font-family:var(--mono);font-size:var(--t-xs);font-weight:400;
  letter-spacing:0.14em;text-transform:uppercase;color:var(--ink-3);
}
/* Section eyebrows. "Introducing" above the Faros Agent block set the size for
   these, so the labels that head a whole section match it instead of using the
   11px .lab meant for cards and footers. Keep in step with .intro .kick. */
.lab-lg{font-size:0.9375rem;letter-spacing:0.22em;}
.lab-a{color:var(--accent);}
.dim{color:var(--ink-3);}
p+p{margin-top:0.9rem;}
b,strong{font-weight:500;color:var(--ink);}

/* ══ buttons ═════════════════════════════════════════════════ */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:0.5rem;
  padding:0.85rem 1.6rem;border-radius:var(--r-pill);
  font-family:var(--sans);font-size:var(--t-body);font-weight:500;
  letter-spacing:-0.01em;border:1px solid transparent;cursor:pointer;
  transition:background-color .2s ease,color .2s ease,border-color .2s ease,
             transform .2s var(--ease);
}
.btn:hover{transform:translateY(-1px);}
.btn:active{transform:translateY(0);}
.btn-dark{background:var(--ink);color:var(--paper);}
.btn-dark:hover{background:#000;}
.btn-light{background:var(--white);color:var(--ink);}
.btn-light:hover{background:#EDEDEA;}
.btn-line{border-color:var(--ink);color:var(--ink);background:transparent;}
.btn-line:hover{background:var(--ink);color:var(--paper);}
.btn-line-l{border-color:rgba(255,255,255,0.4);color:#fff;background:transparent;}
.btn-line-l:hover{background:#fff;color:var(--ink);}
.btn-sm{padding:0.55rem 1.1rem;font-size:var(--t-sm);font-family:var(--mono);
  letter-spacing:0.04em;}

/* ══ drafting rules ══════════════════════════════════════════
   Thin vertical rules with a dot at each crossing, showing only in the
   whitespace. Purely decorative, so it is hidden from assistive tech and
   never intercepts a click. */
.ruled{position:relative;}
.ruled>*{position:relative;z-index:1;}
.ruled::before{
  content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background-image:linear-gradient(90deg,var(--rule) 1px,transparent 1px);
  background-size:25% 100%;background-position:0 0;
  opacity:0.9;
}
@media (max-width:900px){.ruled::before{background-size:50% 100%;}}

/* ══ header ══════════════════════════════════════════════════
   A floating plate in the top-left, as on the reference, rather than a full
   width bar. The plate stays dark the whole way down so the mark is the same
   mark on every section -- its centre dot is currentColor, so letting the
   plate invert would have flipped the dot from white to ink halfway through
   the page. Only the bare Menu control beside it inverts, because that one
   sits directly on the section behind it. */
header{position:fixed;top:0;left:0;right:0;z-index:100;pointer-events:none;
  /* one solid bar edge to edge, so the plate and the arrow sit on black
     rather than floating with the page showing between them */
  background:var(--dark);}
.head{
  display:flex;align-items:flex-start;justify-content:space-between;
  gap:1rem;padding-top:0;
}
.plate{
  pointer-events:auto;
  display:grid;grid-template-columns:auto 1fr;grid-template-rows:auto auto;
  gap:0.1rem 0.75rem;align-items:center;
  padding:1rem 1.6rem 1rem 1.15rem;
  background:rgba(11,13,16,0.88);   /* opaque enough to stay dark over paper */
  backdrop-filter:saturate(150%) blur(14px);
  -webkit-backdrop-filter:saturate(150%) blur(14px);
  transition:background-color .35s ease,color .35s ease;
  color:#fff;
}
.plate .mark{grid-row:1 / 3;width:52px;height:52px;}
.plate .mark svg{width:100%;height:100%;}
.plate .p-crumb{font-family:var(--mono);font-size:var(--t-xs);letter-spacing:0.1em;
  text-transform:uppercase;opacity:0.55;align-self:end;}
.plate .p-name{font-size:var(--t-body);font-weight:500;letter-spacing:-0.02em;
  align-self:start;white-space:nowrap;}
.head-r{pointer-events:auto;display:flex;align-items:center;gap:0.6rem;
  padding:1.15rem 0 0 0;}
.menu-t{font-size:var(--t-body);font-weight:500;letter-spacing:-0.02em;color:#fff;
  transition:color .35s ease;}
header.on-paper .menu-t{color:var(--ink);}
/* the same plate treatment as the brand block, so the two read as one pair:
   constant dark ground, dots in the mark's blue. Like the plate, it does not
   invert on paper -- that is what kept it looking like a different control on
   every section. */
.menu-b{
  width:36px;height:36px;border-radius:8px;border:0;cursor:pointer;
  background:rgba(11,13,16,0.88);
  backdrop-filter:saturate(150%) blur(14px);
  -webkit-backdrop-filter:saturate(150%) blur(14px);
  display:grid;place-items:center;
}
.menu-b span{
  display:grid;grid-template-columns:repeat(3,3px);gap:2px;
}
.menu-b i{width:3px;height:3px;border-radius:50%;background:var(--accent-bright);}
/* the menu is parked, not removed -- the label, the grid button and the panel
   it opens all stay in the page for when it comes back. A back-to-top arrow
   holds its spot, on the same plate treatment as the button it replaces. */
.menu-t,.menu-b{display:none;}
.top-b{
  width:36px;height:36px;border-radius:8px;
  background:rgba(11,13,16,0.88);
  backdrop-filter:saturate(150%) blur(14px);
  -webkit-backdrop-filter:saturate(150%) blur(14px);
  display:grid;place-items:center;color:var(--accent-bright);
  /* pointless at the top of the page, so it waits until you have scrolled */
  opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s;
}
.top-b.is-on{opacity:1;visibility:visible;}
.top-b svg{width:16px;height:16px;}

/* the panel the grid button opens */
.menu{
  position:fixed;inset:0;z-index:150;background:var(--dark);color:#fff;
  display:grid;place-items:center;
  opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s;
}
.menu[data-open="true"]{opacity:1;visibility:visible;}
.menu nav{display:grid;gap:0.4rem;text-align:center;}
.menu nav a{font-size:clamp(2rem,6vw,3.6rem);letter-spacing:var(--tr2);font-weight:500;
  transition:opacity .2s ease;}
.menu nav a:hover{opacity:0.55;}
.menu-x{position:absolute;top:1.4rem;right:var(--gut);width:44px;height:44px;
  border-radius:50%;border:1px solid rgba(255,255,255,0.3);background:transparent;
  color:#fff;cursor:pointer;font-size:1.2rem;line-height:1;}

/* ══ hero ════════════════════════════════════════════════════ */
.hero{
  position:relative;min-height:100svh;display:flex;flex-direction:column;
  /* Centred rather than bottom-anchored. The credential strip that used to
     terminate the hero is gone, so flex-end left the block sitting low with a
     large void above it. The top padding clears the fixed header plate, which
     centring alone would not guarantee -- and because centring divides the
     surplus evenly, it is also the lever that sets how high the block rides.
     At 11vh the block sat noticeably high: 170px under the header against
     242px under the CTA at 900px tall. 18vh brings those to 202/210. */
  justify-content:center;background:var(--dark);color:#fff;overflow:hidden;
  padding-top:clamp(7rem,18vh,11rem);
  padding-bottom:clamp(3rem,6vw,5.5rem);
}
/* the media layer. data-mode picks which child is live, so dropping in real
   footage later is a one-attribute change and nothing here has to move. */
.hero-media{position:absolute;inset:0;z-index:0;}
.hero-media::after{
  content:"";position:absolute;inset:0;
  background:
    linear-gradient(180deg,rgba(11,13,16,0.72) 0%,rgba(11,13,16,0.25) 38%,rgba(11,13,16,0.88) 100%),
    radial-gradient(ellipse 70% 60% at 70% 40%,rgba(46,107,230,0.16),transparent 70%);
}
.hero-canvas,.hero-video{position:absolute;inset:0;width:100%;height:100%;}
.hero-video{object-fit:cover;}
.hero-media[data-mode="canvas"] .hero-video{display:none;}
.hero-media[data-mode="video"] .hero-canvas{display:none;}

.hero-inner{position:relative;z-index:1;width:100%;}
/* Two sentences rather than two words, so the display size comes right down
   from the reference's 192px and the tracking eases with it. Sentence case
   also needs real leading -- 0.82 clipped the descenders. */
.hero h1{
  font-size:clamp(1.9rem,3.9vw,3.6rem);letter-spacing:-0.035em;
  line-height:1.02;font-weight:500;text-transform:none;
  margin-bottom:clamp(0.8rem,1.6vw,1.2rem);
  text-wrap:balance;
}
.hero h1 .l1{display:block;}
.hero h1 .l2{display:block;}
/* the promise in the headline, in the brand blue. Amber is reserved for the
   alert states on the figures and status lamps, so it stays out of here. */
.hero h1 em{font-style:normal;color:var(--accent-bright);}
.hero h1 .glyph{
  display:inline-block;vertical-align:baseline;width:0.62em;height:0.62em;
  margin:0 0.12em;
}
.hero h1 .glyph svg{width:100%;height:100%;}
.hero .lede{
  font-size:clamp(0.95rem,1.15vw,1.125rem);line-height:1.5;color:rgba(255,255,255,0.78);
  max-width:56ch;letter-spacing:-0.01em;margin-bottom:clamp(1.2rem,2.4vw,1.7rem);
}
.hero-cta{display:flex;flex-wrap:wrap;gap:0.75rem;margin-bottom:clamp(1rem,2vw,1.5rem);}

/* The home hero's copy is centred over the diagram below it, which is itself
   symmetrical about the node. Scoped away from .hero-ex, because the examples
   hero stays left-aligned -- and the card copy inside the diagram must not be
   caught by this, so the rules target the text elements rather than a shared
   ancestor. */
.hero:not(.hero-ex) h1{text-align:center;}
.hero:not(.hero-ex) .lede{text-align:center;margin-inline:auto;}
.hero:not(.hero-ex) .hero-cta{justify-content:center;}

/* ══ signal path ═════════════════════════════════════════════
   Left half is one SVG on a fixed viewBox, so the wires land exactly on the
   ring. Right half is real markup, so the report copy wraps and stays
   selectable. Packets ride the wire paths with animateMotion; the script
   pauses every SVG animation outright when reduced motion is asked for. */
/* The hero above is the same ground, so a full section padding here reads as
   dead space rather than separation. The bottom keeps the standard rhythm,
   because the section below it is light. */
/* .sec.sig, not .sig -- .sec sets `padding` as a shorthand further down the
   file, so a single-class rule up here loses the cascade and the trim silently
   does nothing.

   The trim has a floor. Every card behind the front one is translated up 14px,
   so the stack rises 42px above the deck box, and the section clips its
   overflow. The stack clears the top edge only while
   padding-top + stage height / 2 >= 42 + 330 / 2, i.e. 207px. The stage is
   widest-dependent, and at 1041px -- the narrowest width where the deck is
   still laid over the figure -- half of it is about 162px, so 44px is the
   least this can be without cutting the top card. Below 1041px the deck drops
   under the figure and the standard section rhythm applies again. */
@media (min-width:1041px){
  .sec.sig{padding-top:clamp(2.75rem,3vw,3.5rem);}
}
/* One full-width stage rather than two columns: the node has to sit at exactly
   50% of the section, which a grid track cannot guarantee. The SVG sets the
   height; the deck is laid over the right of it at the point the outgoing line
   stops, so the line and the card meet without a bridge. */
.sig-grid{position:relative;width:100%;max-width:min(100%,175vh);margin-inline:auto;}
/* overflow:visible lets the halo bloom past the viewBox, which is the point --
   but a painted SVG shape also swallows clicks, and the halo reaches ~60px
   above its own box, straight over the Book a demo button. The figure is
   decorative and has nothing to click, so it takes itself out of hit
   testing entirely. The report cards are HTML outside this SVG, so their
   hover-to-pause is unaffected. */
.sig-figure svg{width:100%;height:auto;display:block;overflow:visible;pointer-events:none;}
/* The wide figure is drawn for a landscape column -- 1360 by 452, nearly three
   times wider than tall. On a phone it lands at about 353 by 117, which scales
   every label down to roughly 4px: still on the page, but nothing in it can be
   read. The portrait figure says the same thing stacked instead of strung out,
   with the sources across the top funnelling down into the node and the
   finding leaving the bottom. Only ever one of the two is in the document. */
.sig-figure .sig-tall{display:none;max-width:440px;margin-inline:auto;}
/* Opaque, not the 3.5% white it looks like. Over --dark (#0B0D10) that wash
   composites to exactly this value, so nothing changes to look at -- but each
   wire now runs a little way under its chip so a chip drifting sideways never
   uncovers the start of its own cable, and at 3.5% the line showed straight
   through the label. The chips sit well outside the halo, so there is no
   gradient behind them for a flat fill to flatten. */
/* These sit over the hero canvas and the node's glow, so they need an edge
   and a shadow to read as cards floating above it rather than holes cut
   into it. The fill stays opaque -- a translucent one lets the waves run
   straight through. */
.sig-chip{fill:#141518;stroke:#2E3642;filter:drop-shadow(0 3px 8px rgba(0,0,0,0.55));}
/* The source chips drift, so the panel reads as something live rather than a
   diagram printed on the page. Each carries its own period and starts part way
   into it, or the five would rise and fall together and look like one object
   on a hinge. The travel is 2.5 user units either way and every wire ends well
   inside its chip, so nothing pulls away from its cable at the extremes. The
   reduced-motion rule at the top of the file already stops all of this. */
/* A chip drifts along the axis its own cable leaves on. That is the whole
   trick: the wide figure's wires run out sideways, so those chips travel left
   and right and the join stays put on the chip's edge while the chip slides
   along the line. The portrait figure's cables drop from the underside of CAN
   BUS, so there the travel is vertical for the same reason. Drift either of
   them the other way and the join walks up and down the edge until the line
   looks like it is hanging off a corner. */
.sig-node{animation-duration:var(--f,9s);animation-delay:var(--fd,0s);
  animation-timing-function:ease-in-out;animation-iteration-count:infinite;}
@keyframes sigFloatX{
  0%,100%{transform:translateX(calc(var(--fy,7px) * -1));}
  50%{transform:translateX(var(--fy,7px));}
}
@keyframes sigFloatY{
  0%,100%{transform:translateY(calc(var(--fy,7px) * -1));}
  50%{transform:translateY(var(--fy,7px));}
}
/* The two are drawn at very different scales -- 1360 units across against 390
   -- so equal travel in user units would read as three times the movement on
   the phone. These land both at roughly six screen pixels either way. */
.sig-wide .sig-node{--fy:10px;animation-name:sigFloatX;}
.sig-tall .sig-node{--fy:7px;animation-name:sigFloatY;}
.sig-chiptx{font-family:var(--mono);font-size:16px;letter-spacing:0.13em;fill:var(--dark-dim);}
/* The portrait figure's chips are 150 units wide against the wide one's 180,
   so the longest label (FAULT CODES) runs past the box at 16px. Same type,
   set smaller only here -- the portrait viewBox is 390 units rendered at
   ~440px, so 13 units still lands near 15px on screen. */
.sig-tall .sig-chiptx{font-size:13px;letter-spacing:0.10em;}

.sig-wire{fill:none;stroke:rgba(255,255,255,0.15);stroke-width:1;}
.sig-out{fill:none;stroke:var(--signal-bright);stroke-width:1.2;opacity:0.55;}
.sig-pkt.out{fill:var(--signal-bright);}
.sig-pkt{fill:var(--accent-bright);opacity:0;}
.sig-name{font-family:var(--sans);font-size:25px;font-weight:500;letter-spacing:-0.03em;fill:#fff;}
.sig-sub{font-family:var(--mono);font-size:11px;letter-spacing:0.18em;fill:var(--dark-faint);}
.sig-halo{transform-box:fill-box;transform-origin:center;}
html.anim .sig-halo{animation:sigBreathe 4.5s var(--ease) infinite;}
@keyframes sigBreathe{0%,100%{opacity:0.85;}50%{opacity:1;}}

/* ── the deck ── */
.sig-deck{
  /* 50% works only because the node sits at the viewBox midpoint (226 of 452).
     If the figure is redrawn with the node off-centre, this has to follow or
     the outgoing line will stop short of the card. */
  position:absolute;left:66%;right:0;top:50%;transform:translateY(-50%);
  /* body-size foot wraps to two lines, hence the extra room */
  min-height:358px;
}
.sig-card{
  position:absolute;inset:0;
  display:flex;flex-direction:column;
  padding:clamp(1rem,1.5vw,1.25rem);
  border:1px solid #2A313B;border-radius:10px;background:#12151A;
  /* All four cards share the deck's one footprint, so they are the same size
     and sit exactly on top of each other. Only the front card (--i:0) is
     visible; a cycle is a plain crossfade, the incoming card fading in over
     the outgoing one. */
  opacity:calc(1 - min(var(--i),1));
  z-index:calc(10 - var(--i));
  /* most of the 2.5s cycle, so the crossfade breathes at the cadence of
     the packets riding the wires on the left. Not var(--ease): that curve
     lands early and sits, which reads as a fast fade and a long hold; a
     symmetric ease spends the whole 2s visibly fading. */
  transition:opacity 2s ease-in-out;
}
.sig-card::before{
  content:"";position:absolute;left:0;right:0;top:0;height:5px;
  border-radius:10px 10px 0 0;background:var(--signal-bright);
}
/* The stacked look is decoration now: two card-shaped ghosts fanned up and to
   the right behind the deck. They never move or cycle; the real cards all sit
   on the front slot and crossfade above them. The horizontal step is clamped
   because the only room to fan into is the gutter -- 56px at 1440 but 18px at
   390 -- and the section clips its overflow. */
.sig-deck::before,.sig-deck::after{
  content:"";position:absolute;inset:0;border-radius:10px;
  border:1px solid #2A313B;
  background:linear-gradient(var(--signal-bright),var(--signal-bright)) top/100% 5px no-repeat,#12151A;
}
.sig-deck::before{
  transform:translate(clamp(6px,1vw,14px),-14px) scale(0.974);
  opacity:0.74;z-index:2;
}
.sig-deck::after{
  transform:translate(calc(2 * clamp(6px,1vw,14px)),-28px) scale(0.948);
  opacity:0.48;z-index:1;
}
/* without scripting only the front card exists, so nothing sits half-faded */
html:not(.js) .sig-card[data-i]:not([data-i="0"]){display:none;}
/* The two chips are both nowrap, so a long category name pushes the row wider
   than the card rather than being shortened. Wrapping is the backstop: below
   about 340px "FARM ROBOT" cannot share a line with "Found a problem" at any
   sensible type size, so it drops to its own. */
.sig-top{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:0.75rem;margin-bottom:1.05rem;}
.sig-flag,.sig-cat{
  display:inline-flex;align-items:center;gap:0.5rem;border-radius:var(--r-pill);
  padding:0.42rem 0.85rem;font-family:var(--mono);font-size:var(--t-xs);
  font-weight:700;letter-spacing:0.14em;text-transform:uppercase;white-space:nowrap;
}
.sig-flag{color:var(--signal-bright);background:rgba(245,165,36,0.12);border:1px solid rgba(245,165,36,0.45);}
.sig-cat{color:var(--ok-bright);background:rgba(95,191,127,0.12);border:1px solid rgba(95,191,127,0.45);}
.sig-flag i,.sig-cat i{width:7px;height:7px;border-radius:50%;flex:none;background:currentColor;}
.sig-klab{font-family:var(--mono);font-size:var(--t-xs);letter-spacing:0.16em;
  text-transform:uppercase;color:var(--dark-faint);margin-bottom:0.6rem;}
.sig-cause{font-size:clamp(1.2rem,1.75vw,1.45rem);line-height:1.14;letter-spacing:-0.035em;
  font-weight:500;color:#fff;margin-bottom:0.5rem;}
.sig-cause em{font-style:normal;color:var(--signal-bright);}
.sig-why{color:var(--dark-dim);font-size:var(--t-body);line-height:1.45;margin-bottom:1.4rem;}
.sig-chart{position:relative;margin-bottom:1.1rem;}
.sig-normal{position:absolute;left:0;bottom:100%;margin-bottom:0.3rem;
  font-family:var(--mono);font-size:10px;letter-spacing:0.14em;
  text-transform:uppercase;color:var(--dark-faint);}
.sig-spark{width:100%;height:64px;display:block;}
.sig-fill{fill:rgba(245,165,36,0.10);}
.sig-lim{stroke:rgba(245,165,36,0.42);stroke-width:1;stroke-dasharray:5 5;}
.sig-line{fill:none;stroke:var(--signal-bright);stroke-width:2;stroke-linejoin:round;
  vector-effect:non-scaling-stroke;}
.sig-dot{fill:var(--signal-bright);}
.sig-foot{margin-top:auto;padding-top:0.95rem;border-top:1px solid var(--dark-line);
  color:#9AA4B2;font-size:var(--t-body);line-height:1.5;}

@media (max-width:1040px){
  /* The swap happens on the same seam as the deck. Above it the figure has a
     column of its own and the wide drawing has the room it was built for; below
     it the layout is stacked, and the wide drawing would be squeezed to a strip
     whose labels run from 8px at this end to 4px on a phone. */
  .sig-figure .sig-wide{display:none;}
  .sig-figure .sig-tall{display:block;}
  /* relative, not static: the cards inside are absolutely positioned against
     the deck, so taking the deck out of the positioned chain hands them to
     .sig-grid instead and the whole stack paints at the top of the grid, over
     the figure. It went unnoticed while the figure was a 117px sliver -- there
     was nothing there to cover. top has to be released too, or the 50% from
     the desktop rule would push the deck down half its own height. */
  .sig-deck{position:relative;top:auto;transform:none;left:auto;right:auto;margin-top:2rem;}
  .sig-deck{min-height:352px;}
}
@media (max-width:520px){
  .sig-deck{min-height:404px;}
  .sig-top{flex-wrap:wrap;}
}

/* ══ generic light section ═══════════════════════════════════ */
.sec{padding:clamp(3rem,7vw,6.5rem) 0;}
/* A section is otherwise only as tall as whatever it holds, which on a large
   screen can be little more than half the window -- and no scroll position can
   hide that, since a 575px block simply cannot fill a 1080px window on its own.
   Standing them up to the window is what makes a section something you can look
   at by itself. min-height rather than height, so the two that already run
   longer than a screen are left alone, and the content is centred rather than
   left at the top, or the new space would all pool underneath it as a void. */
.sec{min-height:100svh;display:flex;flex-direction:column;justify-content:center;}
/* A flex item whose side margins are auto shrinks to its content instead of
   stretching, and .wrap is centred with exactly those margins -- so without
   this the whole column collapses to its narrowest possible width. */
.sec > .wrap{width:100%;}
.sec-paper{background:var(--paper);}
.sec-white{background:var(--white);}
/* label left, content right — the reference's About layout */
.cols{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,2.2fr);
  gap:clamp(1.6rem,5vw,4rem);align-items:start;}
.cols-2{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:clamp(1.6rem,4vw,3.5rem);}
.cols h3{font-size:var(--t-body);font-weight:500;letter-spacing:-0.01em;
  margin-bottom:0.6rem;}
.cols p{color:var(--ink-2);}

/* ══ operations overview ═════════════════════════════════════
   Four cards alternating ink and paper, each ending in a figure and a status
   lamp. The layout is the reference's; the treatment is this site's, so the
   corners are the 8px card radius rather than crop marks and the labels use
   the same tracked monospace as every other block. */
.ops{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:1rem;}
.ops-card{
  display:flex;flex-direction:column;
  padding:clamp(1.3rem,2vw,1.75rem);
  /* Sized for copy alone. The taller 290-360px was carrying the stat-and-lamp
     foot row, which is out until there are real figures for it; restore that
     range at the same time the row comes back. */
  min-height:clamp(200px,20vw,270px);
  border:1px solid var(--rule);border-radius:var(--r-card);
  background:var(--white);
  transition:border-color .25s ease,transform .25s var(--ease);
}
.ops-card:hover{transform:translateY(-2px);border-color:var(--ink);}
/* a pale card on a white section is only a hairline away from disappearing,
   so it takes the paper ground instead. :not() keeps the ink cards ink. */
.sec-white .ops-card:not(.is-ink){background:var(--paper);}
.ops-card.is-ink{background:var(--dark);border-color:var(--dark);color:#fff;}
.ops-card.is-ink:hover{border-color:var(--dark-line-2);}
/* a step up from the 11px shared label: these name the card and are the first
   thing read in it, so they carry more than an eyebrow does */
.ops-card .lab{display:block;margin-bottom:1.4rem;font-size:0.8125rem;
  letter-spacing:0.12em;}
.ops-card.is-ink .lab{color:var(--dark-faint);}
.ops-card h3{margin-bottom:0.7rem;}
.ops-card p{color:var(--ink-2);}
.ops-card.is-ink p{color:var(--dark-dim);}
/* the rule sits hard against the foot, so the figures line up across the row
   however much body copy each card carries */
.ops-foot{
  margin-top:auto;padding-top:1rem;border-top:1px dashed var(--rule);
  display:flex;align-items:center;justify-content:space-between;gap:1rem;
}
.ops-card.is-ink .ops-foot{border-top-color:var(--dark-line);}
.ops-stat{font-size:clamp(1.4rem,2.2vw,1.8rem);font-weight:500;
  letter-spacing:var(--tr4);line-height:1;font-variant-numeric:tabular-nums;}
.ops-state{display:inline-flex;align-items:center;gap:0.5rem;
  font-family:var(--mono);font-size:var(--t-xs);letter-spacing:0.12em;
  text-transform:uppercase;color:var(--ink-3);white-space:nowrap;}
.ops-card.is-ink .ops-state{color:var(--dark-dim);}
.ops-state i{width:8px;height:8px;border-radius:50%;flex:none;background:var(--ink-3);}
.ops-state.ok i{background:var(--ok);}
.ops-state.bad i{background:var(--bad);}
.ops-state.warn i{background:var(--warn);}

/* ══ product introduction ════════════════════════════════════
   Not a section head. The reader meets the product name here for the first
   time, so it gets the width and the silence of a reveal: mark, kicker, the
   name alone at display size, then one plain sentence saying what it is
   before any of the how. */
.intro{text-align:center;margin:0 auto clamp(2.4rem,5.5vw,4.25rem);}
.intro .mark{display:block;width:clamp(58px,5.4vw,82px);height:clamp(58px,5.4vw,82px);
  margin:0 auto 1.7rem;}
.intro .mark svg{width:100%;height:100%;}
/* larger than the 11px labels elsewhere: this one announces rather than files */
.intro .kick{display:block;font-family:var(--mono);font-size:0.9375rem;
  letter-spacing:0.22em;text-transform:uppercase;color:rgba(255,255,255,0.55);
  margin-bottom:1.2rem;}
.intro h2{font-size:clamp(2.6rem,6vw,5rem);letter-spacing:-0.045em;line-height:1;
  color:#fff;margin-bottom:1.5rem;}
/* the definition carries the weight, so it is set well above body size */
.intro .def{font-size:clamp(1.3rem,2.1vw,1.75rem);line-height:1.35;
  letter-spacing:-0.02em;color:#fff;max-width:24ch;margin:0 auto 1.1rem;}
.intro .sub{font-size:var(--t-lead);line-height:1.6;
  color:rgba(255,255,255,0.6);max-width:56ch;margin:0 auto;}

/* ══ process ═════════════════════════════════════════════════
   Sticky dial, stepping copy. position:sticky does the work, so with
   JavaScript off the steps simply read as a list and nothing is held. */
.proc{background:var(--dark);color:#fff;--dial:420px;}
.proc-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  gap:clamp(2rem,6vw,5rem);align-items:start;}
.proc-grid>*{min-width:0;}
/* The dial is parked so its CENTRE lands on 50vh -- the same line the step
   observer uses to pick the active step, so the two stay level. */
.proc-sticky{position:sticky;top:calc(50vh - var(--dial) / 2);align-self:start;}
.proc-dial{position:relative;aspect-ratio:1;max-width:var(--dial);margin-inline:auto;
  display:grid;place-items:center;}
.proc-dial canvas{position:absolute;inset:0;width:100%;height:100%;}
.proc-dial .n{font-size:clamp(4rem,10vw,7rem);letter-spacing:var(--tr1);
  font-weight:500;line-height:1;position:relative;font-variant-numeric:tabular-nums;}
/* the dial is drawn at runtime, so without scripting there is nothing in it */
html:not(.js) .proc-dial{display:none;}
/* Trailing room so the LAST step can still reach 50vh before the sticky dial
   hits the bottom of its container and unpins. Without it, step 04 is stranded
   below centre no matter what the offsets say. */
.proc-steps{display:grid;padding-bottom:clamp(5rem,12vh,10rem);}
.proc-step{
  padding:clamp(2.2rem,7vw,4.5rem) 0;border-bottom:1px solid rgba(255,255,255,0.12);
  opacity:0.32;transition:opacity .45s ease;
}
.proc-step:last-child{border-bottom:0;}
.proc-step.is-live{opacity:1;}
html:not(.anim) .proc-step{opacity:1;}
.proc-step .lab{color:rgba(255,255,255,0.5);display:block;margin-bottom:0.7rem;}
.proc-step h3{margin-bottom:0.6rem;}
.proc-step p{color:rgba(255,255,255,0.72);max-width:44ch;}

/* ══ use cases ═══════════════════════════════════════════════
   Six cards in a checkerboard of ink and paper. The layout is the reference's;
   the treatment is this site's, so the corners take the 8px card radius rather
   than crop marks and the button is the same pill every other call uses. */
/* Five cards. The track is six columns and each card claims two, so the first
   row is three across. Two equal cards in the last row would seam at column 3,
   under the middle of the card above; the last card claims four instead, which
   drops the seam onto column 2 where the first row already has one. Add a
   sixth card and the :last-child rules come out, back to three across. */
.uc{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:1rem;}
.uc-card{grid-column:span 2;}
.uc-card:last-child{grid-column:span 4;}
/* wider measure for the wide card, or its copy sets as a narrow column
   stranded in a lot of empty card */
.uc-card:last-child p{max-width:46ch;}
.uc-card{
  border:1px solid var(--rule);border-radius:var(--r-card);
  padding:clamp(1.35rem,2.2vw,1.9rem);background:var(--white);
  transition:border-color .25s ease,transform .25s var(--ease);
}
.uc-card:hover{transform:translateY(-2px);border-color:var(--ink);}
.uc-card.is-ink{background:var(--dark);border-color:var(--dark);color:#fff;}
.uc-card.is-ink:hover{border-color:var(--dark-line-2);}
.uc-card h3{margin-bottom:0.55rem;}
.uc-card p{color:var(--ink-2);max-width:30ch;}
.uc-card.is-ink p{color:var(--dark-dim);}
/* a pale card on a white section is only a hairline from disappearing */
.sec-white .uc-card:not(.is-ink){background:var(--paper);}

/* ══ demo video ══════════════════════════════════════════════
   Click-to-load facade: nothing is requested from YouTube until the visitor
   actually asks for the video. */
.vid{border:1px solid var(--rule);border-radius:var(--r-card);overflow:hidden;
  background:var(--dark);
  /* Held short of the full content column. At the column's own width the stage
     runs to 1246px and the still dominates the section; this is the one number
     to move if it wants to be larger or smaller again. */
  max-width:960px;margin-inline:auto;}
.vid-bar{display:flex;align-items:center;justify-content:space-between;gap:1rem;
  padding:0.65rem 1rem;border-bottom:1px solid var(--dark-line);
  font-family:var(--mono);font-size:var(--t-xs);letter-spacing:0.1em;
  text-transform:uppercase;color:var(--dark-faint);}
.vid-stage{position:relative;width:100%;aspect-ratio:16/9;background:#000;}
.vid-stage iframe{position:absolute;inset:0;width:100%;height:100%;border:0;}
.facade{position:absolute;inset:0;width:100%;height:100%;padding:0;border:0;
  background:#000;cursor:pointer;display:block;}
.facade img{width:100%;height:100%;object-fit:cover;opacity:0.66;transition:opacity .2s ease;}
.facade:hover img{opacity:0.84;}
.facade .play{
  position:absolute;inset:0;margin:auto;width:78px;height:78px;border-radius:50%;
  background:rgba(11,13,16,0.7);border:1px solid rgba(233,236,241,0.35);
  display:grid;place-items:center;transition:background-color .2s ease,border-color .2s ease;
}
.facade:hover .play{background:var(--white);border-color:var(--white);}
.facade .play svg{fill:#fff;margin-left:3px;transition:fill .2s ease;}
.facade:hover .play svg{fill:var(--ink);}

/* ══ closing call ════════════════════════════════════════════ */
.cta{text-align:center;padding:clamp(3.75rem,9vw,8rem) 0;}
.cta h2{margin-bottom:1.1rem;}
/* the closing block had a heading and a button with nothing between them; the
   tagline is the only place that says why you would book */
.cta .tag{max-width:54ch;margin:0 auto 2.2rem;font-size:var(--t-lead);
  line-height:1.55;color:var(--ink-2);}
/* was 12px monospace in light grey, which is the hardest thing on the page to
   read; set in the text face at body size and darkened */
.cta .direct{display:block;margin-top:1.4rem;font-family:var(--sans);
  font-size:1rem;letter-spacing:-0.005em;color:var(--ink-2);}
.cta .direct a{border-bottom:1px solid var(--rule);}
.cta .direct a:hover{border-color:var(--ink);}

/* the Google scheduler. Lazy so it costs nothing until someone scrolls to it,
   and boxed in the site's card so its white panel does not bleed into the
   white section around it. */
.book{
  max-width:840px;margin:clamp(1.5rem,3.5vw,2.4rem) auto clamp(1rem,2vw,1.4rem);
  border:1px solid var(--rule);border-radius:var(--r-card);
  overflow:hidden;background:var(--white);
}
/* Tall enough that the month grid and a usable run of times are reachable
   without depending on the iframe's own scrollbar, which touch devices handle
   badly. The widget is taller still when availability is wide open, hence the
   escape link underneath. */
.book iframe{display:block;width:100%;height:820px;border:0;}
@media (max-width:720px){ .book iframe{height:940px;} }
/* the fixed plate must not sit over the top of the scheduler on a jump link */
#contact{scroll-margin-top:clamp(5.5rem,9vw,7rem);}

/* ══ footer ══════════════════════════════════════════════════ */
footer{
  position:relative;background:var(--dark);color:#fff;overflow:hidden;
  padding:clamp(2.5rem,6vw,4rem) 0 clamp(1.5rem,3vw,2.5rem);
}
footer::before{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:
    radial-gradient(ellipse 80% 70% at 60% 100%,rgba(94,74,58,0.55),transparent 70%),
    radial-gradient(ellipse 60% 50% at 20% 0%,rgba(46,107,230,0.12),transparent 70%);
}
footer .wrap{position:relative;z-index:1;}
.foot-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:2rem;margin-bottom:clamp(2.4rem,7vw,5.25rem);}
.foot-grid h4{font-size:var(--t-body);font-weight:500;color:rgba(255,255,255,0.85);
  margin-bottom:1.2rem;letter-spacing:-0.01em;}
.foot-list{display:grid;gap:0.55rem;list-style:none;}
.foot-list a{font-family:var(--mono);font-size:var(--t-sm);letter-spacing:0.08em;
  text-transform:uppercase;color:rgba(255,255,255,0.62);transition:color .2s ease;}
.foot-list a:hover{color:#fff;}
.foot-sign{display:flex;gap:0.5rem;flex-wrap:wrap;}
.foot-sign input{
  flex:1;min-width:0;padding:0.8rem 1rem;border-radius:6px;
  border:1px solid rgba(255,255,255,0.18);background:rgba(255,255,255,0.07);
  color:#fff;font-family:var(--sans);font-size:var(--t-body);
}
.foot-sign input::placeholder{color:rgba(255,255,255,0.4);}
.foot-bar{
  display:flex;flex-wrap:wrap;gap:0.8rem 1.5rem;justify-content:space-between;
  align-items:center;font-family:var(--mono);font-size:var(--t-xs);
  letter-spacing:0.1em;text-transform:uppercase;color:rgba(255,255,255,0.45);
  border-top:1px solid rgba(255,255,255,0.12);padding-top:1.2rem;
}
/* The address is the only actionable thing in this row, and it was inheriting
   the bar's 11px tracked-out uppercase monospace -- the least readable setting
   on the page. Set like the one in Get started: text face, body size,
   underlined, no tracking. The copyright and the wordmark beside it keep the
   small mono, which is what that kind of line is for. */
.foot-bar a[href^="mailto:"]{
  font-family:var(--sans);font-size:1rem;letter-spacing:-0.005em;
  text-transform:none;color:rgba(255,255,255,0.88);
  border-bottom:1px solid rgba(255,255,255,0.28);
}
.foot-bar a[href^="mailto:"]:hover{color:#fff;border-bottom-color:#fff;}
.foot-bar a:hover{color:#fff;}

/* Tap targets, on touch only -- a mouse is precise and does not need this, and
   applying it everywhere would open the footer up on desktop for no reason.

   A footer link is one line of 12px mono, about 17px tall, and a thumb wants
   44. The list links take the difference as padding so the box grows while the
   type stays where it is, and the list gap comes off to pay for it. The address
   in the bottom bar is underlined and padding would drag the rule away from the
   text, so it gets an invisible box centred on it instead. The address under
   Book a demo is set the same way and gets the same box, and so does the menu
   button, which paints at 36 and is better left looking that way. */
@media (pointer:coarse){
  .foot-list{gap:0;}
  .foot-list a{display:inline-block;padding:0.85rem 0;}
  .foot-bar a[href^="mailto:"],.direct a,.menu-b{position:relative;}
  .foot-bar a[href^="mailto:"]::after,.direct a::after,.menu-b::after{
    content:"";position:absolute;left:50%;top:50%;
    transform:translate(-50%,-50%);
    width:max(100%,44px);height:44px;
  }
}

/* ══ motion ══════════════════════════════════════════════════
   Same contract as the original page: the .anim class is only set when the
   visitor has not asked for reduced motion, so without it nothing hides. */
html.anim [data-rise]{
  opacity:0;transform:translateY(18px);
  transition:opacity .8s var(--ease) var(--d,0s),transform .8s var(--ease) var(--d,0s);
}
html.anim [data-rise].is-in{opacity:1;transform:none;}

/* ══ adaptive ════════════════════════════════════════════════ */
@media (max-width:1040px){
  .ops{grid-template-columns:repeat(2,minmax(0,1fr));}
  .uc{grid-template-columns:repeat(2,minmax(0,1fr));}
  .uc-card,.uc-card:last-child{grid-column:auto;}
  /* the dial stops sticking and sits above the steps */
  .proc-grid{grid-template-columns:1fr;}
  .proc-sticky{position:static;margin-bottom:1.5rem;}
  .proc-steps{padding-bottom:0;}
  .proc-dial{max-width:240px;}
  /* with the dial no longer beside them, several steps share the screen at
     once, so the inactive ones are dimmed far less to stay readable */
  .proc-step{opacity:0.55;}
}
@media (max-width:900px){
  .cols{grid-template-columns:1fr;}
  .cols-2{grid-template-columns:1fr;}
  .hero h1 .l2{padding-left:0;}
  .foot-grid{grid-template-columns:1fr;gap:2.4rem;}
}
@media (max-width:720px){
  /* on a phone the numbered dial is just a big circle between the heading and
     the steps -- the step numbers carry on their own */
  .proc-sticky{display:none;}
  /* The card is the tallest thing in this section on a phone and does not need
     to be -- the copy is short, and the height was going into padding and type
     scaled for a desktop column.

     Stacking them in one grid cell matters as much as the sizing. Each card was
     free to be its own height, and on a narrow screen the copy wraps to
     different depths, so a longer card behind the front one hung out below it
     and printed its text through the stack. Sharing a cell makes the row as
     tall as the tallest card and stretches the rest to match, so the one in
     front covers the others. It also means no fixed height to get wrong: the
     tallest card runs from 262px at 720 wide to 341px at 320, and any single
     number would either clip the small screens or pad out the large ones. */
  .sig-deck{display:grid;min-height:0;}
  /* no gutter to fan into on a narrow screen, and pseudo-elements of a grid
     container would become grid items and take cells of their own */
  .sig-deck::before,.sig-deck::after{display:none;}
  .sig-card{position:relative;grid-area:1/1;inset:auto;padding:0.85rem 0.9rem;}
  .sig-top{margin-bottom:0.8rem;}
  /* trimmed just enough that the longest pair still shares a line at 360 */
  .sig-flag,.sig-cat{padding:0.4rem 0.7rem;letter-spacing:0.1em;}
  .sig-cause{font-size:1.08rem;}
  .sig-why{font-size:0.92rem;margin-bottom:1rem;}
  .sig-chart{margin-bottom:0.85rem;}
  .sig-spark{height:46px;}
  .sig-foot{font-size:0.9rem;padding-top:0.75rem;}
  .plate{padding:0.8rem 1.1rem 0.8rem 0.85rem;}
  .plate .mark{width:40px;height:40px;}
  /* Standing sections up to the window is a large-screen idea. A phone holds
     the same content in a narrow column, so the stretch leaves more emptiness
     than content -- over half the screen on the last two, which reads as a
     page that failed to load rather than a spacious one. Here a section goes
     back to being as tall as whatever it holds.

     The top padding then has a second job: the header badge is 69px tall at
     this width and the padding was 48px, so a headline arriving at the top of
     the window sat behind it with its first line unreadable. This clears it
     with room to spare. */
  .sec{min-height:0;display:block;padding-top:5.5rem;}
  .hero{min-height:92svh;}
  .ops{grid-template-columns:1fr;}
  .ops-card{min-height:0;}
  .uc{grid-template-columns:1fr;}
  .uc-card,.uc-card:last-child{grid-column:auto;}
}
@media (max-width:460px){
  /* The button used to stretch full width here, from when the hero copy was
     left-aligned and a wide block anchored it. The copy is centred now, so a
     content-width pill reads better and matches the desktop shape. */
  .hero-cta .btn{padding:0.72rem 1.35rem;font-size:var(--t-sm);}
  .menu-t{display:none;}
}
