/* ---------------------------------------------------------------------------
   Arta Cyber Inc. — site stylesheet
   Light theme. Off-white ground, navy ink, bronze used sparingly.
   No build step: this file is served exactly as written.

   COLOUR IS DERIVED, NOT CHOSEN BY EYE.
   The theme inverted from dark to light, so every ratio from the previous
   stylesheet is void and was re-measured against the new grounds. The three
   results that actually constrain the design:

     bronze #A5845E on ground #F7F6F3  ->  3.21:1   FAILS AA as text
     bronze #A5845E on white  #FFFFFF  ->  3.46:1   FAILS AA as text
     white  #FFFFFF on bronze #A5845E  ->  3.46:1   FAILS AA as a button

   So the brand bronze is a GRAPHIC colour on this site: rules, underlines and
   the hairline marks above section headings, where WCAG 1.4.11 asks for 3.0
   and 3.21 clears it. Wherever bronze has to be READ it is darkened to
   --bronze-ink #7A5C39 (5.69:1 on ground, 6.14:1 on white, 5.25:1 on the sunk
   band). The primary button is navy, not bronze, because navy carries
   off-white at 16.11:1 and bronze cannot carry anything.

   TYPE. IBM Plex Sans does all structural work. IBM Plex Mono appears in
   exactly ONE place on the whole site — the protocol status table on
   /products — where the monospace is carrying a protocol name and a state
   value. There are no mono buttons, mono eyebrows, mono nav or mono tags,
   because that is the tell this rebuild exists to remove.
   --------------------------------------------------------------------------- */

:root {
  /* --- brand core, as graphic fills ------------------------------------ */
  --navy:        #101A2C;
  --bronze:      #A5845E;
  --steel:       #7189B0;   /* CertPilot sub-brand only */
  --green:       #45A46E;   /* Arta OT Lab sub-brand only */

  /* --- light-theme surfaces -------------------------------------------- */
  --ground:      #F7F6F3;   /* the page */
  --card:        #FFFFFF;   /* raised card */
  --sunk:        #EFEDE7;   /* alternating band, form fields */
  --hairline:    #E3E0D8;   /* decorative separators only */
  --rule:        #D5D1C6;   /* stronger decorative rule */
  --field-line:  #5C6A86;   /* form control borders: 5.03:1, a real boundary */

  /* --- ink, every value measured on --ground unless noted --------------- */
  --ink:         #101A2C;   /* 16.11:1  headings */
  --ink-body:    #2B3852;   /* 10.85:1  body copy */
  --ink-soft:    #4A5771;   /*  6.72:1  secondary copy, captions */
  --ink-mute:    #5C6A86;   /*  5.03:1  smallest supporting text */
  --bronze-ink:  #7A5C39;   /*  5.69:1  the only readable bronze */
  --steel-ink:   #3F5A87;   /*  5.91:1 on the steel pill */
  --green-ink:   #2A6E47;   /*  5.32:1 on the green pill */

  /* --- ink on the navy band -------------------------------------------- */
  --on-navy:       #F7F6F3; /* 16.11:1 */
  --on-navy-soft:  #C6CFE0; /* 11.11:1 */
  --on-navy-mute:  #AFBBD2; /*  9.01:1 */
  --on-navy-bronze:#C9A87C; /*  7.77:1 */

  /* --- pills ------------------------------------------------------------ */
  --pill-steel-bg:  #E8EDF5;
  --pill-green-bg:  #E4F2EA;
  --pill-bronze-bg: #F2EAE0;
  --pill-navy-bg:   #E9EBF0;

  --font-sans: "IBM Plex Sans", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* --- ONE spacing scale. Every gap on the site is one of these. --------
     The ~500px void between hero and first section on the old build came
     from stacking ad-hoc paddings that were never added up. Sections now
     take their vertical space from --space-section and nothing else. */
  --sp-1: 4px;   --sp-2: 8px;   --sp-3: 12px;  --sp-4: 16px;
  --sp-5: 24px;  --sp-6: 32px;  --sp-7: 48px;  --sp-8: 64px;
  --sp-9: 96px;
  --space-section: clamp(56px, 7vw, 100px);
  --space-section-tight: clamp(40px, 5vw, 68px);

  --wrap: 1160px;
  --wrap-text: 68ch;         /* the reading measure, ~62-72 characters */
  --gutter: clamp(20px, 5vw, 48px);
  --radius: 3px;
  --header-h: 68px;

  /* Photograph fallback. Every framed image sits on this, so a photo that has
     not landed yet leaves a deliberate flat tone rather than a broken icon. */
  --photo-fallback: linear-gradient(150deg, #E7E4DC 0%, #DCD8CE 55%, #CFCABE 100%);
}

/* --- reset -------------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + var(--sp-5));
  /* `clip`, not `hidden`: hidden makes this a scroll container and kills the
     sticky header. */
  overflow-x: clip;
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink-body);
  font-family: var(--font-sans);
  font-size: 17px;
  font-weight: 400;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img, svg { max-width: 100%; }
img { height: auto; display: block; }
ul, ol { margin: 0; padding: 0; }
li { list-style: none; }

/* --- type scale ---------------------------------------------------------
   Four clearly separated steps plus a caption, so the reading order is
   obvious at a glance without any label needing to shout in capitals.
     display  38 -> 60px
     h2       28 -> 40px
     h3       21px
     lead     19px
     body     17px
     caption  14px                                                        */

h1, h2, h3, h4 {
  margin: 0 0 0.5em;
  color: var(--ink);
  font-weight: 600;
  line-height: 1.14;
  letter-spacing: -0.02em;
  text-wrap: balance;
}
h1 { font-size: clamp(2.375rem, 1.55rem + 3.4vw, 3.75rem); letter-spacing: -0.031em; line-height: 1.06; }
h2 { font-size: clamp(1.75rem, 1.32rem + 1.8vw, 2.5rem);   letter-spacing: -0.026em; line-height: 1.1; }
h3 { font-size: 1.3125rem; letter-spacing: -0.014em; line-height: 1.25; }
h4 { font-size: 1.0625rem; letter-spacing: -0.008em; line-height: 1.35; }

p { margin: 0 0 1.05em; text-wrap: pretty; }
p:last-child { margin-bottom: 0; }

.lead {
  font-size: clamp(1.125rem, 1.02rem + 0.4vw, 1.25rem);
  line-height: 1.58;
  color: var(--ink-soft);
  max-width: 60ch;
}
.caption { font-size: 0.875rem; line-height: 1.5; color: var(--ink-mute); }

strong, b { font-weight: 600; color: var(--ink); }

/* Reading measure. Body copy never runs wider than this. */
.prose { max-width: var(--wrap-text); }
.prose p { margin-bottom: 1.15em; }

/* --- eyebrow ------------------------------------------------------------
   Sentence case, not capitals, not monospace. The gold rule above it does
   the signalling that letter-spacing used to be asked to do. */
.eyebrow {
  display: block;
  margin: 0 0 var(--sp-4);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--bronze-ink);
}
.eyebrow::before {
  content: "";
  display: block;
  width: 32px;
  height: 2px;
  margin-bottom: var(--sp-3);
  background: var(--bronze);   /* 3.21:1 vs ground — non-text, clears 3.0 */
}
.eyebrow--plain::before { display: none; }

/* --- links --------------------------------------------------------------
   Gold appears here as an underline only. Body links keep the ink colour so
   they stay AA; the gold is the mark, not the text. */
a { color: var(--ink); text-decoration: none; }

.prose a,
a.textlink {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: var(--bronze);
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
  transition: text-decoration-color .15s ease, color .15s ease;
}
.prose a:hover,
a.textlink:hover { color: var(--bronze-ink); text-decoration-color: var(--bronze-ink); }

/* --- focus --------------------------------------------------------------
   Navy ring at 16.11:1 against the ground, far above the 3.0 that WCAG
   1.4.11 asks of a focus indicator. Declared for :focus-visible only, so a
   mouse click does not draw it. */
:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
  border-radius: 2px;
}
.on-navy :focus-visible { outline-color: var(--on-navy); }

.skip {
  position: absolute; left: var(--sp-4); top: -100px; z-index: 100;
  padding: var(--sp-3) var(--sp-5);
  background: var(--ink); color: var(--on-navy);
  font-weight: 600; border-radius: var(--radius);
  transition: top .15s ease;
}
.skip:focus { top: var(--sp-4); }

/* --- layout primitives --------------------------------------------------- */

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* The ONLY source of vertical section space. */
.section { padding-block: var(--space-section); }
.section--tight { padding-block: var(--space-section-tight); }
.section--sunk  { background: var(--sunk); }
.section--card  { background: var(--card); }

/* Two sections with the same background sitting next to each other would
   otherwise read as one enormous gap. */
.section--sunk + .section--sunk,
.section--card + .section--card { padding-top: 0; }

.section__head { max-width: 62ch; margin-bottom: var(--sp-8); }
.section__head p:last-child { margin-bottom: 0; }
.section__head .lead { margin-top: var(--sp-4); }

/* --- buttons ------------------------------------------------------------
   Navy fill, off-white text: 16.11:1. Bronze is not used as a button fill
   anywhere, because no text colour clears AA on it. */
.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 13px 22px;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: var(--ink);
  color: var(--on-navy);
  font-family: inherit;
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: -0.005em;
  line-height: 1.2;
  cursor: pointer;
  transition: background .15s ease, transform .15s ease;
}
.btn:hover { background: #1D2C48; color: var(--on-navy); }
.btn:active { transform: translateY(1px); }

.btn--ghost {
  background: transparent;
  color: var(--ink);
  border-color: var(--rule);
}
.btn--ghost:hover { background: var(--card); border-color: var(--ink); color: var(--ink); }

.btn--on-navy { background: var(--on-navy); color: var(--ink); }
.btn--on-navy:hover { background: #FFFFFF; color: var(--ink); }

/* A button must keep its own colours wherever it is nested. The contextual link
   rules (`.prose a`, `.nav a`, `.nav a[aria-current]`) are all MORE specific
   than `.btn`, so without these the label is painted in navy body ink on the
   navy fill. Measured at exactly 1.00:1 — invisible — on three pages before
   this rule existed. Every selector here is one notch above the rule it beats. */
.prose a.btn,
.nav a.btn,
.nav a.btn[aria-current="page"],
.site-footer a.btn {
  color: var(--on-navy);
  font-weight: 600;
  text-decoration: none;
}
.prose a.btn:hover,
.nav a.btn:hover,
.site-footer a.btn:hover { color: var(--on-navy); text-decoration: none; }

.prose a.btn--ghost, .nav a.btn--ghost { color: var(--ink); }

.btn__arrow { transition: transform .15s ease; }
.btn:hover .btn__arrow { transform: translateX(3px); }

.actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4) var(--sp-6);
  margin-top: var(--sp-7);
}

/* --- pills / badges ------------------------------------------------------ */
.badge {
  display: inline-block;
  padding: 4px 11px;
  border-radius: 100px;
  background: var(--pill-navy-bg);
  color: #26324B;
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.45;
  letter-spacing: 0;
}
.badge--live  { background: var(--pill-steel-bg);  color: var(--steel-ink); }
.badge--dev   { background: var(--pill-bronze-bg); color: var(--bronze-ink); }
.badge--lab   { background: var(--pill-green-bg);  color: var(--green-ink); }

/* A plain inline note line: "Segmentation · Industrial DMZ · Vendor-neutral".
   Sentence case, sans, muted. This used to be monospace capitals. */
.notes {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  margin: 0 0 var(--sp-6);
  font-size: 0.875rem;
  color: var(--ink-mute);
}
.notes li { position: relative; }
.notes li + li::before {
  content: "";
  position: absolute; left: calc(var(--sp-4) / -2 - 1px); top: 0.72em;
  width: 3px; height: 3px; border-radius: 50%;
  background: var(--rule);
}

/* --- framed photography -------------------------------------------------
   Photographs sit in their own framed block on the ground. Nothing is
   printed on top of a photograph anywhere on this site, and no photograph
   is faded behind text: that was defect 3, and the fix is structural rather
   than a matter of picking a better opacity. */
.frame {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--photo-fallback);
  box-shadow:
    0 1px 2px rgba(16, 26, 44, 0.06),
    0 18px 40px -20px rgba(16, 26, 44, 0.30);
}
.frame::after {
  /* A hairline inside the edge so the photo meets the ground on a drawn line
     rather than on a torn one. */
  content: "";
  position: absolute; inset: 0;
  border: 1px solid rgba(16, 26, 44, 0.10);
  border-radius: inherit;
  pointer-events: none;
}
.frame img { width: 100%; height: 100%; object-fit: cover; }
/* Ratios are chosen against the ratios the photographs ACTUALLY are, so the
   centre crop stays small. The page-header photographs are 1200x800 (3:2), so a
   2:1 band trims 25% of the height and nothing sideways. The four hero frames
   are 1920x1080 (16:9); a 4:3 box trims 25% of the width and keeps the subject,
   which sits centre in all four. */
.frame--wide  { aspect-ratio: 2 / 1; }
.frame--photo { aspect-ratio: 3 / 2; }
@media (max-width: 640px) { .frame--wide { aspect-ratio: 3 / 2; } }

.figure { margin: 0; }
.figure figcaption {
  margin-top: var(--sp-3);
  font-size: 0.875rem;
  color: var(--ink-mute);
}

/* --- header -------------------------------------------------------------- */

.site-header {
  position: sticky; top: 0; z-index: 50;
  background: rgba(247, 246, 243, 0.94);
  backdrop-filter: saturate(140%) blur(8px);
  border-bottom: 1px solid var(--hairline);
}
.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-5);
  min-height: var(--header-h);
}

/* The mark is used AS SUPPLIED by the brand lane. It is never re-traced,
   re-coloured or rebuilt as inline SVG here: doing exactly that is what
   flattened the darts, collapsed the gold sphere and lost the padlock. */
/* --- brand lockup -------------------------------------------------------
   THE MARK IS AN IMAGE; THE WORDMARK IS REAL TEXT. Do not swap this back for
   lockup-light.svg at header size.

   That lockup is artwork drawn for a 620px-wide display, and every glyph in it
   scales with the box. In the header it rendered 204px wide, a scale of 0.329,
   which turned its 30px wordmark into 9.9px and its 11px tagline into 3.6px —
   a grey smear that no font-size change inside the SVG can fix, because the
   problem is the scale factor, not the type size.

   Real HTML text scales independently of the mark, stays crisp at any size, is
   selectable, and is readable by search engines and screen readers. The lockup
   files stay correct artwork for places that display them LARGE: og-image and
   print. They are simply not header-sized assets. */
.brand { display: inline-flex; align-items: center; gap: 11px; flex: 0 0 auto; }
.brand__mark { height: 40px; width: 40px; flex: 0 0 auto; }
.brand__name {
  font-size: 1.1875rem;        /* 19px rendered, vs 9.9px before */
  font-weight: 600;
  letter-spacing: 0.15em;
  line-height: 1;
  color: var(--ink);
  white-space: nowrap;
}
@media (max-width: 480px) {
  .brand__mark { height: 34px; width: 34px; }
  .brand__name { font-size: 1rem; letter-spacing: 0.11em; }
}

.nav > ul { display: flex; align-items: center; gap: var(--sp-2); }
.nav a {
  display: inline-block;
  padding: 8px 12px;
  border-radius: var(--radius);
  color: var(--ink-body);
  font-size: 0.9375rem;
  font-weight: 500;
  line-height: 1.3;
  transition: color .12s ease, background .12s ease;
}
.nav a:hover { color: var(--ink); background: rgba(16, 26, 44, 0.05); }
.nav a[aria-current="page"] { color: var(--ink); font-weight: 600; }
/* `.nav a` is more specific than `.btn`, so without these two declarations it
   wins and paints the button's label in body ink on the navy fill. Measured at
   1.48:1 before this rule existed. */
.nav .btn { margin-left: var(--sp-3); padding: 9px 18px; color: var(--on-navy); font-weight: 600; }
.nav .btn:hover { background: #1D2C48; color: var(--on-navy); }

.nav-toggle {
  display: none;
  align-items: center;
  gap: var(--sp-2);
  padding: 9px 14px;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 0.9375rem;
  font-weight: 500;
  cursor: pointer;
}
.nav-toggle__bars { display: block; width: 15px; height: 9px; position: relative; }
.nav-toggle__bars::before,
.nav-toggle__bars::after {
  content: ""; position: absolute; left: 0; width: 100%; height: 1.5px;
  background: currentColor;
}
.nav-toggle__bars::before { top: 0; }
.nav-toggle__bars::after  { bottom: 0; }

@media (max-width: 900px) {
  .nav-toggle { display: inline-flex; }
  .nav {
    position: absolute; left: 0; right: 0; top: 100%;
    padding: var(--sp-3) var(--gutter) var(--sp-5);
    background: var(--ground);
    border-bottom: 1px solid var(--hairline);
    box-shadow: 0 16px 30px -22px rgba(16, 26, 44, 0.4);
  }
  .nav > ul { flex-direction: column; align-items: stretch; gap: 0; }
  .nav a { display: block; padding: 12px 0; font-size: 1.0625rem; border-bottom: 1px solid var(--hairline); }
  .nav a:hover { background: transparent; }
  .nav .btn {
    display: inline-flex;
    margin: var(--sp-4) 0 0; justify-content: center; border-bottom: 0;
    font-size: 0.9375rem;
  }
  .nav > ul > li:last-child { display: flex; }
  .nav .btn:hover { background: #1D2C48; }
  /* Closed by default once JS is running. Under .no-js the menu stays open so
     a blocked script can never hide the navigation. */
  html:not(.no-js) .nav { display: none; }
  html:not(.no-js) .nav.is-open { display: flex; }
}

/* --- hero ---------------------------------------------------------------
   Type on the ground, photograph in its own frame beside it. The headline is
   never set over the photograph, so it cannot be fought by a bright frame of
   the crossfade, and the photographs are not darkened into grey to make room
   for it — defects 7 and 3. */
.hero { padding-block: clamp(40px, 5vw, 72px) var(--space-section); }
.hero__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-8);
  align-items: center;
}
@media (min-width: 900px) {
  .hero__grid { grid-template-columns: minmax(0, 1.02fr) minmax(0, 1fr); gap: clamp(40px, 5vw, 72px); }
}
.hero__kicker {
  margin: 0 0 var(--sp-5);
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--bronze-ink);
  letter-spacing: 0.005em;
}
.hero h1 { margin-bottom: var(--sp-5); max-width: 16ch; }
.hero .lead { max-width: 46ch; }

/* --- hero crossfade -----------------------------------------------------
   Four frames, one visible at a time, 6s apart with a slow fade. No zoom, no
   parallax, no movement of any kind. The first frame carries the real alt
   text and is painted by the HTML, so the block is correct with JS blocked
   and correct under reduced motion, where the timer never starts. */
.crossfade { position: relative; aspect-ratio: 4 / 3; }
.crossfade img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 1600ms ease-in-out;
}
/* The outgoing frame stays fully opaque UNDERNEATH while the incoming one
   fades in on top, and is only dropped once the fade has finished. Fading both
   at once would dip to 50/50 halfway through and let the ground colour show
   between them, which reads as a flicker on a light page. */
.crossfade img.was-on { opacity: 1; z-index: 0; transition: none; }
.crossfade img.is-on  { opacity: 1; z-index: 1; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .crossfade img { transition: none; }
  /* Belt and braces: even if the timer were somehow started, nothing but the
     first frame can ever be painted. */
  .crossfade img + img { display: none; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* --- cards --------------------------------------------------------------
   Equal height by grid stretch; the call-to-action is pushed to the bottom
   with margin-top:auto so a card with four lines of copy and a card with two
   still line their links up. That was defect 6. */
.grid {
  display: grid;
  gap: var(--sp-5);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  align-items: stretch;
}
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 270px), 1fr)); }

.card {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: var(--sp-6);
  background: var(--card);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
}
.card > h3 { margin-bottom: var(--sp-3); }
.card__body { margin-bottom: var(--sp-5); }
.card__body p:last-child { margin-bottom: 0; }
.card__foot { margin-top: auto; padding-top: var(--sp-4); }
.card__badge { margin-bottom: var(--sp-4); }

.card--accent { border-top: 3px solid var(--bronze); }

/* A card link that is the whole call to action. */
.golink {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  color: var(--ink);
  font-size: 0.9375rem;
  font-weight: 600;
  text-decoration: underline;
  text-decoration-color: var(--bronze);
  text-decoration-thickness: 2px;
  text-underline-offset: 4px;
}
.golink:hover { color: var(--bronze-ink); text-decoration-color: var(--bronze-ink); }
.golink svg { transition: transform .15s ease; }
.golink:hover svg { transform: translateX(3px); }

/* --- the three jobs -----------------------------------------------------
   Design / Defend / Operate are told apart by name, weight, colour and a
   gold rule. There is no "01 /", no step index and no count in a label slot
   anywhere on this site. */
.job { border-top: 3px solid var(--bronze); padding-top: var(--sp-5); }
.job h3 { font-size: 1.5rem; margin-bottom: var(--sp-3); }

/* --- service list -------------------------------------------------------- */
.svc-group + .svc-group { margin-top: var(--space-section); }
.svc-group__head {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-6);
  padding-bottom: var(--sp-6);
  margin-bottom: var(--sp-7);
  border-bottom: 1px solid var(--rule);
}
@media (min-width: 880px) {
  .svc-group__head { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); gap: clamp(32px, 4vw, 64px); align-items: center; }
}
.svc-group__head h2 { margin-bottom: var(--sp-4); }
.svc-group__head .lead { margin-bottom: var(--sp-5); }
.svc-group__head .notes { margin-bottom: 0; }
.svc-group__head .frame { aspect-ratio: 3 / 2; }

.svc-list { display: grid; gap: var(--sp-6); }
@media (min-width: 760px) { .svc-list { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-7) var(--sp-8); } }
.svc { display: flex; flex-direction: column; }
.svc h3 { margin-bottom: var(--sp-3); }
.svc p { color: var(--ink-body); margin-bottom: 0; }

/* Compact grouped index used on the home page. */
.svc-index { display: grid; gap: var(--sp-7); }
@media (min-width: 760px) { .svc-index { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-8); } }
.svc-index h2,
.svc-index h3 { font-size: 1.125rem; letter-spacing: -0.014em; margin-bottom: var(--sp-4); }
.svc-index ul { display: grid; gap: var(--sp-3); }
.svc-index a {
  color: var(--ink-body);
  font-size: 0.9688rem;
  text-decoration: underline;
  text-decoration-color: transparent;
  text-decoration-thickness: 2px;
  text-underline-offset: 4px;
  transition: color .12s ease, text-decoration-color .12s ease;
}
.svc-index a:hover { color: var(--ink); text-decoration-color: var(--bronze); }

/* --- anchor jump row (no numbers, no capitals, no monospace) ------------- */
.jump {
  display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-5);
  padding-top: var(--sp-6);
  margin-top: var(--sp-6);
  border-top: 1px solid var(--hairline);
}
.jump a {
  color: var(--ink-body); font-size: 0.9375rem; font-weight: 500;
  text-decoration: underline; text-decoration-color: var(--rule);
  text-decoration-thickness: 2px; text-underline-offset: 4px;
}
.jump a:hover { color: var(--ink); text-decoration-color: var(--bronze); }

/* --- the sequence on /solutions -----------------------------------------
   Order is carried by the stack and by a connecting rule, not by numerals. */
.steps { display: grid; gap: 0; }
.step {
  position: relative;
  padding: 0 0 var(--sp-7) var(--sp-7);
  border-left: 1px solid var(--rule);
}
.step:last-child { padding-bottom: 0; border-left-color: transparent; }
.step::before {
  content: "";
  position: absolute; left: -5px; top: 7px;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--bronze);
  box-shadow: 0 0 0 4px var(--ground);
}
.section--sunk .step::before { box-shadow: 0 0 0 4px var(--sunk); }
.step h3 { margin-bottom: var(--sp-3); }
.step p { max-width: var(--wrap-text); margin-bottom: 0; }

/* --- pull quote ---------------------------------------------------------- */
.pullquote {
  margin: 0;
  padding-left: var(--sp-6);
  border-left: 3px solid var(--bronze);
  font-size: clamp(1.25rem, 1.05rem + 0.85vw, 1.625rem);
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: -0.017em;
  color: var(--ink);
  text-wrap: balance;
}

/* --- the one dark band --------------------------------------------------
   Permitted for the footer, and for a single feature section, which is the
   band on the home page. Nowhere else. */
.on-navy {
  background: var(--navy);
  color: var(--on-navy-soft);
}
.on-navy h1, .on-navy h2, .on-navy h3, .on-navy h4, .on-navy strong { color: var(--on-navy); }
.on-navy .lead { color: var(--on-navy-mute); }
.on-navy .eyebrow { color: var(--on-navy-bronze); }
.on-navy .eyebrow::before { background: var(--on-navy-bronze); }
.on-navy .pullquote { color: var(--on-navy); border-left-color: var(--on-navy-bronze); }
.on-navy .caption { color: var(--on-navy-mute); }
.on-navy a.textlink, .on-navy .prose a {
  color: var(--on-navy);
  text-decoration-color: var(--on-navy-bronze);
}
.on-navy a.textlink:hover { color: var(--on-navy-bronze); }

.feature__grid { display: grid; gap: var(--sp-8); grid-template-columns: minmax(0, 1fr); }
@media (min-width: 880px) {
  .feature__grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: clamp(48px, 6vw, 88px); align-items: start; }
}

/* --- product blocks ------------------------------------------------------ */
.product + .product { margin-top: var(--space-section); padding-top: var(--space-section); border-top: 1px solid var(--rule); }
.product__head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-4); margin-bottom: var(--sp-5); }
.product__head h2 { margin: 0; }
/* minmax(0,1fr) rather than the default implicit `auto` column: an auto column
   is sized by its content's MIN-CONTENT width, so the protocol table below was
   able to push this column — and the whole document — 18px past a 320px
   viewport. Measured, then fixed here rather than by shrinking the table. */
.product__grid { display: grid; gap: var(--sp-8); grid-template-columns: minmax(0, 1fr); }
@media (min-width: 880px) { .product__grid { grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: clamp(40px, 5vw, 72px); align-items: start; } }

.featurelist { display: grid; gap: var(--sp-4); margin-top: var(--sp-6); }
.featurelist li { position: relative; padding-left: var(--sp-5); color: var(--ink-body); }
.featurelist li::before {
  content: ""; position: absolute; left: 0; top: 0.72em;
  width: 6px; height: 6px; border-radius: 50%; background: var(--bronze);
}

/* --- protocol table -----------------------------------------------------
   THE ONLY MONOSPACE ON THE SITE. Both columns that use it are carrying a
   machine value: the protocol's own name, and the state it is in. */
/* A table is the one thing allowed to be wider than the narrow viewport, and
   only inside its own scroller. The page body must never scroll sideways. */
.tablewrap { overflow-x: auto; margin-top: var(--sp-5); }
.protocols { width: 100%; min-width: 340px; border-collapse: collapse; }
.protocols caption {
  text-align: left; padding-bottom: var(--sp-4);
  font-size: 0.875rem; color: var(--ink-mute);
}
.protocols th, .protocols td {
  padding: var(--sp-4) var(--sp-4) var(--sp-4) 0;
  border-bottom: 1px solid var(--hairline);
  text-align: left;
  vertical-align: top;
}
.protocols thead th {
  padding-top: 0;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--ink-mute);
  letter-spacing: 0;
}
.protocols tbody th {
  width: 1%; white-space: nowrap;
  font-family: var(--font-mono);
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--ink);
}
.protocols .state {
  font-family: var(--font-mono);
  font-size: 0.875rem;
  font-weight: 500;
}
.protocols .state--ok  { color: var(--green-ink); }
.protocols .state--dev { color: var(--bronze-ink); }
.protocols td p { margin: 0; font-size: 0.9375rem; color: var(--ink-soft); }
.protocols tr:last-child th, .protocols tr:last-child td { border-bottom: 0; }

/* --- definition rows ----------------------------------------------------- */
.facts { display: grid; gap: 0; }
.fact {
  display: grid; gap: var(--sp-1) var(--sp-6);
  padding: var(--sp-5) 0;
  border-bottom: 1px solid var(--hairline);
}
@media (min-width: 640px) { .fact { grid-template-columns: 190px minmax(0, 1fr); } }
.fact:first-child { padding-top: 0; }
.fact:last-child { border-bottom: 0; }
.fact dt { color: var(--ink-mute); font-size: 0.9375rem; }
.fact dd { margin: 0; color: var(--ink-body); }

/* --- form ---------------------------------------------------------------- */
.form { display: grid; gap: var(--sp-5); }
.field { display: grid; gap: var(--sp-2); }
.field label { font-size: 0.9375rem; font-weight: 600; color: var(--ink); }
.field input, .field textarea {
  width: 100%;
  padding: 12px 14px;
  border: 1px solid var(--field-line);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--ink);
  font: inherit;
  font-size: 1rem;
  line-height: 1.5;
}
.field textarea { min-height: 168px; resize: vertical; }
.field input::placeholder, .field textarea::placeholder { color: var(--ink-mute); opacity: 1; }
.field input:focus-visible, .field textarea:focus-visible { outline-offset: 1px; border-color: var(--ink); }

/* Honeypot. Removed from the accessibility tree and from view without using
   display:none, which some bots check for. */
.hp {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

.form__note { min-height: 1.5em; font-size: 0.9375rem; color: var(--ink-soft); }
.form__note.is-ok  { color: var(--green-ink); font-weight: 600; }
.form__note.is-err { color: #A33B2E; font-weight: 600; }

/* --- footer -------------------------------------------------------------- */
.site-footer { background: var(--navy); color: var(--on-navy-soft); }
/* The footer is a navy band, so the light-ground ink tokens are wrong inside
   it. --ink-mute is 5.03:1 on the ground and only 3.20:1 here. */
.site-footer .caption { color: var(--on-navy-mute); }
.site-footer .wrap { padding-block: var(--space-section-tight) var(--sp-7); }
.footer__grid { display: grid; gap: var(--sp-8); }
@media (min-width: 760px) { .footer__grid { grid-template-columns: minmax(0, 1.5fr) repeat(3, minmax(0, 1fr)); gap: var(--sp-7); } }
/* Same treatment in the footer: at 54px tall the lockup rendered 239px wide,
   putting its tagline at 4.2px. Mark plus real text instead. The strapline is
   not repeated here as a label because the sentence below already says it.

   THE MARK SITS ON A LIGHT CHIP HERE, and that is not decoration. mark-*.png is
   the PRIMARY mark: navy darts, drawn for a light ground. Measured directly
   against the footer navy, 54.6% of its opaque pixels come out below 1.5:1 and
   the median is 1.44:1 — the darts vanish and only the gold sphere survives,
   which is the same failure as the original rejected header in reverse. There
   is no reversed bare-mark PNG in assets/brand/ and that directory is not ours
   to add one to, so the chip restores the light ground the artwork expects.
   If a reversed mark PNG is ever produced, use it and drop the chip. */
.footer__brand .brand { margin-bottom: var(--sp-5); }
.footer__brand .brand__mark {
  height: 54px; width: 54px;          /* 40px of mark inside 7px of padding */
  padding: 7px;
  background: var(--ground);
  border-radius: 10px;
}
.footer__brand .brand__name { color: var(--on-navy); font-size: 1.25rem; }
.footer__brand p { color: var(--on-navy-mute); font-size: 0.9375rem; max-width: 34ch; }
.site-footer h2 {
  font-size: 0.9375rem; font-weight: 600; color: var(--on-navy);
  margin-bottom: var(--sp-4); letter-spacing: 0;
}
.site-footer nav ul { display: grid; gap: var(--sp-3); }
.site-footer nav a, .site-footer address a {
  color: var(--on-navy-mute); font-size: 0.9375rem; font-style: normal;
  text-decoration: underline; text-decoration-color: transparent;
  text-decoration-thickness: 2px; text-underline-offset: 4px;
  transition: color .12s ease, text-decoration-color .12s ease;
}
.site-footer nav a:hover, .site-footer address a:hover {
  color: var(--on-navy); text-decoration-color: var(--on-navy-bronze);
}
.site-footer address { font-style: normal; display: grid; gap: var(--sp-3); }
.footer__legal {
  display: flex; flex-wrap: wrap; gap: var(--sp-3) var(--sp-6);
  margin-top: var(--space-section-tight);
  padding-top: var(--sp-5);
  border-top: 1px solid rgba(247, 246, 243, 0.14);
  font-size: 0.875rem;
  color: var(--on-navy-mute);
}

/* --- breadcrumb / page nav ----------------------------------------------- */
.crumbs {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2);
  padding-top: var(--sp-6);
  font-size: 0.875rem; color: var(--ink-mute);
}
.crumbs a { color: var(--ink-mute); text-decoration: underline; text-decoration-color: var(--rule); text-underline-offset: 3px; }
.crumbs a:hover { color: var(--ink); text-decoration-color: var(--bronze); }
.crumbs [aria-current="page"] { color: var(--ink-body); }

/* No margin-top here. The wrapper this sits in already supplies the section
   padding, and carrying both is exactly how a gap silently becomes 300px:
   two section paddings plus a margin plus this element's own padding, none of
   which was ever added up. Vertical space comes from the wrapper only. */
.pagenav {
  display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--sp-4);
  padding-top: var(--sp-6);
  border-top: 1px solid var(--hairline);
}
.pagenav a { color: var(--ink-body); font-size: 0.9375rem; font-weight: 500; }
.pagenav a:hover { color: var(--bronze-ink); }

/* --- 404 ------------------------------------------------------------------ */
.notfound { padding-block: clamp(72px, 12vw, 148px); }
.notfound h1 { max-width: 20ch; }

/* --- reveal on scroll ---------------------------------------------------
   The class is only ever added by JS, so a blocked script or a dead
   IntersectionObserver can never leave content invisible. */
.js-reveal { opacity: 0; transform: translateY(14px); }
.js-reveal.is-in { opacity: 1; transform: none; transition: opacity .5s ease, transform .5s ease; }
@media (prefers-reduced-motion: reduce) {
  .js-reveal, .js-reveal.is-in { opacity: 1; transform: none; transition: none; }
}

@media print {
  .site-header, .nav-toggle, .jump, .pagenav { display: none; }
  body { background: #fff; color: #000; }
}
