/* ==========================================================================
   SENTRIX SMART SYSTEMS — CORE STYLESHEET
   Depends on tokens.css. Organised as:
     01 Reset & base      02 Typography      03 Layout
     04 Navigation        05 Buttons         06 Cards
     07 Sections          08 Diagram         09 Forms
     10 Footer            11 Reveal system   12 Utilities
   ========================================================================== */

/* ==========================================================================
   01 — RESET & BASE
   ========================================================================== */
*,
*::before,
*::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Anchor targets clear the sticky nav */
  scroll-padding-top: calc(var(--nav-h) + 1.5rem);
}

body {
  margin: 0;
  background: var(--white);
  color: var(--ink-900);
  font-family: var(--font-body);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  letter-spacing: var(--tr-body);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;   /* guards against any sub-pixel horizontal scroll */
}

img,
picture,
video,
svg { display: block; max-width: 100%; }

/* The display:block above is an author rule and would otherwise beat the
   user-agent's [hidden] rule, leaving hidden images visible. Restore it.
   This matters for the logo and media slots, which stay hidden until the
   real asset is confirmed loaded. */
[hidden] { display: none !important; }

img,
video { height: auto; }

button,
input,
select,
textarea { font: inherit; color: inherit; }

h1, h2, h3, h4, h5, h6, p, figure, blockquote { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }

a { color: inherit; text-decoration: none; }

/* ---------- Focus: always visible, never removed --------------------- */
:focus { outline: none; }

:focus-visible {
  outline: 2px solid var(--accent-solid);
  outline-offset: 3px;
  border-radius: var(--r-xs);
}

/* A blue focus ring would disappear on a blue surface — use white. */
.surface-dark :focus-visible,
.hero :focus-visible,
.footer :focus-visible,
.mobile-menu :focus-visible { outline-color: var(--white); }

/* Skip link — first tab stop on every page */
.skip-link {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-50%, -120%);
  z-index: 200;
  padding: 0.75rem 1.5rem;
  background: var(--surface-deep);
  color: var(--white);
  border-radius: 0 0 var(--r-sm) var(--r-sm);
  font-size: var(--t-small);
  font-weight: 600;
  transition: transform var(--d-base) var(--ease-out);
}

.skip-link:focus-visible { transform: translate(-50%, 0); }

::selection {
  background: var(--blue);
  color: var(--white);
}

/* ==========================================================================
   02 — TYPOGRAPHY
   ========================================================================== */
.t-hero,
.t-display,
.t-h1,
.t-h2,
.t-h3 {
  font-family: var(--font-display);
  font-weight: 800;
  text-wrap: balance;        /* avoids orphan words on headings */
  margin: 0;
}

.t-hero {
  font-size: var(--t-hero);
  line-height: var(--lh-tight);
  letter-spacing: var(--tr-hero);
}

.t-display {
  font-size: var(--t-display);
  line-height: var(--lh-snug);
  letter-spacing: var(--tr-display);
}

.t-h1 {
  font-size: var(--t-h1);
  line-height: var(--lh-snug);
  letter-spacing: var(--tr-display);
}

.t-h2 {
  font-size: var(--t-h2);
  line-height: var(--lh-heading);
  letter-spacing: var(--tr-heading);
}

.t-h3 {
  font-size: var(--t-h3);
  font-weight: 700;
  line-height: var(--lh-heading);
  letter-spacing: var(--tr-heading);
}

.t-h4 {
  font-family: var(--font-display);
  font-size: var(--t-h4);
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: var(--tr-heading);
  margin: 0;
}

.t-lead {
  font-size: var(--t-lead);
  line-height: 1.55;
  font-weight: 400;
  color: var(--fg-muted);
  text-wrap: pretty;
}

.t-body { font-size: var(--t-body); line-height: var(--lh-body); }

.t-muted { color: var(--fg-muted); }

.t-small {
  font-size: var(--t-small);
  line-height: 1.6;
  color: var(--fg-muted);
}

/* ---------- Eyebrow: the mono technical voice ------------------------- */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--font-mono);
  font-size: var(--t-nano);
  font-weight: 500;
  letter-spacing: var(--tr-mono);
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 var(--s-5);
}

.eyebrow::before {
  content: "";
  width: 22px;
  height: 1px;
  background: currentColor;
  opacity: 0.55;
  flex: none;
}

.eyebrow--plain::before { display: none; }

/* Index numerals (01, 02 …) — mono, quiet, structural */
.idx {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.1em;
  color: var(--fg-subtle);
  font-variant-numeric: tabular-nums;
}

.accent-text { color: var(--accent); }

/* Inline text link with an underline that grows from the left */
.link-line {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  color: var(--accent);
  font-weight: 600;
  font-size: var(--t-small);
  position: relative;
  padding-bottom: 2px;
}

.link-line::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--d-base) var(--ease-out);
}

.link-line:hover::after,
.link-line:focus-visible::after { transform: scaleX(1); }

.link-line svg {
  transition: transform var(--d-base) var(--ease-out);
  flex: none;
}

.link-line:hover svg { transform: translateX(3px); }

/* ==========================================================================
   03 — LAYOUT
   ========================================================================== */
.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.wrap--narrow { max-width: var(--maxw-narrow); }
.wrap--mid { max-width: var(--maxw-mid); }

.section {
  padding-block: var(--section-y);
  position: relative;
}

.section--tight { padding-block: var(--section-y-tight); }
.section--flush-top { padding-top: 0; }
.section--flush-bottom { padding-bottom: 0; }

/* Section header block: eyebrow + heading + lead */
.section-head { max-width: 62ch; margin-bottom: clamp(2.5rem, 5vw, 4.5rem); }
.section-head--center { margin-inline: auto; text-align: center; }
.section-head--wide { max-width: 80ch; }
.section-head .t-lead { margin-top: var(--s-5); }

/* Generic responsive grid */
.grid { display: grid; gap: clamp(1rem, 1.8vw, 1.75rem); }
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }

/* Split: content beside media */
.split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(2rem, 5vw, 5rem);
  align-items: center;
}

.split--reverse .split__media { order: -1; }
.split--wide-media { grid-template-columns: 0.85fr 1.15fr; }

@media (max-width: 900px) {
  .grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); }
  .split,
  .split--wide-media { grid-template-columns: 1fr; }
  .split--reverse .split__media { order: 0; }
}

@media (max-width: 600px) {
  .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; }
}

/* Hairline divider */
.rule {
  border: 0;
  height: 1px;
  background: var(--rule);
  margin: 0;
}

/* ==========================================================================
   04 — NAVIGATION
   ========================================================================== */
.nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  transition: height var(--d-base) var(--ease-out),
              background-color var(--d-base) var(--ease-out),
              backdrop-filter var(--d-base) var(--ease-out),
              border-color var(--d-base) var(--ease-out);
  border-bottom: 1px solid transparent;
}

/* Over a dark hero the nav is transparent with white text */
.nav--over-dark { color: var(--white); }
.nav--over-light { color: var(--ink-900); }

/* Once scrolled: shorter, dark translucent, 16px blur, hairline bottom */
.nav.is-scrolled {
  height: var(--nav-h-scrolled);
  background: rgba(var(--scrim-rgb), 0.82);
  backdrop-filter: saturate(140%) blur(16px);
  -webkit-backdrop-filter: saturate(140%) blur(16px);
  border-bottom-color: rgba(255, 255, 255, 0.10);
  color: #f4f6f8;
}

/* Light-hero pages get a light frosted bar instead */
.nav--over-light.is-scrolled {
  background: rgba(242, 244, 246, 0.82);
  border-bottom-color: rgba(7, 17, 31, 0.10);
  color: var(--ink-900);
}

.nav__inner {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-6);
}

.nav__links {
  display: flex;
  align-items: center;
  gap: clamp(1.25rem, 2.6vw, 2.5rem);
}

.nav__link {
  position: relative;
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: -0.01em;
  opacity: 0.82;
  transition: opacity var(--d-fast) var(--ease-out);
  padding-block: 0.4rem;
}

.nav__link:hover { opacity: 1; }

.nav__link::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 1.5px;
  background: var(--blue);
  border-radius: 2px;
  transform: scaleX(0);
  transform-origin: center;
  transition: transform var(--d-base) var(--ease-out);
}

.nav__link:hover::after { transform: scaleX(0.55); }

/* Current page marker */
.nav__link[aria-current="page"] { opacity: 1; font-weight: 600; }
.nav__link[aria-current="page"]::after { transform: scaleX(1); }

.nav__right { display: flex; align-items: center; gap: var(--s-4); }

/* ---------- Logo slot ------------------------------------------------- */
.logo {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  flex: none;
}

.logo__img {
  height: 30px;
  width: auto;
  /* Preserves the real logo's proportions — never stretched */
  object-fit: contain;
  transition: height var(--d-base) var(--ease-out);
}

.nav.is-scrolled .logo__img { height: 26px; }

/* The supplied logo is navy-and-blue artwork on transparency. It reads as
   drawn on a light navigation, and would sink into the dark nav and the
   footer, so it is rendered solid white there rather than shipping a second
   asset: brightness(0) flattens every colour to black, invert(1) lifts it to
   white. Alpha is untouched, so the mark keeps its exact silhouette and
   nothing is scaled, cropped or distorted. */
.nav--over-dark .logo__img,
.footer .logo__img { filter: brightness(0) invert(1); }

/* The mobile panel is a dark surface and the nav sits above it, so a
   light-nav page needs the white treatment while that panel is open. */
.is-menu-open .nav--over-light .logo__img { filter: brightness(0) invert(1); }

/* Text fallback shown until the real logo file is dropped in.
   See assets/brand/README.md for the swap instructions. */
.logo__wordmark {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.16rem;
  letter-spacing: -0.03em;
  line-height: 1;
  white-space: nowrap;
}

.logo__wordmark span { color: var(--blue); }

/* On blue surfaces the accent dot inverts to pale blue so it stays visible. */
.nav--over-dark .logo__wordmark span,
.footer .logo__wordmark span,
.mobile-menu .logo__wordmark span { color: var(--blue-bright); }

.logo__sub {
  font-family: var(--font-mono);
  font-size: 0.5rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  opacity: 0.62;
  display: block;
  margin-top: 4px;
}

/* ---------- Mobile menu ----------------------------------------------- */
.nav__toggle {
  display: none;
  width: 42px;
  height: 42px;
  align-items: center;
  justify-content: center;
  background: none;
  border: 0;
  cursor: pointer;
  color: inherit;
  border-radius: var(--r-xs);
}

.nav__toggle-bars {
  position: relative;
  width: 19px;
  height: 12px;
}

.nav__toggle-bars span {
  position: absolute;
  left: 0;
  width: 100%;
  height: 1.5px;
  background: currentColor;
  border-radius: 2px;
  transition: transform var(--d-base) var(--ease-out),
              opacity var(--d-fast) var(--ease-out);
}

.nav__toggle-bars span:nth-child(1) { top: 0; }
.nav__toggle-bars span:nth-child(2) { bottom: 0; }

.nav__toggle[aria-expanded="true"] .nav__toggle-bars span:nth-child(1) {
  transform: translateY(5px) rotate(45deg);
}

.nav__toggle[aria-expanded="true"] .nav__toggle-bars span:nth-child(2) {
  transform: translateY(-5px) rotate(-45deg);
}

.mobile-menu {
  position: fixed;
  inset: 0;
  z-index: 99;

  /* The panel is a dark surface, so it must carry the dark token set —
     otherwise muted/subtle text inherits the light values and drops below
     4.5:1 against the black. */
  --fg-muted: var(--ink-300);
  --fg-subtle: var(--ink-350-on-dark);
  --rule: rgba(255, 255, 255, 0.10);
  --accent: var(--blue-bright);

  background: var(--surface-deep);
  color: #f4f6f8;
  padding: calc(var(--nav-h) + 3rem) var(--gutter) var(--s-8);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-8px);
  transition: opacity var(--d-base) var(--ease-out),
              transform var(--d-base) var(--ease-out),
              visibility var(--d-base);
}

.mobile-menu.is-open {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.mobile-menu__links { display: flex; flex-direction: column; }

.mobile-menu__link {
  font-family: var(--font-display);
  font-size: clamp(1.9rem, 8vw, 2.6rem);
  font-weight: 800;
  letter-spacing: -0.035em;
  padding-block: 0.62rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.09);
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  /* Staggered entrance driven by --i set inline */
  opacity: 0;
  transform: translateY(14px);
  transition: opacity var(--d-slow) var(--ease-out),
              transform var(--d-slow) var(--ease-out);
  transition-delay: calc(var(--i, 0) * 55ms);
}

.mobile-menu.is-open .mobile-menu__link {
  opacity: 1;
  transform: translateY(0);
}

/* Explicit colour rather than an opacity knock-down, which would have put
   these numerals at roughly 2:1 against the panel. */
.mobile-menu__link .idx {
  font-size: 0.7rem;
  color: rgba(255, 255, 255, 0.62);   /* 7.4:1 on --ink-900 */
}

.mobile-menu__foot {
  display: flex;
  flex-direction: column;
  gap: var(--s-5);
  opacity: 0;
  transition: opacity var(--d-slow) var(--ease-out) 260ms;
}

.mobile-menu.is-open .mobile-menu__foot { opacity: 1; }

@media (max-width: 860px) {
  .nav__links,
  .nav__right .btn { display: none; }
  .nav__toggle { display: inline-flex; }
}

/* Locks background scroll while the menu is open */
body.is-menu-open { overflow: hidden; }

/* ==========================================================================
   05 — BUTTONS
   ========================================================================== */
/* Lightweight and sentence case. Uppercase tracking reads as a brochure
   headline; a filled button should look like a control you press, and
   should only appear where the action genuinely earns the emphasis.
   Everything else on the site is a text link — see .link-cta below. */
.btn {
  --btn-bg: var(--blue);
  --btn-fg: var(--white);
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.8rem 1.5rem;
  min-height: 46px;          /* comfortable touch target */
  background: var(--btn-bg);
  color: var(--btn-fg);
  border: 1px solid transparent;
  border-radius: var(--r-xs);
  font-size: var(--t-body);
  font-weight: 500;
  letter-spacing: -0.005em;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color var(--d-base) var(--ease-out),
              border-color var(--d-base) var(--ease-out),
              opacity var(--d-base) var(--ease-out),
              color var(--d-base) var(--ease-out);
}

/* The whole button doesn't move; only the arrow does. */
.btn:hover { opacity: 0.88; }

.btn:active { opacity: 1; }

.btn svg { flex: none; transition: transform var(--d-base) var(--ease-out); }
.btn:hover svg { transform: translateX(5px); }

.btn--primary { --btn-bg: var(--blue); --btn-fg: var(--white); }
.btn--primary:hover { --btn-bg: var(--blue-ink); }

/* Ghost: hairline outline, fills on hover */
.btn--ghost {
  --btn-bg: transparent;
  --btn-fg: currentColor;
  border-color: var(--rule-strong);
  box-shadow: none;
}

.btn--ghost:hover {
  border-color: currentColor;
  background: color-mix(in srgb, currentColor 8%, transparent);
  box-shadow: none;
}

/* Solid neutral — inverts against its surface */
.btn--invert {
  --btn-bg: var(--surface-deep);
  --btn-fg: var(--white);
  box-shadow: none;
}

.btn--invert:hover {
  --btn-bg: var(--surface-mid);
  box-shadow: none;
}

.surface-dark .btn--invert {
  --btn-bg: var(--white);
  --btn-fg: var(--surface-low);
}

.surface-dark .btn--invert:hover { --btn-bg: var(--ink-100); }

.btn--lg { padding: 0.95rem 1.9rem; min-height: 52px; font-size: var(--t-lead); }

/* ---------- Pill CTA with a disc-set arrow -----------------------------
   For the case where one control carries a whole section and there is no
   second button to balance against. The disc is drawn from currentColor at
   low alpha rather than a new colour, so it inherits whatever the button
   already is on that surface and nothing new enters the palette. The right
   padding tightens because the disc supplies its own optical margin. */
.btn--pill {
  border-radius: var(--r-pill);
  gap: 0.75rem;
  padding-right: 0.5rem;
}

.btn__dot {
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: color-mix(in srgb, currentColor 12%, transparent);
  transition: transform var(--d-base) var(--ease-out),
              background var(--d-base) var(--ease-out);
}

.btn--pill:hover .btn__dot {
  transform: translateX(calc(3px * var(--motion)));
  background: color-mix(in srgb, currentColor 20%, transparent);
}
.btn--sm { padding: 0.55rem 1.1rem; min-height: 36px; font-size: var(--t-small); }
.btn--block { width: 100%; }

/* Nav quote button stays compact */
.nav .btn { padding: 0.5rem 1.1rem; min-height: 36px; font-size: var(--t-small); }

/* ---------- Text-link CTA ----------------------------------------------
   The default call to action across the site. Carries no box at all: the
   colour and the arrow do the work, so a page can offer several onward
   routes without turning into a wall of buttons. */
.link-cta {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--accent);
  font-size: var(--t-body);
  font-weight: 500;
  transition: opacity var(--d-base) var(--ease-out);
}

.link-cta svg {
  flex: none;
  transition: transform var(--d-base) var(--ease-out);
}

.link-cta:hover { opacity: 0.75; }
.link-cta:hover svg { transform: translateX(4px); }

.surface-dark .link-cta,
.hero .link-cta { color: var(--blue-bright); }

/* ---------- Buttons on blue surfaces ----------------------------------
   A blue-filled button is invisible against a blue section. Everywhere the
   background is blue — the nav (which floats over a blue hero), the hero
   itself, dark sections, the mobile menu — the primary button inverts to a
   white fill with deep-blue text (11.07:1). */
.nav .btn--primary,
.hero .btn--primary,
.surface-dark .btn--primary,
.footer .btn--primary,
.mobile-menu .btn--primary {
  --btn-bg: var(--white);
  --btn-fg: var(--surface-deep);
  box-shadow: none;
}

.nav .btn--primary:hover,
.hero .btn--primary:hover,
.surface-dark .btn--primary:hover,
.footer .btn--primary:hover,
.mobile-menu .btn--primary:hover {
  --btn-bg: var(--ink-100);
  box-shadow: none;
}

/* ==========================================================================
   06 — CARDS
   ========================================================================== */
/* Soft and tactile rather than a technical panel: 16px corners, generous
   internal padding, a border you can barely see and a shadow you can
   barely see either. The depth is what makes it feel like an object; the
   restraint is what keeps it premium. */
/* A soft surface, not a container drawn around data. No border, no
   shadow — the card is simply a slightly different tone of the page with
   generous room inside it. */
.card {
  position: relative;
  background: var(--card);
  color: var(--card-fg);
  border: 1px solid var(--card-border);
  border-radius: var(--r-lg);
  padding: clamp(1.9rem, 3vw, 3rem);
  overflow: hidden;
  transition: background-color var(--d-base) var(--ease-out),
              border-color var(--d-base) var(--ease-out);
}

/* Hover barely happens: the surface warms very slightly. Nothing lifts,
   nothing glows, nothing announces itself. */
.card--hover:hover { background: var(--card-hover, #ececef); }

/* Dark cards are the exception, used where a dark section calls for them —
   never as the default treatment. */
.surface-dark .card {
  background: var(--surface-low);
  border-color: var(--card-border);
  box-shadow: none;
}

.surface-dark .card--hover:hover { background: #13253a; }

/* On the grey secondary background, cards are white and warm to a barely
   perceptible grey rather than getting darker. */
.surface-light .card--hover:hover,
.surface-soft .card--hover:hover { background: #fbfbfc; }

.card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  margin-bottom: var(--s-5);
}

.card__title { margin-bottom: var(--s-3); }
.card__body { color: var(--fg-muted); font-size: var(--t-small); }

.card__list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: var(--s-5);
}

/* Small technical chip — used for capability tags */
.chip {
  font-family: var(--font-mono);
  font-size: var(--t-nano);
  letter-spacing: 0.05em;
  padding: 0.32rem 0.66rem;
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  color: var(--fg-muted);
  white-space: nowrap;
  transition: border-color var(--d-fast) var(--ease-out),
              color var(--d-fast) var(--ease-out);
}

.card--hover:hover .chip { border-color: var(--blue-line); }

.chip--accent {
  color: var(--accent);
  border-color: var(--blue-line);
  background: var(--blue-wash);
}

/* ---------- Icon plate ------------------------------------------------ */
.icon-plate {
  width: 46px;
  height: 46px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: var(--r-sm);
  background: var(--blue-wash);
  color: var(--accent);
  border: 1px solid var(--blue-line);
  transition: background-color var(--d-base) var(--ease-out),
              transform var(--d-base) var(--ease-out);
}

.card--hover:hover .icon-plate {
  background: var(--blue);
  color: var(--white);
  border-color: var(--blue);
}

/* On blue cards the hover plate inverts instead, so it stays visible. */
.surface-dark .card--hover:hover .icon-plate,
.hero .card--hover:hover .icon-plate {
  background: var(--white);
  color: var(--surface-low);
  border-color: var(--white);
}

.icon-plate svg { width: 21px; height: 21px; }

/* ---------- Service card: the asymmetric feature grid ------------------
   Deliberately NOT a repeating 3-column set. The flagship card spans
   a larger area; the rest form a varied mosaic.                       */
.service-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: clamp(0.85rem, 1.4vw, 1.4rem);
}

.service-card {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: 260px;
  padding: clamp(1.4rem, 2.2vw, 2.2rem);
  border-radius: var(--r-lg);
  border: 1px solid var(--rule);
  background: var(--card);
  color: var(--card-fg);
  overflow: hidden;
  isolation: isolate;
  transition: transform var(--d-base) var(--ease-out),
              border-color var(--d-base) var(--ease-out);
}

.surface-dark .service-card {
  background: var(--surface-low);
  border-color: rgba(255, 255, 255, 0.10);
}

.service-card:hover { border-color: var(--blue-line); }
.service-card:hover { transform: translateY(calc(-4px * var(--motion))); }

/* Span rules that build the mosaic. Each row totals 6 so no gaps are left:
     row 1  flagship(4) + tall(2)
     row 2  half(3)     + half(3)
     row 3  third(2)    + wide(4)
   Varied sizes, complete rows. */
.service-card--flagship { grid-column: span 4; min-height: clamp(340px, 38vw, 460px); }
.service-card--tall     { grid-column: span 2; min-height: clamp(340px, 38vw, 460px); }
.service-card--half     { grid-column: span 3; }
.service-card--third    { grid-column: span 2; }
.service-card--wide     { grid-column: span 4; }
.service-card--full     { grid-column: span 6; min-height: 220px; }

@media (max-width: 900px) {
  .service-grid { grid-template-columns: repeat(2, 1fr); }
  .service-card--flagship,
  .service-card--tall,
  .service-card--half,
  .service-card--third,
  .service-card--wide,
  .service-card--full { grid-column: span 2; min-height: 250px; }
}

@media (max-width: 600px) {
  .service-grid { grid-template-columns: 1fr; }
  .service-card { grid-column: span 1 !important; min-height: 230px; }
}

.service-card__media {
  position: absolute;
  inset: 0;
  z-index: -2;
}

.service-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--d-slow) var(--ease-out);
}

.service-card:hover .service-card__media img {
  transform: scale(calc(1 + 0.03 * var(--motion)));
}

/* Legibility scrim over photography */
.service-card__media::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(
    to top,
    rgba(var(--scrim-rgb), 0.94) 0%,
    rgba(var(--scrim-rgb), 0.76) 34%,
    rgba(var(--scrim-rgb), 0.34) 70%,
    rgba(var(--scrim-rgb), 0.22) 100%
  );
}

.service-card--photo { color: var(--white); border-color: rgba(255, 255, 255, 0.12); }
.service-card--photo .card__body { color: rgba(255, 255, 255, 0.82); }
.service-card--photo .idx { color: rgba(255, 255, 255, 0.82); }
.service-card--photo .chip { color: rgba(255,255,255,0.86); border-color: rgba(255,255,255,0.30); }

.service-card__top {
  position: absolute;
  top: clamp(1.4rem, 2.2vw, 2.2rem);
  left: clamp(1.4rem, 2.2vw, 2.2rem);
  right: clamp(1.4rem, 2.2vw, 2.2rem);
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-4);
}

.service-card__title { margin-bottom: var(--s-3); }

/* ==========================================================================
   07 — SECTIONS
   ========================================================================== */

/* ---------- Hero ------------------------------------------------------ */
.hero {
  position: relative;
  min-height: clamp(600px, 96svh, 1000px);
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  padding-bottom: clamp(3rem, 7vw, 6rem);
  padding-top: calc(var(--nav-h) + 3rem);

  /* The hero is a dark surface, so it must carry the dark token set —
     otherwise ghost-button borders and hairlines resolve to their light
     values and disappear against the black. */
  --bg: var(--surface-deep);
  --bg-raised: var(--surface-low);
  --fg: var(--white);
  --fg-muted: var(--ink-300);
  --fg-subtle: var(--ink-350-on-dark);
  --rule: rgba(255, 255, 255, 0.10);
  --rule-strong: rgba(255, 255, 255, 0.24);
  --accent: var(--blue-bright);
  --accent-solid: #f4f6f8;
  --blue-wash: rgba(255, 255, 255, 0.08);
  --blue-line: rgba(255, 255, 255, 0.24);
  background: var(--surface-deep);
  color: #f4f6f8;
}

.hero__media {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.hero__media img,
.hero__media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Two-layer scrim: vertical for text, slight vignette for depth */
.hero__media::after {
  content: "";
  position: absolute;
  inset: 0;
  /* Weighted to the bottom, where the headline and copy sit, and eased off
     higher up so the blue surface stays bright rather than going navy.
     Still dense enough to hold white text over an unknown photograph. */
  background:
    linear-gradient(to top,
      rgba(var(--scrim-rgb), 0.82) 0%,
      rgba(var(--scrim-rgb), 0.56) 32%,
      rgba(var(--scrim-rgb), 0.20) 64%,
      rgba(var(--scrim-rgb), 0.34) 100%),
    radial-gradient(120% 90% at 50% 40%,
      transparent 46%, rgba(var(--scrim-rgb), 0.30) 100%);
}

/* A flat dark-navy overlay laid over the full-width media, weighted to the
   left where the headline sits. Sits above the shared scrim (z-index 1) but
   below .hero__inner (z-index 2), so the type stays clear. Driven by the
   scrim token so it tracks the palette rather than drifting from it. */
.hero--overlay-blue .hero__media::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(to right,
    rgba(var(--scrim-rgb), 0.92) 0%,
    rgba(var(--scrim-rgb), 0.72) 55%,
    rgba(var(--scrim-rgb), 0.52) 100%);
}

.hero__inner { position: relative; z-index: 2; width: 100%; }

.hero__title { max-width: 15ch; margin-bottom: var(--s-6); }

.hero__lead {
  max-width: 54ch;
  color: rgba(255, 255, 255, 0.78);
  margin-bottom: var(--s-7);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
  align-items: center;
}

/* Sub-hero for interior pages. Still cinematic — the brief's 85–100vh band
   applies here too; only the type scale steps down. */
.hero--page {
  min-height: clamp(520px, 84svh, 900px);
  align-items: center;
  padding-bottom: clamp(2rem, 4vw, 3rem);
}

.hero--page .hero__title { max-width: 18ch; }

/* ---------- Hero entrance ----------------------------------------------
   One easing system, one slow settle. The media eases from 103% to 100%
   over two and a half seconds — enough to feel alive, not enough to read
   as a zoom. Under reduced motion the global rule collapses the duration
   and the frame simply starts at rest. */
.hero__media {
  animation: hero-settle 2600ms var(--ease-out) both;
}

@keyframes hero-settle {
  from { transform: scale(1.03); }
  to   { transform: scale(1); }
}

/* Staggered entrance: eyebrow 0.4s, heading 0.7s, copy 1.0s, CTA 1.3s.
   Targeted by role rather than by an --i index so every hero on the site
   shares the same cadence without per-page markup. */
.hero .eyebrow[data-reveal]       { transition-delay: 400ms; }
.hero .hero__title[data-reveal]   { transition-delay: 700ms; }
.hero .hero__lead[data-reveal]    { transition-delay: 1000ms; }
.hero .hero__actions[data-reveal] { transition-delay: 1300ms; }

/* The line-mask headline variant runs on the heading's own clock. */
.hero .hero__title .reveal-lines > span {
  transition-delay: calc(700ms + var(--i, 0) * 110ms);
}

/* ---------- Film grain --------------------------------------------------
   Monochrome fractal noise laid over the large dark surfaces at 1.5%.
   It is meant to be felt rather than seen: it stops big flat navy fields
   from banding and gives them the texture of a printed plate.

   Painted above content on purpose — grain sits over the whole frame in
   film, and at this opacity it costs legibility nothing. pointer-events
   is off so it can never intercept a click. */
.surface-dark::after,
.hero::after,
.footer::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: var(--grain-opacity);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
  background-repeat: repeat;
}

.surface-dark,
.footer { position: relative; }

/* The hero's own layers already sit at z-index 1 and 2; keep the grain
   between the media and the copy so headlines stay perfectly crisp. */
.hero::after { z-index: 1; }

/* Scroll cue */
.hero__cue {
  position: absolute;
  bottom: clamp(1.25rem, 3vw, 2rem);
  right: var(--gutter);
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--font-mono);
  font-size: var(--t-nano);
  letter-spacing: var(--tr-mono);
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.84);   /* 5.4:1 on the blue hero */
}

.hero__cue-line {
  width: 1px;
  height: 34px;
  background: linear-gradient(to bottom,
    rgba(255,255,255,0.5), transparent);
  position: relative;
  overflow: hidden;
}

.hero__cue-line::after {
  content: "";
  position: absolute;
  top: 0; left: 0;
  width: 100%;
  height: 12px;
  background: var(--blue-bright);
  animation: cue-fall 2.4s var(--ease-in-out) infinite;
}

@keyframes cue-fall {
  0%   { transform: translateY(-14px); opacity: 0; }
  40%  { opacity: 1; }
  100% { transform: translateY(34px); opacity: 0; }
}

@media (max-width: 700px) { .hero__cue { display: none; } }

/* ---------- Section media backdrop -------------------------------------
   Muted footage or a still behind a dark section. Two things keep it in
   the background where it belongs: the media itself is held well under
   full opacity, and a flat dark-blue overlay sits above it. The copy is
   lifted to z-index 2 so it never has to compete with either. */
.section--media {
  position: relative;
  overflow: hidden;
}

.section--media > .wrap { position: relative; z-index: 2; }

.section__media {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.section__media video,
.section__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.5;
}

.section__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom,
    rgba(var(--scrim-rgb), 0.88) 0%,
    rgba(var(--scrim-rgb), 0.80) 100%);
}

/* ---------- Three-layer card graphic -----------------------------------
   One fixed box for all three line graphics so size, weight, and baseline
   alignment stay identical across the row. */
.layer-icon {
  display: block;
  width: 56px;
  height: 56px;
  margin-bottom: var(--s-5);
}

/* ---------- Operations band -------------------------------------------
   Compact four-column strip: line icon, label, one line of copy, hairline
   dividers between columns. No media, no button. */
.ops-row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}

.ops {
  padding-inline: clamp(1.15rem, 2.2vw, 2rem);
  border-left: 1px solid var(--rule);
}

.ops:first-child { border-left: 0; padding-left: 0; }

.ops__icon {
  display: block;
  color: var(--accent);
  margin-bottom: var(--s-4);
}

.ops__title {
  font-family: var(--font-display);
  font-size: var(--t-h4);
  font-weight: 700;
  letter-spacing: var(--tr-heading);
  margin-bottom: var(--s-3);
}

.ops__body { color: var(--fg-muted); font-size: var(--t-small); }

@media (max-width: 880px) {
  .ops-row { grid-template-columns: 1fr 1fr; }
  .ops:nth-child(odd) { border-left: 0; padding-left: 0; }
  .ops:nth-child(n + 3) {
    border-top: 1px solid var(--rule);
    padding-top: var(--s-6);
    margin-top: var(--s-6);
  }
}

@media (max-width: 520px) {
  .ops-row { grid-template-columns: 1fr; }
  .ops { border-left: 0; padding-inline: 0; }
  .ops + .ops {
    border-top: 1px solid var(--rule);
    padding-top: var(--s-6);
    margin-top: var(--s-6);
  }
}

/* ---------- Stat / spec row --------------------------------------------
   Columns of air with a hairline above each, rather than a grid of boxes.
   The label and value carry it; nothing is drawn around them. */
.spec-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: clamp(1.75rem, 3vw, 3rem);
}

.spec {
  padding-top: var(--s-5);
  border-top: 1px solid var(--rule);
  transition: border-color var(--d-base) var(--ease-out);
}

a.spec:hover { border-top-color: var(--rule-strong); }

.spec__label {
  font-family: var(--font-mono);
  font-size: var(--t-nano);
  letter-spacing: var(--tr-mono);
  text-transform: uppercase;
  color: var(--fg-subtle);
  margin-bottom: var(--s-3);
  display: block;
}

.spec__value {
  font-family: var(--font-display);
  font-size: clamp(1.1rem, 1.6vw, 1.35rem);
  font-weight: 600;
  letter-spacing: var(--tr-heading);
}

/* ---------- Feature panel (large rounded media + copy) ------------------ */
.panel {
  border-radius: var(--r-hero);
  overflow: hidden;
  position: relative;
  background: var(--surface-deep);
  color: var(--white);
  min-height: clamp(420px, 56vw, 660px);
  display: flex;
  align-items: flex-end;
  padding: clamp(1.75rem, 4vw, 4rem);
}

.panel__media { position: absolute; inset: 0; z-index: 0; }

.panel__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.panel__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    rgba(var(--scrim-rgb), 0.92) 0%,
    rgba(var(--scrim-rgb), 0.58) 42%,
    rgba(var(--scrim-rgb), 0.20) 78%
  );
}

.panel__content { position: relative; z-index: 1; max-width: 62ch; }
.panel__content .t-lead { color: rgba(255, 255, 255, 0.78); }

/* Panel variant where copy sits beside media rather than over it */
.feature {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(2rem, 5vw, 5.5rem);
  align-items: center;
}

.feature--reverse .feature__media { order: -1; }

.feature__media {
  border-radius: var(--r-xl);
  overflow: hidden;
  position: relative;
  aspect-ratio: 4 / 3.2;
  background: var(--surface-low);
}

.feature__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.feature__copy .t-lead { margin-block: var(--s-5); }

@media (max-width: 900px) {
  .feature { grid-template-columns: 1fr; }
  .feature--reverse .feature__media { order: 0; }
}

/* Media fills the row instead of being pinned to a fixed aspect ratio, so a
   taller copy column stops leaving dead space above and below the photo.
   The copy now sets the section height and the photo follows it.

   Written as "on by default, switched off when stacked" and keyed to the
   same max-width: 900px above, so the two rules can never disagree. Pairing
   it with a min-width: 901px query instead would leave fractional viewport
   widths between 900 and 901 matching neither. Once stacked the ratio has
   to come back: it is the only thing giving the slot height, because the
   <img> inside it is absolutely positioned and contributes none of its own. */
.feature--fill { align-items: stretch; }
.feature--fill .feature__media { aspect-ratio: auto; }

@media (max-width: 900px) {
  .feature--fill { align-items: center; }
  .feature--fill .feature__media { aspect-ratio: 4 / 3.2; }
}

/* Capability list with hairline separators */
.cap-list { margin-top: var(--s-6); border-top: 1px solid var(--rule); }

.cap-list li {
  display: flex;
  align-items: baseline;
  gap: var(--s-4);
  padding-block: 0.72rem;
  border-bottom: 1px solid var(--rule);
  font-size: var(--t-small);
}

.cap-list li::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent);
  flex: none;
  transform: translateY(-2px);
}

/* Two-column capability list on wide screens */
.cap-list--split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: var(--s-6);
}

@media (max-width: 560px) { .cap-list--split { grid-template-columns: 1fr; } }

/* ---------- Split: residential / commercial ---------------------------- */
.duo {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(0.85rem, 1.5vw, 1.5rem);
}

@media (max-width: 860px) { .duo { grid-template-columns: 1fr; } }

.duo__side {
  position: relative;
  border-radius: var(--r-xl);
  overflow: hidden;
  min-height: clamp(460px, 52vw, 620px);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: clamp(1.6rem, 3vw, 2.85rem);
  color: var(--white);
  isolation: isolate;
}

.duo__media { position: absolute; inset: 0; z-index: -1; }

.duo__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--d-slow) var(--ease-out);
}

.duo__side:hover .duo__media img {
  transform: scale(calc(1 + 0.035 * var(--motion)));
}

.duo__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top,
    rgba(var(--scrim-rgb), 0.93) 6%,
    rgba(var(--scrim-rgb), 0.64) 45%,
    rgba(var(--scrim-rgb), 0.26) 100%);
}

.duo__list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-block: var(--s-5);
}

.duo__list .chip {
  color: rgba(255, 255, 255, 0.82);
  border-color: rgba(255, 255, 255, 0.22);
}

/* ---------- Process timeline -------------------------------------------
   A drawn rail rather than a row of boxes: one hairline across the whole
   sequence, a node per stage, and a progress fill that advances as the
   section is read. The steps stay legible at rest — the active state only
   adjusts emphasis, so nothing is ever hidden behind an interaction. */
.process {
  position: relative;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 0;
  padding-top: var(--s-8);
}

/* The rail */
.process::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: var(--rule);
}

/* The progress fill. --progress is set by the script, 0 to 1. */
.process::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  height: 1px;
  width: calc(var(--progress, 0) * 100%);
  background: var(--accent);
  transition: width var(--d-slow) var(--ease-out);
}

.process__step {
  position: relative;
  padding: var(--s-7) clamp(1rem, 2vw, 2rem) 0 0;
  opacity: 0.55;
  transition: opacity var(--d-base) var(--ease-out);
}

.process__step.is-active { opacity: 1; }

/* The node sitting on the rail */
.process__step::before {
  content: "";
  position: absolute;
  top: -4px;
  left: 0;
  width: 7px;
  height: 7px;
  border-radius: var(--r-pill);
  background: var(--bg);
  border: 1px solid var(--rule-strong);
  transition: background-color var(--d-base) var(--ease-out),
              border-color var(--d-base) var(--ease-out),
              transform var(--d-base) var(--ease-out);
}

.process__step.is-active::before {
  background: var(--accent);
  border-color: var(--accent);
  transform: scale(1.25);
}

/* Large, quiet numerals. They carry the sequence, so the copy underneath
   can stay short. */
.process__num {
  font-family: var(--font-display);
  font-size: clamp(2rem, 3.2vw, 3rem);
  font-weight: 600;
  line-height: 1;
  color: var(--fg-subtle);
  letter-spacing: var(--tr-heading);
  font-variant-numeric: tabular-nums;
  display: block;
  margin-bottom: var(--s-4);
  transition: color var(--d-base) var(--ease-out);
}

.process__step.is-active .process__num { color: var(--fg); }

/* Vertical rail on tablet and below. Deliberately a different layout, not
   a squashed version of the horizontal one. */
@media (max-width: 900px) {
  .process {
    grid-template-columns: 1fr;
    padding-top: 0;
    padding-left: var(--s-7);
  }

  .process::before {
    top: 0;
    bottom: 0;
    left: 0;
    right: auto;
    width: 1px;
    height: auto;
  }

  .process::after {
    top: 0;
    left: 0;
    width: 1px;
    height: calc(var(--progress, 0) * 100%);
    transition: height var(--d-slow) var(--ease-out);
  }

  .process__step {
    padding: 0 0 var(--s-7) 0;
    opacity: 1;          /* everything readable at once on a small screen */
  }

  .process__step:last-child { padding-bottom: 0; }

  .process__step::before {
    top: 7px;
    left: calc(-1 * var(--s-7) - 3px);
  }
}

.process__title { margin-bottom: var(--s-3); }

/* ---------- Technology ecosystem -------------------------------------- */
/* ---------- Technology platforms ---------------------------------------
   Not four boxes. Four columns of air with a name and a category in them,
   separated by a single hairline above each. Typography does the work. */
.eco {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(2rem, 4vw, 4rem) clamp(1.5rem, 3vw, 3.5rem);
}

@media (max-width: 900px) { .eco { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 520px) { .eco { grid-template-columns: 1fr; } }

.eco__item {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding-top: var(--s-5);
  border-top: 1px solid var(--rule);
}

.eco__name {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.3rem, 2vw, 1.75rem);
  letter-spacing: var(--tr-heading);
  line-height: var(--lh-snug);
}

.eco__cat {
  font-size: var(--t-body);
  color: var(--fg-muted);
}

/* ---------- Final CTA -------------------------------------------------- */
.cta {
  position: relative;
  overflow: hidden;
  text-align: center;
  padding-block: clamp(5rem, 12vw, 11rem);
}

/* Floor-glow — the one gradient on the site, kept very subtle. On a blue
   section a blue glow is invisible, so this lifts toward white instead. */
.cta::before {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -60%;
  width: min(1100px, 130%);
  aspect-ratio: 1;
  transform: translateX(-50%);
  background: radial-gradient(circle,
    rgba(255, 255, 255, 0.16) 0%,
    rgba(255, 255, 255, 0.05) 42%,
    transparent 68%);
  pointer-events: none;
}

.cta__inner { position: relative; z-index: 1; }
.cta__title { max-width: 18ch; margin-inline: auto; margin-bottom: var(--s-6); }
.cta__lead { max-width: 52ch; margin-inline: auto; margin-bottom: var(--s-8); }

/* ==========================================================================
   08 — SIGNATURE SYSTEM DIAGRAM
   Orthogonal routing echoes real structured-cabling geometry.
   ========================================================================== */
.sysmap { position: relative; width: 100%; }

.sysmap__stage {
  position: relative;
  width: 100%;
  border-radius: var(--r-xl);
  border: 1px solid var(--rule);
  background:
    linear-gradient(rgba(255,255,255,0.015) 1px, transparent 1px) 0 0 / 100% 44px,
    linear-gradient(90deg, rgba(255,255,255,0.015) 1px, transparent 1px) 0 0 / 44px 100%,
    var(--surface-mid);
  overflow: hidden;
  padding: clamp(1rem, 2.5vw, 2rem);
}

.sysmap__svg { width: 100%; height: auto; display: block; }

/* Connection paths draw themselves in on reveal */
.sysmap__path {
  fill: none;
  stroke: rgba(255, 255, 255, 0.16);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: var(--len, 1000);
  stroke-dashoffset: var(--len, 1000);
  transition: stroke-dashoffset 1.15s var(--ease-out);
  transition-delay: calc(var(--i, 0) * 90ms);
}

.sysmap.is-revealed .sysmap__path { stroke-dashoffset: 0; }

/* The travelling data pulse */
.sysmap__pulse {
  fill: none;
  stroke: var(--white);   /* white reads on blue; blue-on-blue would not */
  stroke-width: 2;
  stroke-linecap: round;
  stroke-dasharray: 26 var(--len, 1000);
  stroke-dashoffset: var(--len, 1000);
  opacity: 0;
}

.sysmap.is-revealed .sysmap__pulse {
  opacity: 1;
  animation: pulse-run 3.4s linear infinite;
  animation-delay: calc(var(--i, 0) * 420ms);
}

@keyframes pulse-run {
  0%   { stroke-dashoffset: var(--len, 1000); opacity: 0; }
  8%   { opacity: 0.95; }
  72%  { opacity: 0.95; }
  100% { stroke-dashoffset: 0; opacity: 0; }
}

/* Node plates */
.sysmap__node rect {
  fill: var(--surface-low);
  stroke: rgba(255, 255, 255, 0.14);
  stroke-width: 1;
  transition: fill var(--d-base) var(--ease-out),
              stroke var(--d-base) var(--ease-out);
}

.sysmap__node text {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: -0.01em;
  fill: #fff;
}

.sysmap__node .sysmap__meta {
  font-family: var(--font-mono);
  font-size: 8.5px;
  letter-spacing: 0.14em;
  fill: rgba(255, 255, 255, 0.68);
  font-weight: 400;
}

.sysmap__node { opacity: 0; transform: translateY(10px); }

.sysmap.is-revealed .sysmap__node {
  animation: node-in var(--d-slow) var(--ease-out) forwards;
  animation-delay: calc(280ms + var(--i, 0) * 85ms);
}

@keyframes node-in {
  to { opacity: 1; transform: translateY(0); }
}

.sysmap__node:hover rect {
  fill: var(--surface-mid);
  stroke: var(--blue);
}

/* The central core node reads as the hub. On a blue stage it inverts to a
   white plate with deep-blue type — the strongest contrast available, so
   the hub still reads as the focal point. */
.sysmap__core rect {
  fill: var(--white);
  stroke: var(--white);
}

.sysmap__core text { fill: var(--surface-low); }
.sysmap__core .sysmap__meta { fill: rgba(7, 17, 31, 0.72); }

.sysmap__dot { fill: var(--white); }

/* Wide diagram on desktop, vertical rail on narrow screens.
   Two real layouts — the mobile view is not a shrunken desktop one. */
.sysmap__svg--tall { display: none; }

@media (max-width: 820px) {
  .sysmap__svg--wide { display: none; }
  .sysmap__svg--tall { display: block; }
  .sysmap__stage { padding: 0.85rem; }
}

/* Screen-reader text: the same information as the diagram, always
   available regardless of motion settings or SVG support. */
.sysmap__fallback {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

@media (prefers-reduced-motion: reduce) {
  .sysmap__path { stroke-dashoffset: 0; }
  .sysmap__pulse { display: none; }
  .sysmap__node { opacity: 1; transform: none; animation: none; }
}

/* ==========================================================================
   09 — FORMS
   ========================================================================== */
.form { display: grid; gap: var(--s-5); }

.field { display: grid; gap: 0.5rem; }

.field__label {
  font-family: var(--font-mono);
  font-size: var(--t-nano);
  letter-spacing: var(--tr-mono);
  text-transform: uppercase;
  color: var(--fg-muted);
}

.field__opt { text-transform: none; letter-spacing: 0; opacity: 0.7; }

.field__input,
.field__textarea,
.field__select {
  width: 100%;
  padding: 0.95rem 1.1rem;
  background: var(--bg-raised);
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-sm);
  font-size: var(--t-body);
  color: var(--fg);
  transition: border-color var(--d-base) var(--ease-out),
              box-shadow var(--d-base) var(--ease-out);
}

.surface-dark .field__input,
.surface-dark .field__textarea {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.16);
  color: var(--white);
}

.field__input::placeholder,
.field__textarea::placeholder { color: var(--fg-subtle); }

.field__input:focus,
.field__textarea:focus,
.field__select:focus {
  border-color: var(--blue);
  box-shadow: 0 0 0 3px var(--blue-wash);
  outline: none;
}

.field__textarea { resize: vertical; min-height: 132px; }

/* Native select, matched to the text inputs. The chevron is a background
   image rather than the OS arrow so the closed control looks the same in
   every browser.

   background-color is set on its own here — never the `background`
   shorthand — because the surface-dark override below would otherwise
   reset background-image and take the chevron with it. */
.field__select {
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  padding-right: 2.75rem;
  background-color: var(--bg-raised);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%230a1740' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.6 6 6.4 11 1.6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 1.05rem center;
  background-size: 11px 7px;
}

.surface-dark .field__select {
  background-color: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.16);
  color: var(--white);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23ffffff' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.6 6 6.4 11 1.6'/%3E%3C/svg%3E");
}

/* The dropdown list itself is drawn by the OS, which does not inherit the
   dark surface — set it explicitly so options never land white-on-white. */
.field__select option {
  color: var(--ink-900);
  background-color: var(--white);
}

.field__hint { font-size: var(--t-micro); color: var(--fg-subtle); }

/* Inline validation message */
.field__error {
  font-size: var(--t-micro);
  color: #e5484d;
  display: none;
  align-items: center;
  gap: 0.35rem;
}

.field.has-error .field__input,
.field.has-error .field__textarea,
.field.has-error .field__select { border-color: #e5484d; }
.field.has-error .field__error { display: flex; }

.form__status {
  padding: 1rem 1.15rem;
  border-radius: var(--r-sm);
  border: 1px solid var(--blue-line);
  background: var(--blue-wash);
  font-size: var(--t-small);
  display: none;
}

.form__status.is-visible { display: block; }

/* Consent line under the submit button. The inline link is underlined
   because the site's base rule strips decoration from every anchor. */
.form__legal {
  font-size: var(--t-micro);
  color: var(--fg-subtle);
  line-height: var(--lh-body);
}

.form__legal a {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 0.18em;
  transition: color var(--d-fast) var(--ease-out);
}

.form__legal a:hover { color: var(--accent-solid); }

/* ==========================================================================
   10 — FOOTER
   ========================================================================== */
.footer {
  background: var(--surface-deep);
  color: var(--white);
  padding-top: clamp(3.5rem, 7vw, 6rem);
  padding-bottom: var(--s-7);
}

.footer__top {
  display: grid;
  grid-template-columns: 1.5fr 1fr 1fr;
  gap: clamp(2rem, 4vw, 3.5rem);
  padding-bottom: clamp(2.5rem, 5vw, 4rem);
}

@media (max-width: 900px) { .footer__top { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .footer__top { grid-template-columns: 1fr; } }

.footer__brand-line {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 3vw, 2rem);
  font-weight: 800;
  letter-spacing: -0.035em;
  margin-block: var(--s-5) var(--s-4);
}

.footer__tag {
  color: var(--ink-300);
  font-size: var(--t-small);
  max-width: 34ch;
}

.footer__col-title {
  font-family: var(--font-mono);
  font-size: var(--t-nano);
  letter-spacing: var(--tr-mono);
  text-transform: uppercase;
  color: var(--ink-300);   /* 6.35:1 on the footer's near-black */
  margin-bottom: var(--s-5);
}

.footer__list { display: grid; gap: 0.72rem; }

/* The footer's CTA is the last architectural gesture on the page, so it
   carries real weight rather than sitting in the link list. It now follows
   the email address, so it needs the separation the list used to carry. */
.footer__cta { width: 100%; max-width: 260px; margin-top: var(--s-5); }

.footer__list a {
  color: var(--ink-200);
  font-size: var(--t-small);
  transition: color var(--d-fast) var(--ease-out);
  display: inline-block;
}

.footer__list a:hover { color: var(--white); }

.footer__social {
  display: flex;
  gap: var(--s-3);
  margin-top: var(--s-6);
}

.footer__social-link {
  display: inline-grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: var(--r-pill);
  border: 1px solid rgba(255, 255, 255, 0.22);
  color: var(--ink-200);
  transition: color var(--d-fast) var(--ease-out),
              border-color var(--d-fast) var(--ease-out),
              background var(--d-fast) var(--ease-out);
}

.footer__social-link:hover {
  color: var(--white);
  border-color: rgba(255, 255, 255, 0.55);
  background: rgba(255, 255, 255, 0.10);
}

.footer__bottom {
  border-top: 1px solid rgba(255, 255, 255, 0.10);
  padding-top: var(--s-6);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
}

.footer__meta {
  font-family: var(--font-mono);
  font-size: var(--t-nano);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-300);   /* 6.35:1 on the footer's near-black */
}


/* ==========================================================================
   11 — SCROLL REVEAL
   Opt-in via [data-reveal]. JS adds .is-revealed when the element enters
   the viewport. Forced visible under reduced motion (see tokens.css).
   ========================================================================== */
[data-reveal] {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity var(--d-reveal) var(--ease-out),
              transform var(--d-reveal) var(--ease-out);
  transition-delay: calc(var(--i, 0) * 80ms);
  will-change: opacity, transform;
}

/* Safety net: the starting state above is invisible, and for the "mask"
   variant it is clipped away entirely. Nothing but JS ever clears it, so if
   the script fails to load the page renders blank. .no-js is on <html> until
   the script boots and removes it, which makes it exactly the right hook. */
.no-js [data-reveal] {
  opacity: 1;
  transform: none;
  clip-path: none;
}

[data-reveal].is-revealed {
  opacity: 1;
  transform: none;
  will-change: auto;
}

[data-reveal="fade"] { transform: none; }
[data-reveal="left"] { transform: translateX(-26px); }
[data-reveal="right"] { transform: translateX(26px); }
[data-reveal="scale"] { transform: scale(0.965); }

/* Image reveal: the picture un-masks upward */
[data-reveal="mask"] {
  opacity: 1;
  clip-path: inset(100% 0 0 0);
  transform: none;
  transition: clip-path 1.15s var(--ease-out);
}

[data-reveal="mask"].is-revealed { clip-path: inset(0 0 0 0); }

/* Line-by-line heading reveal */
.reveal-lines { display: block; overflow: hidden; }

.reveal-lines > span {
  display: block;
  transform: translateY(102%);
  transition: transform 1s var(--ease-out);
  transition-delay: calc(var(--i, 0) * 90ms);
}

.is-revealed .reveal-lines > span,
.reveal-lines.is-revealed > span { transform: translateY(0); }

@media (prefers-reduced-motion: reduce) {
  .reveal-lines > span { transform: none !important; }
}

/* Parallax layer — translated by JS, capped to a few percent */
[data-parallax] { will-change: transform; }

/* ==========================================================================
   12 — UTILITIES
   ========================================================================== */
.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;
}

.stack-2 { display: grid; gap: var(--s-2); }
.stack-4 { display: grid; gap: var(--s-4); }
.stack-6 { display: grid; gap: var(--s-6); }

.mt-4 { margin-top: var(--s-4); }
.mt-6 { margin-top: var(--s-6); }
.mt-8 { margin-top: var(--s-8); }
.mb-6 { margin-bottom: var(--s-6); }

.text-center { text-align: center; }
.flex-row { display: flex; flex-wrap: wrap; gap: var(--s-4); align-items: center; }
.justify-center { justify-content: center; }

/* ---------- Media slot ------------------------------------------------
   Every photograph on the site sits in one of these. The <img> points at
   its final path; until that file exists the technical placeholder shows
   instead. Drop the real photo in and it takes over with no markup edit.
   Slot inventory: assets/img/README.md                                 */
.media-slot {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: var(--surface-low);
}

.media-slot img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.media-slot .ph { position: absolute; inset: 0; }

/* Placeholder media — a neutral technical field, clearly not a real photo.
   Every instance is meant to be replaced. See assets/img/README.md. */
.ph {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 180px;
  background:
    linear-gradient(rgba(255,255,255,0.028) 1px, transparent 1px) 0 0 / 100% 32px,
    linear-gradient(90deg, rgba(255,255,255,0.028) 1px, transparent 1px) 0 0 / 32px 100%,
    var(--surface-low);
  display: grid;
  place-items: center;
  color: var(--ink-350-on-dark);   /* 4.89:1 on the placeholder field */
}

.ph__label {
  font-family: var(--font-mono);
  font-size: var(--t-nano);
  letter-spacing: var(--tr-mono);
  text-transform: uppercase;
  text-align: center;
  padding: 0.5rem 1rem;
  border: 1px dashed rgba(255, 255, 255, 0.18);
  border-radius: var(--r-pill);
  max-width: 90%;
  overflow-wrap: anywhere;
}

/* Wherever copy sits ON TOP of the media, the placeholder label is parked in
   the top-left corner so it never collides with the text over it. Applies to
   every overlay context, not just the ones tagged in markup. */
.ph--corner,
.hero__media .ph,
.panel__media .ph,
.duo__media .ph,
.service-card__media .ph { place-items: start; }

.ph--corner .ph__label,
.hero__media .ph__label,
.panel__media .ph__label,
.duo__media .ph__label,
.service-card__media .ph__label {
  margin: clamp(0.75rem, 2vw, 1.25rem);
  font-size: 0.625rem;
  opacity: 0.75;
}

/* Inside a hero the label also has to clear the fixed navigation bar. */
.hero .ph--corner .ph__label {
  margin-top: calc(var(--nav-h) + 0.85rem);
}

/* Inside a service card it has to clear the index numeral and tag row. */
.service-card__media .ph__label {
  margin-top: 3.4rem;
  opacity: 0.55;
}
