/* ==========================================================================
   NEXISTEK — Layout & Sections
   Mobile-first composition. Every breakpoint lives in responsive.css.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Shell
   -------------------------------------------------------------------------- */
.page {
  position: relative;
  isolation: isolate;
}

/* A single fixed field of light behind the whole document. Sections sit on
   top of it, so scrolling reads as travelling through one space. */
.page::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: var(--z-below);
  background:
    radial-gradient(
      70rem 45rem at 78% -10%,
      rgb(var(--rgb-primary) / 0.1),
      transparent 60%
    ),
    radial-gradient(
      55rem 40rem at 8% 55%,
      rgb(var(--rgb-deep) / 0.9),
      transparent 65%
    ),
    var(--c-void);
  pointer-events: none;
}

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

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

/* Ambient background: a soft glow plus a faint grid, masked so both fade out
   before they reach the far edge. For sections that are text-only and would
   otherwise sit in a flat, empty field — a lighter touch than a full
   illustration, borrowed from the same light source as the hero. */
.ambient {
  position: relative;
  isolation: isolate;
}

.ambient::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: var(--z-below);
  background: radial-gradient(
      34rem 28rem at var(--ambient-x, 88%) var(--ambient-y, 12%),
      rgb(var(--rgb-primary) / 0.09),
      transparent 62%
    ),
    linear-gradient(rgb(var(--rgb-text) / 0.025) 1px, transparent 1px),
    linear-gradient(90deg, rgb(var(--rgb-text) / 0.025) 1px, transparent 1px);
  background-size: 100% 100%, 2.75rem 2.75rem, 2.75rem 2.75rem;
  -webkit-mask-image: radial-gradient(
    46% 60% at var(--ambient-x, 88%) var(--ambient-y, 12%),
    var(--c-void) 0%,
    transparent 78%
  );
  mask-image: radial-gradient(
    46% 60% at var(--ambient-x, 88%) var(--ambient-y, 12%),
    var(--c-void) 0%,
    transparent 78%
  );
  pointer-events: none;
}

.ambient--left {
  --ambient-x: 12%;
  --ambient-y: 18%;
}

/* --------------------------------------------------------------------------
   2. Header / navigation
   -------------------------------------------------------------------------- */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: var(--z-nav);
  transition: transform var(--dur-slow) var(--ease-out),
    background-color var(--dur-slow) var(--ease-out),
    border-color var(--dur-slow) var(--ease-out);
  border-bottom: 1px solid transparent;
}

/* Condensed state: applied by main.js past the fold. */
.site-header.is-condensed {
  background-color: rgb(var(--rgb-void) / 0.72);
  border-bottom-color: var(--border-hairline);
  -webkit-backdrop-filter: blur(var(--blur-glass));
  backdrop-filter: blur(var(--blur-glass));
}

/* Hidden while scrolling down, revealed on scroll up. */
.site-header.is-hidden {
  transform: translateY(-100%);
}

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-5);
  height: var(--nav-h);
  /* Positioning context for the wide dropdown panel, which centres against
     the nav rather than against the item that triggers it. */
  position: relative;
}

/* Brand ------------------------------------------------------------------- */
.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  flex: none;
}

/* The official lockup, height-constrained. width/height attributes stay on the
   element so the intrinsic ratio reserves space and nothing shifts on load. */
.brand__logo {
  width: auto;
  height: 1.75rem;
  transition: filter var(--dur-slow) var(--ease-out),
    transform var(--dur-slow) var(--ease-out);
}

.brand:hover .brand__logo,
.brand:focus-visible .brand__logo {
  filter: drop-shadow(var(--glow-sm));
  transform: translateY(-1px);
}

.site-footer__logo {
  height: 2.5rem;
  /* .site-footer__intro is a flex column with the default `stretch`, which
     would otherwise pull this to the container's full width — with its
     height already fixed, that distorted the logo instead of just scaling it. */
  align-self: flex-start;
}

/* Desktop links (hidden on mobile — see responsive.css) ------------------- */
.nav__links {
  display: none;
  align-items: center;
  gap: var(--sp-2);
  list-style: none;
}

.nav__link {
  position: relative;
  display: inline-block;
  /* A destination is one word-group, never two lines — "Career Development"
     broke across the row and pushed the whole nav out of shape. */
  white-space: nowrap;
  padding: var(--sp-2) var(--sp-4);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  border-radius: var(--r-full);
  transition: color var(--dur-base) var(--ease-out),
    transform var(--dur-fast) var(--ease-spring);
}

.nav__link:hover,
.nav__link:focus-visible {
  color: var(--text-primary);
  transform: scale(1.1);
}

.nav__link[aria-current="page"] {
  color: var(--c-primary);
}

.nav__actions {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex: none;
}

/* An action, not a destination — visually separate from the six links and
   never counted as one of them. */
.nav__cta {
  display: none;
}

/* --- Desktop dropdowns ----------------------------------------------------
 * Courses and Career Development. Each trigger is a real link to a real
 * page, so everything here is enhancement: with the CSS or the script gone,
 * the link still navigates.
 */
.nav__item--menu {
  position: relative;
}

/* A five-column panel is far wider than the item that triggers it, so that
   item goes static and the panel centres against the whole nav instead —
   centred on the item it would run off the side of the viewport. */
.nav__item--wide {
  position: static;
}

.nav__link-caret {
  width: 0.75rem;
  height: 0.75rem;
  margin-left: var(--sp-1);
  vertical-align: -0.1em;
  transition: transform var(--dur-base) var(--ease-out);
}

.nav__item.is-open .nav__link-caret {
  transform: rotate(180deg);
}

.nav__item.is-open > .nav__link {
  color: var(--text-primary);
}

.mega {
  position: absolute;
  top: 100%;
  left: 50%;
  z-index: var(--z-raised);
  /* Padded rather than offset, so the gap between trigger and panel is part
     of the panel — the pointer crosses it without the menu closing. */
  padding-top: var(--sp-3);
  transform: translateX(-50%) translateY(-0.5rem);
  visibility: hidden;
  pointer-events: none;
  /* Deliberately no opacity transition. An element with opacity < 1 becomes
     a backdrop root, which suppresses backdrop-filter on everything inside
     it — so fading this in left the panel unblurred for the whole
     transition and snapped the blur on at the end, showing sharp page text
     through it first. The panel slides instead, and the glass is correct
     from the first frame. */
  transition: transform var(--dur-base) var(--ease-out),
    visibility var(--dur-base) linear;
}

.nav__item.is-open .mega {
  transform: translateX(-50%);
  visibility: visible;
  pointer-events: auto;
}

.mega__inner {
  width: max-content;
  max-width: calc(100vw - var(--gutter) * 2);
  padding: var(--sp-6);
  border-radius: var(--r-lg);
  /* .glass tints with 3.5% white, which lightens rather than darkens — over
     the hero's lit content that left the menu links competing with whatever
     was behind them. This panel sits over arbitrary page content, so it gets
     a dark ground of its own; .glass's gradient still paints over the top,
     so it keeps the same sheen as every other panel. */
  background-color: rgb(var(--rgb-void) / 0.88);
}

/* Titles share one grid row and lists share the next, so a heading that
   wraps ("Safety & Industrial Practices") lifts every column's rule
   together instead of dropping its own list a line below its neighbours.
   .mega__col goes display:contents to hand its two children straight to
   this grid — the alternative was guessing a min-height for the title.
   The rows are assigned explicitly because display:contents flattens in
   DOM order (title, list, title, list…), not all titles and then all
   lists, so auto-placement alone interleaves them across both rows. */
.mega__cols {
  display: grid;
  grid-template-rows: auto auto;
  gap: 0 var(--sp-7);
}

.mega__col {
  display: contents;
}

.mega__cols > .mega__col > .mega__col-title {
  grid-row: 1;
}

.mega__cols > .mega__col > .mega__list {
  grid-row: 2;
}

/* Column counts are set from the data, not guessed — see navbar.php. */
.mega__cols--1 {
  grid-template-columns: minmax(13rem, 1fr);
}

.mega__cols--5 {
  grid-template-columns: repeat(5, minmax(9.5rem, 1fr));
}

/* Bottom-aligned within the shared title row, so the rules under every
   heading line up whether the heading wrapped or not. */
.mega__col-title {
  display: flex;
  align-items: flex-end;
  padding-bottom: var(--sp-2);
  border-bottom: 1px solid var(--border-hairline);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--c-primary);
}

.mega__list {
  display: flex;
  flex-direction: column;
  margin-top: var(--sp-3);
  list-style: none;
}

.mega__link {
  display: block;
  padding-block: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  transition: color var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-out);
}

.mega__link:hover,
.mega__link:focus-visible {
  color: var(--text-primary);
  transform: translateX(3px);
}

.mega__all {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  margin-top: var(--sp-6);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border-hairline);
  width: 100%;
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--c-primary);
}

.mega__all:hover .btn__icon,
.mega__all:focus-visible .btn__icon {
  transform: translateX(3px);
}

/* Mobile overlay menu ------------------------------------------------------ */
.nav__panel {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* With both accordions open the content can outgrow the viewport. A
     centred flex column overflows in both directions and puts its own top
     out of reach, so scroll it — and centre it "safely", which falls back
     to flex-start rather than clipping once it no longer fits. Declared
     after the plain value so browsers without `safe` keep the old one. */
  justify-content: safe center;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--sp-9) var(--gutter) var(--sp-8);
  background-color: var(--surface-scrim);
  -webkit-backdrop-filter: blur(var(--blur-heavy));
  backdrop-filter: blur(var(--blur-heavy));
  visibility: hidden;
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease-out),
    visibility var(--dur-slow) linear;
}

.nav__panel.is-open {
  visibility: visible;
  opacity: 1;
}

.nav__panel-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  list-style: none;
}

/* Sized to fit rather than to fill. At clamp(2rem, 9vw, 3rem) this ran to
   48px on a tablet: "Career Development" broke onto two lines, seven items
   plus the tagline and the Enquire button overflowed a phone screen, and the
   panel read as a poster rather than as navigation. This tops out at 34px,
   which keeps every label on one line down to 360px wide and leaves the
   whole menu — footer included — on screen without scrolling. */
.nav__panel-link {
  display: flex;
  align-items: baseline;
  gap: var(--sp-4);
  padding-block: var(--sp-3);
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 6.5vw, 2.125rem);
  font-weight: 500;
  letter-spacing: var(--tracking-tight);
  border-bottom: 1px solid var(--border-hairline);
  transform: translateY(1.5rem);
  opacity: 0;
  transition: color var(--dur-base) var(--ease-out),
    transform var(--dur-slow) var(--ease-out),
    opacity var(--dur-slow) var(--ease-out);
}

.nav__panel.is-open .nav__panel-link {
  transform: translateY(0);
  opacity: 1;
}

.nav__panel-link:hover,
.nav__panel-link:focus-visible {
  color: var(--text-energised);
}

/* --- Mobile accordions ----------------------------------------------------
 * Courses and Career Development expand independently. Two separate
 * offerings, never merged into one list.
 *
 * The disclosure sits over the link rather than beside it so that the
 * link keeps its full-width rule and its staggered entrance untouched.
 * Two distinct targets: the label goes to the page, the caret opens the
 * categories. One target doing both is what makes mobile menus unusable.
 */
.nav__panel-row {
  position: relative;
}

.nav__panel-row .nav__panel-link {
  padding-right: 3.5rem;
}

.nav__panel-toggle {
  position: absolute;
  top: 50%;
  right: 0;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  border: 1px solid var(--border-hairline);
  border-radius: var(--r-full);
  color: var(--text-secondary);
  cursor: pointer;
  transition: color var(--dur-base) var(--ease-out),
    border-color var(--dur-base) var(--ease-out);
}

.nav__panel-toggle:hover,
.nav__panel-toggle:focus-visible {
  color: var(--text-primary);
  border-color: var(--border-energised);
}

.nav__panel-caret {
  width: 1.125rem;
  height: 1.125rem;
  transition: transform var(--dur-base) var(--ease-out);
}

.nav__panel-toggle[aria-expanded="true"] .nav__panel-caret {
  transform: rotate(180deg);
}

.nav__panel-sub {
  padding: var(--sp-4) 0 var(--sp-5) var(--sp-6);
  border-left: 1px solid var(--border-hairline);
  margin-left: var(--sp-2);
}

.nav__panel-sub-title {
  margin-top: var(--sp-4);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--c-primary);
}

.nav__panel-sub-title:first-child {
  margin-top: 0;
}

.nav__panel-sub-list {
  display: flex;
  flex-direction: column;
  margin-top: var(--sp-2);
  list-style: none;
}

.nav__panel-sub-link {
  display: block;
  padding-block: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  transition: color var(--dur-base) var(--ease-out);
}

.nav__panel-sub-link:hover,
.nav__panel-sub-link:focus-visible {
  color: var(--text-primary);
}

.nav__panel-foot {
  margin-top: var(--sp-8);
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

.nav__close {
  position: absolute;
  top: calc((var(--nav-h) - 2.75rem) / 2);
  right: var(--gutter);
}

/* The open/close controls carry the whole navigation on a phone, so they are
   larger than a general icon button and their border is drawn rather than
   hinted — against the hero's bright top-right glow the hairline version
   nearly disappeared. */
.nav__toggle,
.nav__close {
  width: 3rem;
  height: 3rem;
  border-color: var(--border-soft);
  background-color: rgb(var(--rgb-void) / 0.55);
  -webkit-backdrop-filter: blur(var(--blur-glass));
  backdrop-filter: blur(var(--blur-glass));
}

.nav__toggle svg,
.nav__close svg {
  width: 1.375rem;
  height: 1.375rem;
}

/* --------------------------------------------------------------------------
   3. Hero
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  display: flex;
  align-items: flex-end;
  min-height: 100svh;
  padding-block: calc(var(--nav-h) + var(--sp-8)) var(--sp-9);
  overflow: hidden;
}

.hero__stage {
  position: absolute;
  inset: 0;
  z-index: var(--z-below);
  pointer-events: none;
}

/* Must be positioned. Its siblings (.hero__fallback, .hero__vignette) are
   absolutely positioned, and positioned elements paint after in-flow ones
   regardless of DOM order — so a static canvas ends up *underneath* the
   fallback, whose background stack ends in an opaque var(--c-void). That
   hid the WebGL scene completely while it carried on rendering. */
.hero__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  opacity: 0;
  transition: opacity 1.2s var(--ease-out);
  /* Scene clears to near-black (see COLOR_VOID in three-hero.js). "lighten"
     keeps whichever of the two colours is brighter per pixel, so the photo
     behind shows through the scene's dark space while the lit rig, panels
     and particles — always brighter than the photo — stay fully intact on
     top of it. */
  mix-blend-mode: lighten;
}

.hero__canvas.is-ready {
  opacity: 1;
}

/* The workbench photograph behind the hero, on every width.

   It used to be desktop-only — declared inside the 1024px block in
   responsive.css so the file was never even requested below it — on the
   reasoning that the mobile scene has no dedicated half of the frame and the
   flat scrim would swallow the photo anyway. It swallowed it completely: the
   phone hero was flat black. So the image lives here now, and the mobile
   composition is built for a full-width frame instead of borrowed from the
   desktop one.

   Two differences from the desktop treatment below. The mask runs top-to-
   bottom rather than as a radial window, because there is no empty half to
   window into — the photo fades out under the header and the headline and
   carries the lower two-thirds. And it is dimmed a little less than desktop
   (0.68 against 0.6): the mobile vignette above it is a flat 0.66–0.84
   scrim rather than the directional one desktop gets, so the photo would
   disappear under it at the desktop setting — but not much less, because
   here the buttons and the feature list sit directly on top of it.

   Sized like its siblings, since it paints a background rather than hosting
   an <img> — with no content of its own, an unpositioned div collapses to
   zero height and the background would paint into nothing. */
.hero__backdrop {
  position: absolute;
  inset: 0;
  background-position: 60% 58%;
  background-size: cover;
  /* First declaration is the JPEG fallback; browsers that understand
     image-set take the WebP from the second and ignore it. */
  background-image: url("../images/tech-stack.jpg");
  background-image: image-set(
    url("../images/tech-stack.webp") type("image/webp"),
    url("../images/tech-stack.jpg") type("image/jpeg")
  );
  filter: saturate(0.72) brightness(0.68) contrast(1.05);
  -webkit-mask-image: linear-gradient(
    180deg,
    transparent 0%,
    var(--c-void) 34%,
    var(--c-void) 100%
  );
  mask-image: linear-gradient(
    180deg,
    transparent 0%,
    var(--c-void) 34%,
    var(--c-void) 100%
  );
}

/* Painted fallback: visible before WebGL boots, and permanently if it can't.
   Echoes the WebGL scene's own colour split — an orange glow low in frame
   (the platform), a cool blue one upper-right (the holographic panels). */
.hero__fallback {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(
      30rem 22rem at 68% 30%,
      rgb(var(--rgb-hologram) / 0.14),
      transparent 65%
    ),
    radial-gradient(
      38rem 26rem at 50% 96%,
      rgb(var(--rgb-primary) / 0.22),
      transparent 62%
    ),
    radial-gradient(
      50rem 36rem at 50% 100%,
      rgb(var(--rgb-deep) / 0.85),
      transparent 65%
    ),
    var(--c-void);
}

/* Depth grade + film grain sit above the canvas, below the copy. */
/* Mobile-first: below 1024px the copy spans the full width with the scene
   directly behind it, so this is a flat scrim carrying legibility across the
   whole frame. From 1024px the scene gets its own half and the scrim turns
   directional — darkening only the text side. See responsive.css. */
.hero__vignette {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgb(var(--rgb-void) / 0.84) 0%,
    rgb(var(--rgb-void) / 0.66) 38%,
    rgb(var(--rgb-void) / 0.66) 72%,
    rgb(var(--rgb-void) / 0.82) 100%
  );
}

/* Overlay layer above the WebGL scene — desktop only, see responsive.css.
   Painted on top of the vignette so it stays crisp rather than dimmed by it.
   The technology schematics themselves are drawn inside the scene now, so
   the only thing left up here is the badge. */
.hero__panels {
  display: none;
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}

.hero__panel-badge {
  position: absolute;
  bottom: 10%;
  right: 6%;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 12rem;
  padding: var(--sp-3) var(--sp-4);
  text-align: left;
}

.hero__panel-badge-icon {
  width: 1.75rem;
  height: 1.75rem;
  flex: none;
  color: var(--c-primary);
}

.hero__panel-badge-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.hero__panel-badge-text strong {
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--text-primary);
}

.hero__panel-badge-text span {
  font-size: var(--fs-micro);
  color: var(--text-tertiary);
}

.hero__inner {
  position: relative;
  z-index: var(--z-raised);
  width: 100%;
}

/* Badge: a pill rather than the plain dash-prefixed eyebrow used elsewhere —
   the hero is the one place on the page that needs to read as a label you'd
   glance at, not a section kicker. */
.hero__badge {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-6);
  padding: var(--sp-2) var(--sp-5) var(--sp-2) var(--sp-4);
  border-radius: var(--r-full);
  border: 1px solid var(--border-energised);
  background-color: rgb(var(--rgb-primary) / 0.08);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-secondary);
}

.hero__badge-dot {
  width: 6px;
  height: 6px;
  flex: none;
  border-radius: var(--r-full);
  background-color: var(--c-primary);
  box-shadow: var(--glow-sm);
  animation: pulse-glow 2.6s ease-in-out infinite;
}

.hero__title {
  font-size: var(--fs-display);
  font-weight: 500;
  line-height: var(--lh-snug);
  letter-spacing: var(--tracking-mega);
  max-width: 18ch;
}

.hero__title em,
.hero__title-lead {
  font-style: normal;
  font-weight: 300;
  background: var(--gradient-energy);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* The lead-in words on both lines sit a step smaller than the accent they
   set up, so the accent still reads as the point of emphasis. */
.hero__title-lead {
  font-size: 0.86em;
}

/* Each line is masked so the reveal reads as type rising into frame. */
.hero__line {
  display: block;
  overflow: hidden;
}

.hero__line > span {
  display: block;
}

.hero__lead {
  margin-top: var(--sp-6);
  max-width: 44ch;
  font-size: var(--fs-lead);
  color: var(--text-secondary);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-top: var(--sp-7);
}

/* Capability row: icon + two-line label, four across. Replaces a stats bar
   with plain, checkable claims. */
.hero__features {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-6) var(--sp-7);
  margin-top: var(--sp-9);
  list-style: none;
}

.hero__feature {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}

.hero__feature-icon {
  width: 1.5rem;
  height: 1.5rem;
  flex: none;
  color: var(--c-primary);
}

.hero__feature-label {
  font-size: var(--fs-xs);
  line-height: var(--lh-heading);
  color: var(--text-secondary);
  max-width: 9rem;
}

/* --------------------------------------------------------------------------
   5. Origin (about)
   -------------------------------------------------------------------------- */
.origin__grid {
  display: grid;
  gap: var(--sp-9);
  align-items: start;
}

.origin__body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
}

.origin__text {
  color: var(--text-secondary);
  font-size: var(--fs-lead);
  max-width: 46ch;
}

.origin__pillars {
  display: grid;
  gap: var(--sp-4);
  margin-top: var(--sp-2);
  list-style: none;
}

.origin__pillar {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--sp-4);
  align-items: start;
  padding: var(--sp-5);
  border: 1px solid var(--border-hairline);
  border-radius: var(--r-md);
  background-color: rgb(var(--rgb-text) / 0.02);
  transition: border-color var(--dur-base) var(--ease-out),
    background-color var(--dur-base) var(--ease-out);
}

.origin__pillar:hover {
  border-color: var(--border-energised);
  background-color: rgb(var(--rgb-primary) / 0.04);
}

.origin__pillar-icon {
  width: 1.5rem;
  height: 1.5rem;
  color: var(--c-primary);
  flex: none;
  margin-top: 2px;
}

.origin__pillar-title {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: 500;
  margin-bottom: var(--sp-1);
}

.origin__pillar-text {
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

/* --------------------------------------------------------------------------
   5b. About page — why we started, what we do, who we serve, our approach,
   vision & mission, core values
   -------------------------------------------------------------------------- */

/* Why We Started — the robot, shown whole and unframed. It is a cutout with
   its own transparent edges, so nothing here draws a border or a panel
   behind it: a hard rectangle around a cutout reads as a mistake. */
/* The two light pools below deliberately bleed outside the figure's box
   (inset -20% on one side, -16% on the other). On a desktop the figure sits
   in a column with room either side and the spill stays well inside the page.
   On a phone the figure already fills the container, so the same spill landed
   past the right edge of the viewport and gave the whole About page about
   50px of horizontal scroll — the page could be dragged sideways.

   overflow-x: clip rather than hidden: clip does not turn the section into a
   scroll container, so nothing inside it loses position: sticky, and the
   vertical axis stays visible. At desktop widths this clips nothing, because
   the glow never reaches the section's own edges. */
.story {
  overflow-x: clip;
}

.story__figure {
  position: relative;
  max-width: 25rem;
  margin-inline: auto;
}

/* Two pools of light rather than one, placed where the figure is already
   lit — cool behind the tablet and its holographic panels, warm and low
   behind the chest core. The figure's own lighting then continues into the
   page instead of stopping dead at its cut edge. */
.story__figure::before,
.story__figure::after {
  content: "";
  position: absolute;
  z-index: var(--z-below);
  border-radius: 50%;
  pointer-events: none;
}

.story__figure::before {
  inset: 14% -20% 28% 32%;
  background: radial-gradient(
    closest-side,
    rgb(var(--rgb-hologram) / 0.24),
    transparent 72%
  );
}

.story__figure::after {
  inset: 44% 26% 2% -16%;
  background: radial-gradient(
    closest-side,
    rgb(var(--rgb-primary) / 0.18),
    transparent 72%
  );
}

.story__robot {
  display: block;
  width: 100%;
  height: auto;
  filter: drop-shadow(0 26px 44px rgb(var(--rgb-void) / 0.6));
}

/* Why We Started — the mission statement, marked out of the running text.
   Set on a rule in the brand orange rather than in a card: it is one
   sentence, and a panel around it would outweigh the two paragraphs it is
   meant to conclude. */
.story__mission {
  margin-top: var(--sp-7);
  padding-left: var(--sp-5);
  border-left: 2px solid var(--c-primary);
  font-size: var(--fs-lead);
  color: var(--text-primary);
}

/* --- What We Do -----------------------------------------------------------
 * Dense tiles: the drawn equipment plus its name, nothing else. The detail
 * lives on courses.php, which the closing action links to.
 */
.what {
  position: relative;
  overflow: hidden;
}

.what .section-backdrop {
  --backdrop-x: 72%;
  --backdrop-y: 50%;
  --backdrop-opacity: 0.5;
}

.what__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
  gap: var(--sp-4);
  margin-top: var(--sp-9);
  list-style: none;
}

.what-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-5) var(--sp-3);
  text-align: center;
  transition: border-color var(--dur-slow) var(--ease-out),
    transform var(--dur-fast) var(--ease-spring);
}

.what-tile:hover {
  border-color: var(--border-energised);
  transform: translateY(-3px);
}

.what-tile__object {
  width: 3.5rem;
  height: 3.5rem;
}

.what-tile__title {
  font-size: var(--fs-xs);
  font-weight: 500;
  line-height: var(--lh-heading);
}

.what__cta {
  margin-top: var(--sp-8);
}

/* --- Who We Serve ---------------------------------------------------------
 * Seven groups as compact hex badges — the same badge the Industries index
 * uses, so the two "who/where this applies" lists read as a set.
 */
.serve__layout {
  display: flex;
  flex-direction: column;
  gap: var(--sp-9);
}

.serve__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
  gap: var(--sp-4) var(--sp-6);
  margin-top: var(--sp-8);
  list-style: none;
}

.serve-item {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}

.serve-item__hex {
  position: relative;
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  flex: none;
  transition: transform var(--dur-base) var(--ease-spring);
}

.serve-item__hex::before,
.serve-item__hex::after {
  content: "";
  position: absolute;
  clip-path: polygon(50% 0%, 95.5% 25%, 95.5% 75%, 50% 100%, 4.5% 75%, 4.5% 25%);
}

.serve-item__hex::before {
  inset: 0;
  background-color: var(--border-energised);
}

.serve-item__hex::after {
  inset: 1.5px;
  background-color: rgb(var(--rgb-primary) / 0.05);
}

.serve-item:hover .serve-item__hex {
  transform: scale(1.08);
}

.serve-item:hover .serve-item__hex::before {
  background-color: var(--c-primary);
}

.serve-item__icon {
  position: relative;
  z-index: 1;
  width: 1.125rem;
  height: 1.125rem;
  color: var(--c-primary);
}

.serve-item__title {
  font-size: var(--fs-sm);
  font-weight: 500;
}

.serve__note {
  margin-top: var(--sp-8);
  padding-top: var(--sp-6);
  border-top: 1px solid var(--border-hairline);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  max-width: 62ch;
}

/* Feathered on all four sides, the same treatment the homepage's Who We
   Train photo carries, so a photograph never sits in a hard frame. */
.serve__figure {
  position: relative;
  -webkit-mask-image:
    linear-gradient(to right, transparent, var(--c-void) 14%, var(--c-void) 86%, transparent),
    linear-gradient(to bottom, transparent, var(--c-void) 14%, var(--c-void) 86%, transparent);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to right, transparent, var(--c-void) 14%, var(--c-void) 86%, transparent),
    linear-gradient(to bottom, transparent, var(--c-void) 14%, var(--c-void) 86%, transparent);
  mask-composite: intersect;
}

.serve__photo {
  display: block;
  width: 100%;
  height: auto;
  opacity: 0.5;
}

/* --- Our Approach ---------------------------------------------------------
 * Two paragraphs set large against the practice-loop diagram. Text-forward
 * on purpose: this is a statement of method, not a feature list.
 */
.approach__layout {
  display: flex;
  flex-direction: column;
  gap: var(--sp-9);
}

.approach__text {
  margin-top: var(--sp-6);
  font-size: var(--fs-lead);
  color: var(--text-secondary);
  max-width: 54ch;
}

.approach__art {
  max-width: 24rem;
  margin-inline: auto;
}

/* --- Vision & Mission -----------------------------------------------------
 * A lead panel and a checked list rather than a matching pair of cards —
 * the vision is one statement, the mission is five commitments, and equal
 * boxes made one look padded and the other truncated.
 */
.vm__grid {
  display: grid;
  gap: var(--sp-8);
  margin-top: var(--sp-9);
}

.vm__vision {
  padding: var(--sp-7) var(--sp-6);
}

.vm__vision-text {
  margin-top: var(--sp-4);
  font-size: var(--fs-lead);
  color: var(--text-secondary);
}

.vm__list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  margin-top: var(--sp-5);
  list-style: none;
}

.vm__item {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-4);
  padding-bottom: var(--sp-4);
  border-bottom: 1px solid var(--border-hairline);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.vm__item:last-child {
  padding-bottom: 0;
  border-bottom: 0;
}

.vm__item-icon {
  width: 1.125rem;
  height: 1.125rem;
  flex: none;
  margin-top: 0.15em;
  color: var(--c-primary);
}

/* Core Values — icon cards, self-wrapping so five items sit 3+2 on tablet and
   in one row on wide desktop without extra breakpoint rules. */
.values__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: var(--sp-4);
  margin-top: var(--sp-9);
  list-style: none;
}

.value-card {
  padding: var(--sp-6);
  transition: border-color var(--dur-slow) var(--ease-out),
    transform var(--dur-fast) var(--ease-spring);
}

.value-card:hover {
  border-color: var(--border-energised);
  transform: translateY(-3px);
}

.value-card__icon {
  width: 1.75rem;
  height: 1.75rem;
  color: var(--c-primary);
}

.value-card__title {
  margin-top: var(--sp-5);
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: 500;
}

.value-card__text {
  margin-top: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

/* --------------------------------------------------------------------------
   6. Domains (courses as technology objects)
   -------------------------------------------------------------------------- */
.domains__head {
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
  margin-bottom: var(--sp-9);
}

.domains__grid {
  display: grid;
  gap: var(--sp-4);
  list-style: none;
}

.domain-card {
  position: relative;
  display: flex;
  flex-direction: column;
  min-height: 22rem;
  padding: var(--sp-6);
  border-radius: var(--r-lg);
  isolation: isolate;
  transition: border-color var(--dur-slow) var(--ease-out),
    transform var(--dur-slow) var(--ease-out),
    box-shadow var(--dur-slow) var(--ease-out);
}

.domain-card:hover,
.domain-card:focus-within {
  border-color: var(--border-energised);
  transform: translateY(-4px);
  box-shadow: var(--elev-3), var(--glow-lg);
}

/* Cursor-tracked light. --mx/--my are written by animation.js. */
.domain-card::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: var(--z-below);
  background: radial-gradient(
    18rem 18rem at var(--mx, 50%) var(--my, 0%),
    rgb(var(--rgb-primary) / 0.16),
    transparent 70%
  );
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease-out);
  pointer-events: none;
}

.domain-card:hover::after,
.domain-card:focus-within::after {
  opacity: 1;
}

.domain-card__top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-4);
  margin-bottom: var(--sp-5);
}

.domain-card__object {
  width: 6.5rem;
  height: 6.5rem;
  flex: none;
  transition: transform var(--dur-cinematic) var(--ease-out);
}

.domain-card:hover .domain-card__object,
.domain-card:focus-within .domain-card__object {
  transform: translateY(-6px) scale(1.04);
}

.domain-card:hover .to-flow,
.domain-card:focus-within .to-flow {
  opacity: 1;
}

.domain-card:hover .to-fill,
.domain-card:focus-within .to-fill {
  fill-opacity: 0.22;
}

.domain-card:hover .to-detail,
.domain-card:focus-within .to-detail {
  opacity: 0.85;
}

.domain-card__title {
  font-size: var(--fs-h3);
  font-weight: 500;
  margin-bottom: var(--sp-3);
}

/* Stretched link keeps the whole card clickable with one focusable target. */
.domain-card__title a::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

.domain-card__text {
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  margin-bottom: var(--sp-5);
}

.domain-card__foot {
  margin-top: auto;
}

/* --------------------------------------------------------------------------
   6b. Projects page — the Innovation Lab

   The page has nothing on it, and every block here is built so that staying
   empty still reads as deliberate rather than unfinished: a status strip that
   leads with the zero, build areas as rows too wide to be mistaken for
   finished work, and a log panel that shows its own emptiness as a figure.
   -------------------------------------------------------------------------- */

/* --- Lab status strip ------------------------------------------------------
 * Sits tight under the masthead, so it reads as part of it. Mono numerals at
 * heading size: "00 projects published" is the fact the whole page turns on,
 * and it should be legible before any prose is.
 */
.lab-status {
  padding-top: 0;
}

.lab-status__panel {
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
  padding: var(--sp-7) var(--sp-6);
  border-radius: var(--r-lg);
}

.lab-status__label {
  color: var(--text-tertiary);
}

.lab-status__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
  gap: var(--sp-5) var(--sp-6);
  list-style: none;
}

.lab-stat {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding-left: var(--sp-5);
  border-left: 1px solid var(--border-soft);
}

.lab-stat__value {
  font-family: var(--font-mono);
  font-size: var(--fs-h2);
  line-height: var(--lh-snug);
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}

.lab-stat__label {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-tertiary);
}

.lab-status__note {
  font-size: var(--fs-sm);
  color: var(--text-tertiary);
  max-width: 56ch;
}

/* --- 01 What this is -------------------------------------------------------
 * Same composition as the Career page's opening section: heading in its own
 * column from 1024px up, the argument running beside it.
 */
.lab-intro__layout {
  display: flex;
  flex-direction: column;
  gap: var(--sp-8);
}

.lab-intro__body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}

.lab-intro__text {
  font-size: var(--fs-lead);
  color: var(--text-secondary);
  max-width: 54ch;
}

/* The sentence the page is accountable to. On a rule rather than in a panel,
   for the same reason as .career-why__note — a box would outweigh it. */
.lab-intro__note {
  margin-top: var(--sp-3);
  padding-left: var(--sp-5);
  border-left: 2px solid var(--c-primary);
  font-size: var(--fs-lead);
  color: var(--text-primary);
  max-width: 52ch;
}

/* --- 02 Build areas --------------------------------------------------------
 * Rows, not a card grid. Six cards with short titles is precisely how the
 * previous version of this page made six projects that do not exist look like
 * a delivered portfolio; a row that has to carry a paragraph and a stated
 * example cannot be skimmed as a finished piece of work.
 */
.build__list {
  display: flex;
  flex-direction: column;
  margin-top: var(--sp-9);
  list-style: none;
}

.build-row {
  display: flex;
  gap: var(--sp-5);
  padding-block: var(--sp-8);
  border-top: 1px solid var(--border-hairline);
}

.build-row:last-child {
  border-bottom: 1px solid var(--border-hairline);
}

.build-row__lead {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  flex: none;
}

.build-row__object {
  width: 4rem;
  height: 4rem;
  flex: none;
  transition: transform var(--dur-base) var(--ease-spring);
}

.build-row:hover .build-row__object {
  transform: scale(1.07);
}

/* Revealed alongside the object from 1024px up, as on .focus-row — at narrow
   widths the numeral only crowds the art. */
.build-row__num {
  display: none;
}

.build-row__body {
  min-width: 0;
}

.build-row__title {
  font-size: var(--fs-h3);
  font-weight: 500;
}

.build-row__text {
  margin-top: var(--sp-3);
  max-width: 58ch;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
}

/* The conditional example, set apart on a rule so it is never read as a
   description of something already built. */
.build-row__example {
  margin-top: var(--sp-5);
  padding-left: var(--sp-4);
  border-left: 1px solid var(--border-energised);
  max-width: 58ch;
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.build-row__example-label {
  display: block;
  margin-bottom: var(--sp-2);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-energised);
}

/* --- 04 The build log ------------------------------------------------------
 * 03 needs nothing: it reuses .flow from the Learning Methodology unchanged.
 */
.lab-log__panel {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-6);
  margin-top: var(--sp-8);
  padding: var(--sp-8) var(--sp-6);
  border-radius: var(--r-xl);
}

/* The empty state drawn rather than described. A dashed slot with nothing in
   it is the one shape that cannot be misread as a project. */
.lab-log__empty {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  align-self: stretch;
  padding: var(--sp-5);
  border: 1px dashed var(--border-soft);
  border-radius: var(--r-md);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-tertiary);
}

.lab-log__empty-icon {
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  flex: none;
  border-radius: var(--r-full);
  background-color: rgb(var(--rgb-primary) / 0.08);
  border: 1px solid var(--border-energised);
  color: var(--c-primary);
  box-shadow: var(--glow-sm);
}

.lab-log__empty-icon svg {
  width: 1rem;
  height: 1rem;
}

.lab-log__text {
  color: var(--text-secondary);
  max-width: 58ch;
}

.lab-log__spec {
  align-self: stretch;
}

.lab-log__spec-label {
  margin-bottom: var(--sp-4);
  color: var(--text-tertiary);
}

.lab-log__fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: var(--sp-3) var(--sp-6);
  list-style: none;
}

.lab-log__field {
  position: relative;
  padding-left: var(--sp-5);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.lab-log__field::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 5px;
  height: 5px;
  transform: rotate(45deg);
  background-color: var(--c-primary);
}

.lab-log__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
}

/* --------------------------------------------------------------------------
   7. Protocol (Learning Methodology) — a connected flow line, not cards.
   Vertical stack on narrow screens, a true horizontal line from 1024px up.
   -------------------------------------------------------------------------- */
.protocol {
  position: relative;
  overflow: hidden;
}

/* --- Section backdrop -----------------------------------------------------
 * A photograph sitting behind an otherwise text-only section.
 *
 * Position and strength are set per section through the custom properties
 * below, so the same treatment serves more than one section without the
 * rules being written twice.
 *
 * This used to be display:none below 768px, on the reasoning that at phone
 * width the copy already fills the frame. The cost was that half the site's
 * photography simply did not exist on a phone. It shows at every width now:
 * full-bleed across the top of the section instead of the 62%-wide side
 * placement it takes from 768px (see responsive.css), which has nowhere to
 * go when the copy owns the whole column.
 */
.section-backdrop {
  position: absolute;
  top: 0;
  left: 0;
  z-index: var(--z-below);
  width: 100%;
  pointer-events: none;
}

/* <picture> is only a wrapper and carries no box of its own once the media
   reset stops blockifying it, so it is told to behave like the image it
   holds. No height on either it or the image: the backdrop is sized by the
   photograph's own proportions rather than stretched to the section, which
   is also why there is nothing here for object-fit to do. */
.section-backdrop picture {
  display: block;
  width: 100%;
}

/* Narrow screens: the photo runs the full width behind the section heading
   and feathers out on every side, since there is no text-free column to
   offset it into. --backdrop-x / --backdrop-y are deliberately not used —
   they are tuned for the side placement and would push the mask off-frame
   here. The per-section strength is still honoured, just scaled back: the
   copy sits directly on this rather than beside it.

   The per-section values span 0.3–0.7, and simply halving them took the
   weakest (Industries, at 0.3) down to 0.15 — back to invisible, which is
   the bug being fixed. Compressed into 0.26–0.4 instead: the ordering the
   sections were tuned with survives, and nothing falls off the bottom. */
.section-backdrop__img {
  display: block;
  width: 100%;
  opacity: calc(0.16 + var(--backdrop-opacity, 0.6) * 0.34);
  -webkit-mask-image: radial-gradient(
    76% 70% at 50% 44%,
    var(--c-void) 0%,
    transparent 74%
  );
  mask-image: radial-gradient(
    76% 70% at 50% 44%,
    var(--c-void) 0%,
    transparent 74%
  );
}

/* Mirrored, so the one photograph used in two sections reads as a returning
   motif rather than the same picture pasted twice. */
.section-backdrop--flip .section-backdrop__img {
  transform: scaleX(-1);
}

.flow {
  position: relative;
  margin-top: var(--sp-10);
  display: flex;
  flex-direction: column;
  list-style: none;
}

.flow::before {
  content: "";
  position: absolute;
  top: 0.25rem;
  bottom: 0.25rem;
  left: 1.1rem;
  width: 1px;
  background: linear-gradient(
    180deg,
    transparent,
    var(--border-soft) 4%,
    var(--border-soft) 96%,
    transparent
  );
}

.flow__step {
  position: relative;
  padding-left: 3rem;
  padding-bottom: var(--sp-9);
}

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

.flow__marker {
  position: absolute;
  top: 0;
  left: 0;
  z-index: var(--z-raised);
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--r-full);
  background-color: var(--c-void);
  border: 1px solid var(--border-energised);
  box-shadow: var(--glow-sm);
}

.flow__marker-num {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
  color: var(--c-primary);
}

.flow__title {
  font-size: var(--fs-h3);
  font-weight: 500;
  margin-bottom: var(--sp-3);
}

.flow__text {
  color: var(--text-secondary);
  font-size: var(--fs-sm);
  max-width: 40ch;
  margin-bottom: var(--sp-4);
}

.flow__list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  list-style: none;
}

.flow__list li {
  position: relative;
  padding-left: var(--sp-4);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-wide);
  color: var(--text-tertiary);
}

.flow__list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.45em;
  width: 5px;
  height: 5px;
  transform: rotate(45deg);
  background-color: var(--c-primary);
}

/* --------------------------------------------------------------------------
   9. Closing CTA
   -------------------------------------------------------------------------- */
.cta {
  position: relative;
  overflow: hidden;
}

/* Homepage only: the closing pitch sits on a real workbench rather than flat
   void. Done as a background rather than an <img> so the file is only ever
   fetched on the page whose rule matches — the same section renders on five
   other pages. The first declaration is the JPEG fallback; browsers that
   understand image-set take the WebP from the second and ignore it. */
.is-home .cta::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: var(--z-below);
  background-image: linear-gradient(
      180deg,
      var(--c-void) 0%,
      rgb(var(--rgb-void) / 0.62) 42%,
      rgb(var(--rgb-void) / 0.72) 62%,
      var(--c-void) 100%
    ),
    url("../images/tech-stack.jpg");
  background-image: linear-gradient(
      180deg,
      var(--c-void) 0%,
      rgb(var(--rgb-void) / 0.62) 42%,
      rgb(var(--rgb-void) / 0.72) 62%,
      var(--c-void) 100%
    ),
    image-set(
      url("../images/tech-stack.webp") type("image/webp"),
      url("../images/tech-stack.jpg") type("image/jpeg")
    );
  background-size: cover;
  background-position: center;
  pointer-events: none;
}

.cta__panel {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-6);
  padding: var(--sp-9) var(--sp-6);
  text-align: center;
  border-radius: var(--r-xl);
}

.cta__title {
  font-size: var(--fs-h1);
  font-weight: 400;
  line-height: var(--lh-snug);
  letter-spacing: var(--tracking-tight);
  max-width: 18ch;
}

.cta__title strong {
  font-weight: 600;
  color: var(--text-energised);
}

.cta__text {
  color: var(--text-secondary);
  max-width: 48ch;
}

.cta__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-3);
  margin-top: var(--sp-2);
}

.cta__note {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-tertiary);
}

/* --------------------------------------------------------------------------
   10. Page header (interior pages)
   -------------------------------------------------------------------------- */
.page-head {
  position: relative;
  padding-top: calc(var(--nav-h) + var(--section-y-tight));
  padding-bottom: var(--section-y-tight);
  overflow: hidden;
}

/* Same light source as the hero, at lower intensity — interior pages read as
   further inside the same building, not as a different site. */
.page-head::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: var(--z-below);
  background:
    radial-gradient(
      36rem 24rem at 82% 0%,
      rgb(var(--rgb-primary) / 0.16),
      transparent 65%
    ),
    linear-gradient(180deg, rgb(var(--rgb-deep) / 0.5), transparent);
  pointer-events: none;
}

.page-head__title {
  font-size: var(--fs-display);
  font-weight: 500;
  line-height: var(--lh-snug);
  letter-spacing: var(--tracking-mega);
  max-width: 16ch;
  margin-top: var(--sp-5);
}

.page-head__title em {
  font-style: normal;
  font-weight: 300;
  background: var(--gradient-energy);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.page-head__lead {
  margin-top: var(--sp-5);
  max-width: 52ch;
  font-size: var(--fs-lead);
  color: var(--text-secondary);
}

/* --------------------------------------------------------------------------
   11. Contact
   -------------------------------------------------------------------------- */
.contact__grid {
  display: grid;
  gap: var(--sp-9);
  align-items: start;
}

/* Grid items default to min-width:auto, so the column can never shrink
   below its widest indivisible content — and a <select> reports the width
   of its longest option as exactly that. "Corporate or institutional
   training" was therefore setting a floor wider than a phone, pushing the
   whole page sideways. Letting the columns shrink and capping the control
   keeps the option list free to say what it needs to. */
.contact__grid > *,
.contact__row > * {
  min-width: 0;
}

.field__input {
  max-width: 100%;
}

.contact__form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
  padding: var(--sp-6);
  border-radius: var(--r-lg);
}

.contact__row {
  display: grid;
  gap: var(--sp-5);
}

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

.contact__consent {
  font-size: var(--fs-xs);
  color: var(--text-tertiary);
  max-width: 34ch;
}

/* Error summary, shown by contact.js when a send attempt is blocked. Sits
   above the button rather than at the top of the form: it has to be in view
   at the moment the button is pressed, which is where the visitor is looking. */
.contact__summary {
  margin-top: var(--sp-2);
}

.contact__summary[hidden] {
  display: none;
}

/* Connection state. Deliberately quiet when the answer is "encrypted" — a
   padlock is reassurance, not an announcement — and louder when it is not,
   because that is the case someone actually needs to notice. */
.contact__security {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  margin-top: var(--sp-5);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--border-hairline);
  font-size: var(--fs-xs);
  line-height: var(--lh-body);
  color: var(--text-tertiary);
}

.contact__security strong {
  font-weight: 500;
  color: var(--text-secondary);
}

.contact__security code {
  font-family: var(--font-mono);
  font-size: 0.95em;
}

.contact__security-icon {
  width: 1.1rem;
  height: 1.1rem;
  flex: none;
  margin-top: 0.1em;
  color: var(--c-primary);
}

.contact__security--open .contact__security-icon,
.contact__security--open strong {
  color: var(--c-accent);
}

.contact__aside {
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
}

.contact__detail-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  list-style: none;
}

.contact__detail {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--sp-4);
  align-items: start;
  padding-bottom: var(--sp-4);
  border-bottom: 1px solid var(--border-hairline);
}

.contact__detail-icon {
  width: 1.25rem;
  height: 1.25rem;
  color: var(--c-primary);
  margin-top: 3px;
}

.contact__detail-label {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin-bottom: var(--sp-1);
}

.contact__detail-value {
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

/* Honeypot: reachable by scripts, invisible and unreachable for people.
   Kept out of the tab order and hidden from screen readers. */
.contact__trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* --------------------------------------------------------------------------
   12. Courses — the training library
   Twenty areas in six numbered groups, each tile opening a detail modal.
   This replaced a stack of twenty full-width discipline rows: at that
   length the page could not be scanned, and comparing two areas meant
   scrolling past everything between them.
   -------------------------------------------------------------------------- */
.library__note {
  max-width: 52ch;
  font-size: var(--fs-lead);
  color: var(--text-secondary);
}

/* Hidden until courses.js marks it ready — a filter that cannot filter is
   worse than no filter at all. */
.library__filters {
  display: none;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: var(--sp-7);
}

.library__filters.is-ready {
  display: flex;
}

.library-group {
  margin-top: var(--sp-10);
  /* Linked from the homepage and from a modal's close control — clear the
     fixed header when the browser jumps here. */
  scroll-margin-top: calc(var(--nav-h) + var(--sp-6));
}

.library-group__meta {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  margin-bottom: var(--sp-4);
}

.library-group__rule {
  flex: 1;
}

.library-group__title {
  font-size: var(--fs-h2);
  font-weight: 500;
  letter-spacing: var(--tracking-tight);
}

.library-group__lead {
  margin-top: var(--sp-3);
  max-width: 52ch;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
}

.library-group__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: var(--sp-4);
  margin-top: var(--sp-7);
  list-style: none;
}

/* --- Course tile ----------------------------------------------------------
 * An anchor, not a button: it resolves to a real panel id so the page still
 * works with the script blocked. See courses.php.
 */
.course-tile {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  height: 100%;
  padding: var(--sp-5);
  color: inherit;
  text-decoration: none;
  transition: border-color var(--dur-slow) var(--ease-out),
    transform var(--dur-fast) var(--ease-spring);
}

.course-tile:hover,
.course-tile:focus-visible {
  border-color: var(--border-energised);
  transform: translateY(-3px);
}

.course-tile__art {
  display: grid;
  place-items: center;
  width: 3.25rem;
  flex: none;
}

.course-tile__object {
  width: 100%;
  height: auto;
}

/* The objects idle on the homepage and run when the tile is engaged, the
   same rule the programme cards follow. */
.course-tile:hover .to-flow,
.course-tile:focus-visible .to-flow {
  opacity: 1;
}

.course-tile__body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  min-width: 0;
}

.course-tile__title {
  font-size: var(--fs-body);
  font-weight: 500;
  line-height: var(--lh-heading);
}

.course-tile__sub {
  font-size: var(--fs-xs);
  color: var(--text-tertiary);
}

.course-tile__cue {
  margin-left: auto;
  flex: none;
  opacity: 0;
  transform: translateX(-4px);
  transition: opacity var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-out);
}

.course-tile:hover .course-tile__cue,
.course-tile:focus-visible .course-tile__cue {
  opacity: 1;
  transform: none;
}

.course-tile__cue-icon {
  width: 1.125rem;
  height: 1.125rem;
  color: var(--c-primary);
}

/* --- Closing panel -------------------------------------------------------- */
.library-closing__panel {
  padding: var(--sp-8) var(--sp-7);
  text-align: center;
}

.library-closing__title {
  font-size: var(--fs-h2);
  font-weight: 500;
  letter-spacing: var(--tracking-tight);
}

.library-closing__title em {
  font-style: normal;
  color: var(--text-energised);
}

.library-closing__text {
  margin: var(--sp-5) auto var(--sp-7);
  max-width: 60ch;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
}

/* --------------------------------------------------------------------------
   12b. Course detail modal
   Shown by :target with no JavaScript, or by .is-open once courses.js takes
   over the click. display:none until either applies, which is what keeps
   twenty of these out of the accessibility tree on load.
   -------------------------------------------------------------------------- */
.course-modal {
  display: none;
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  padding: var(--sp-5);
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* :target is the no-JavaScript path only, and is switched off the moment
   courses.js takes ownership (it sets .js-course-modals on the root).
   Chromium does not re-evaluate :target after history.replaceState, so a
   panel opened by deep link stayed matched by it even once the hash was
   stripped — the close button removed .is-open and nothing happened,
   because :target was still forcing the panel visible. Scoping it here is
   what makes the two paths mutually exclusive rather than fighting.

   Not keyed off the generic `.js` class: animation.js removes that when
   GSAP fails to load, which would silently hand control back to :target
   while courses.js was still running. */
:root:not(.js-course-modals) .course-modal:target {
  display: grid;
  place-items: center;
  /* A centred grid item that outgrows its track overflows in both
     directions and puts its own top out of reach — the panel is taller
     than the viewport on a phone. `safe` falls back to start alignment
     once it no longer fits, and is declared after the plain value so
     browsers without it keep the old behaviour. */
  place-items: safe center;
}

.course-modal.is-open {
  display: grid;
  place-items: center;
  place-items: safe center;
}

.course-modal__scrim {
  position: fixed;
  inset: 0;
  background-color: var(--surface-scrim);
  cursor: default;
}

.course-modal__panel {
  position: relative;
  width: min(64rem, 100%);
  /* dvh, not vh: on a phone vh is the *large* viewport — the height with
     the address bar retracted — so a vh-capped panel is taller than what
     is actually on screen and its foot sits under the browser chrome.
     The vh line stays as the fallback. */
  max-height: calc(100vh - var(--sp-8));
  max-height: calc(100dvh - var(--sp-8));
  overflow-y: auto;
  overscroll-behavior: contain;
  border-radius: var(--r-xl);
}

.course-modal__close {
  position: absolute;
  top: var(--sp-4);
  right: var(--sp-4);
  z-index: 1;
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  border: 1px solid var(--border-hairline);
  border-radius: var(--r-full);
  background-color: rgb(var(--rgb-void) / 0.6);
  color: var(--text-secondary);
  transition: color var(--dur-base) var(--ease-out),
    border-color var(--dur-base) var(--ease-out);
}

.course-modal__close:hover,
.course-modal__close:focus-visible {
  color: var(--text-primary);
  border-color: var(--border-energised);
}

.course-modal__close-icon {
  width: 1.125rem;
  height: 1.125rem;
}

.course-modal__layout {
  display: grid;
  gap: var(--sp-7);
  padding: var(--sp-7) var(--sp-6);
}

/* The drawn equipment, held on its own ground so it reads as the subject
   rather than as an icon beside the copy. */
.course-modal__art {
  display: grid;
  place-items: center;
  gap: var(--sp-4);
  padding: var(--sp-6);
  border-radius: var(--r-lg);
  background-color: rgb(var(--rgb-text) / 0.03);
  border: 1px solid var(--border-hairline);
}

.course-modal__object {
  width: min(100%, 12rem);
  height: auto;
}

/* Standalone here, not a hover target — the current runs by default. */
.course-modal__object .to-flow {
  opacity: 1;
}

.course-modal__art-caption {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-tertiary);
  text-align: center;
}

.course-modal__title {
  margin-top: var(--sp-3);
  font-size: var(--fs-h2);
  font-weight: 500;
  letter-spacing: var(--tracking-tight);
}

.course-modal__sub {
  margin-top: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--text-tertiary);
}

.course-modal__overview {
  margin-top: var(--sp-5);
  font-size: var(--fs-lead);
  color: var(--text-secondary);
}

.course-modal__label {
  margin-top: var(--sp-7);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border-hairline);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  font-weight: 400;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--c-primary);
}

.course-modal__list {
  display: grid;
  gap: var(--sp-2);
  margin-top: var(--sp-4);
  list-style: none;
}

.course-modal__topic {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.course-modal__topic-icon {
  width: 0.875rem;
  height: 0.875rem;
  flex: none;
  margin-top: 0.35em;
  color: var(--c-primary);
}

.course-modal__text {
  margin-top: var(--sp-4);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.course-modal__label + .chip-set {
  margin-top: var(--sp-4);
}

.course-modal__foot {
  margin-top: var(--sp-8);
}

/* --------------------------------------------------------------------------
   13. In-development state
   -------------------------------------------------------------------------- */
.pending {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-6);
  padding: var(--sp-9) var(--sp-6);
  border-radius: var(--r-xl);
}

.pending__title {
  font-size: var(--fs-h2);
  font-weight: 500;
  max-width: 20ch;
}

.pending__text {
  color: var(--text-secondary);
  max-width: 52ch;
}

.pending__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
}

/* --------------------------------------------------------------------------
   14. Footer
   -------------------------------------------------------------------------- */
.site-footer {
  position: relative;
  padding-top: var(--section-y-tight);
  border-top: 1px solid var(--border-hairline);
  background-color: rgb(var(--rgb-void) / 0.6);
}

.site-footer__grid {
  display: grid;
  gap: var(--sp-8);
  padding-bottom: var(--sp-9);
}

.site-footer__intro {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  max-width: 34ch;
}

.site-footer__text {
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.site-footer__nav-title {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: var(--tracking-widest);
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin-bottom: var(--sp-4);
}

.site-footer__list {
  display: flex;
  flex-direction: column;
  /* Tighter than the padding below suggests: the links carry their own
     vertical padding to reach a tappable height, so the gap only has to
     separate those boxes. */
  gap: var(--sp-1);
  list-style: none;
}

/* inline-block so the padding actually reserves layout height — as a plain
   inline element it would expand the hit area but overlap its neighbours.
   Text alone measured 19px tall, under the 24px minimum target size. */
.site-footer__list a {
  display: inline-block;
  padding-block: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  transition: color var(--dur-base) var(--ease-out);
}

.site-footer__list a:hover,
.site-footer__list a:focus-visible {
  color: var(--text-energised);
}

.site-footer__bar {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  padding-block: var(--sp-5);
  border-top: 1px solid var(--border-hairline);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-tertiary);
}

/* Oversized wordmark bleeding off the bottom edge. */
.site-footer__wordmark {
  display: block;
  width: 100%;
  height: auto;
  color: rgb(var(--rgb-text) / 0.035);
  margin-bottom: -0.6em;
  font-family: var(--font-display);
  font-size: var(--fs-mega);
  font-weight: 600;
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-mega);
  text-align: center;
  user-select: none;
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   15. Editorial rows — shared row used by Who We Train, Why Choose,
   Corporate Training and Career Development. Hairline dividers and big
   typography instead of boxed cards; one component so four homepage
   sections don't each invent their own.

     .info-rows                  wrapper, hairline between children
     .info-rows--numbered        big ghost numeral instead of an icon
     .info-rows--alt             icon/text swap sides on even rows
     .info-row__icon             plain icon, no background box
   -------------------------------------------------------------------------- */
.info-rows {
  display: flex;
  flex-direction: column;
  margin-top: var(--sp-9);
  list-style: none;
}

.info-row {
  display: flex;
  gap: var(--sp-6);
  align-items: flex-start;
  padding-block: var(--sp-7);
  border-top: 1px solid var(--border-hairline);
  transition: background-color var(--dur-slow) var(--ease-out);
}

.info-rows > .info-row:last-child {
  border-bottom: 1px solid var(--border-hairline);
}

.info-row:hover,
.info-row:focus-within {
  background-color: rgb(var(--rgb-primary) / 0.03);
}

.info-row__marker {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 4rem;
}

.info-row__num {
  font-family: var(--font-display);
  font-size: clamp(2rem, 3.6vw, 3rem);
  font-weight: 600;
  line-height: 1;
  letter-spacing: var(--tracking-tight);
  color: transparent;
  -webkit-text-stroke: 1.5px var(--border-energised);
  transition: -webkit-text-stroke-color var(--dur-base) var(--ease-out);
}

.info-row:hover .info-row__num,
.info-row:focus-within .info-row__num {
  -webkit-text-stroke-color: var(--c-primary);
}

.info-row__icon {
  width: 2.25rem;
  height: 2.25rem;
  color: var(--c-primary);
  transition: transform var(--dur-base) var(--ease-spring);
}

.info-row:hover .info-row__icon,
.info-row:focus-within .info-row__icon {
  transform: scale(1.12);
}

.info-row__body {
  flex: 1 1 auto;
  padding-top: var(--sp-1);
}

.info-row__title {
  font-size: var(--fs-h3);
  font-weight: 500;
  margin-bottom: var(--sp-2);
}

.info-row__text {
  color: var(--text-secondary);
  font-size: var(--fs-sm);
  max-width: 60ch;
}

/* Who We Train — stacked on narrow screens; from 1024px the heading moves
   into its own sticky column beside the list, which is what fills the width
   this section used to leave empty. See responsive.css. */
.audience__layout {
  display: flex;
  flex-direction: column;
}

/* All four edges feathered to nothing, so the photo has no visible corners
   or cut sides — it dissolves into the section rather than sitting in a
   frame. Two gradients intersected: each fades one axis, and where they
   overlap at the corners the result is transparent twice over. Masking the
   figure rather than the <img> so the warm wash on top fades with it. */
.audience__figure {
  position: relative;
  margin-top: var(--sp-8);
  -webkit-mask-image:
    linear-gradient(to right, transparent, var(--c-void) 14%, var(--c-void) 86%, transparent),
    linear-gradient(to bottom, transparent, var(--c-void) 14%, var(--c-void) 86%, transparent);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to right, transparent, var(--c-void) 14%, var(--c-void) 86%, transparent),
    linear-gradient(to bottom, transparent, var(--c-void) 14%, var(--c-void) 86%, transparent);
  mask-composite: intersect;
}

.audience__photo {
  display: block;
  width: 100%;
  height: auto;
  /* Held back a touch, so the lit chip doesn't out-glow the copy beside it.
     Light enough that the photo still reads as a photo. */
  opacity: 0.85;
  /* The source is a cool blue macro; this pulls it toward the page's orange
     without needing a second export of the file. */
  filter: saturate(0.85) contrast(1.05);
}

/* Warm wash, so the photo sits in the page's light rather than looking like
   a pasted-in screenshot. No lit top edge any more — a hairline rule is a
   hard edge, which is the one thing the feathered mask above exists to get
   rid of. */
.audience__figure-edge {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(
    160deg,
    rgb(var(--rgb-primary) / 0.16) 0%,
    transparent 45%,
    rgb(var(--rgb-void) / 0.5) 100%
  );
}

/* --------------------------------------------------------------------------
   15b. Programs carousel — Our Training Programs, an arrow-driven horizontal
   row of cards, a "view all" CTA, and a compact capability strip underneath.
   -------------------------------------------------------------------------- */
.programs {
  position: relative;
  margin-top: var(--sp-9);
}

.programs__track {
  display: flex;
  gap: var(--sp-4);
  list-style: none;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scroll-behavior: smooth;
  scrollbar-width: none;
  -ms-overflow-style: none;
  padding-block: var(--sp-2);
  /* Room either side so a snapped-to-start card doesn't sit under the arrow. */
  margin-inline: -1px;
}

.programs__track::-webkit-scrollbar {
  display: none;
}

.program-card {
  position: relative;
  flex: none;
  width: 15.5rem;
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  padding: var(--sp-5);
  border-radius: var(--r-lg);
  scroll-snap-align: start;
  transition: border-color var(--dur-slow) var(--ease-out),
    transform var(--dur-slow) var(--ease-out),
    box-shadow var(--dur-slow) var(--ease-out);
}

.program-card:hover,
.program-card:focus-within {
  border-color: var(--border-energised);
  transform: translateY(-4px);
  box-shadow: var(--elev-3), var(--glow-md);
}

.program-card__icon {
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  border-radius: var(--r-md);
  background-color: rgb(var(--rgb-primary) / 0.06);
  border: 1px solid var(--border-hairline);
  transition: border-color var(--dur-slow) var(--ease-out);
}

.program-card:hover .program-card__icon,
.program-card:focus-within .program-card__icon {
  border-color: var(--border-energised);
}

.program-card__object {
  width: 62%;
  height: auto;
}

.program-card:hover .to-flow,
.program-card:focus-within .to-flow {
  opacity: 1;
  animation: trace-flow 2.4s linear infinite;
}

.program-card__title {
  font-size: var(--fs-body);
  font-weight: 500;
}

/* Stretched link keeps the whole card clickable with one focusable target.
   .program-card is the nearest positioned ancestor, so inset: 0 here covers
   it fully regardless of how tall the card's text makes it. */
.program-card__title a::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

.program-card__text {
  color: var(--text-secondary);
  font-size: var(--fs-xs);
  line-height: var(--lh-body);
}

/* Arrows: hidden on narrow screens (native swipe/scroll-snap handles it
   there) and until JS confirms it can drive them — see responsive.css. */
.programs__arrow {
  display: none;
  position: absolute;
  top: 40%;
  z-index: var(--z-raised);
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  background-color: var(--c-void);
  color: var(--text-primary);
  border: 1px solid var(--border-soft);
  border-radius: var(--r-full);
  cursor: pointer;
  transition: color var(--dur-base) var(--ease-out),
    border-color var(--dur-base) var(--ease-out),
    background-color var(--dur-base) var(--ease-out),
    transform var(--dur-fast) var(--ease-spring);
}

.programs__arrow--prev {
  left: -1.375rem;
}

.programs__arrow--next {
  right: -1.375rem;
}

.programs__arrow-icon {
  width: 1.125rem;
  height: 1.125rem;
}

.programs__arrow--prev .programs__arrow-icon {
  transform: rotate(180deg);
}

.programs__arrow:hover,
.programs__arrow:focus-visible {
  color: var(--text-on-primary);
  border-color: transparent;
  background: var(--gradient-energy);
  box-shadow: var(--glow-sm);
  transform: scale(1.08);
}

.programs__arrow:disabled {
  opacity: 0.3;
  cursor: default;
  pointer-events: none;
  transform: none;
}

.programs__cta {
  display: flex;
  justify-content: center;
  margin-top: var(--sp-8);
}

.programs__features {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-4) var(--sp-5);
  margin-top: var(--sp-9);
  padding: var(--sp-5) var(--sp-4);
  border-radius: var(--r-lg);
  list-style: none;
}

.programs__feature {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 9.25rem;
  font-size: var(--fs-xs);
  color: var(--text-secondary);
}

/* Hexagon badge, drawn with two stacked clip-paths rather than an image —
   an outer hexagon in the border colour, an inner one inset by the stroke
   width, so the gap between them reads as a 1.5px outline. */
.programs__feature-hex {
  position: relative;
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  flex: none;
  transition: transform var(--dur-base) var(--ease-spring);
}

.programs__feature-hex::before,
.programs__feature-hex::after {
  content: "";
  position: absolute;
  clip-path: polygon(50% 0%, 95.5% 25%, 95.5% 75%, 50% 100%, 4.5% 75%, 4.5% 25%);
}

.programs__feature-hex::before {
  inset: 0;
  background-color: var(--border-energised);
}

.programs__feature-hex::after {
  inset: 1.5px;
  background-color: rgb(var(--rgb-primary) / 0.05);
}

.programs__feature:hover .programs__feature-hex,
.programs__feature:focus-within .programs__feature-hex {
  transform: scale(1.08);
}

.programs__feature:hover .programs__feature-hex::before,
.programs__feature:focus-within .programs__feature-hex::before {
  background-color: var(--c-primary);
}

.programs__feature-icon {
  position: relative;
  z-index: 1;
  width: 1.375rem;
  height: 1.375rem;
  flex: none;
  color: var(--c-primary);
}

.programs__feature-label {
  line-height: var(--lh-heading);
}

/* --------------------------------------------------------------------------
   15c. Industries — a scannable index of where the disciplines get used.
   The hex badge is the same one the programme capability strip uses, so the
   two supporting sections read as a set.
   -------------------------------------------------------------------------- */
.industries {
  position: relative;
  overflow: hidden;
}

.industries .section-backdrop {
  --backdrop-opacity: 0.3;
}

.industries__grid {
  display: grid;
  gap: var(--sp-6);
  margin-top: var(--sp-9);
  list-style: none;
}

.industry {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-4);
}

.industry__hex {
  position: relative;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  flex: none;
  transition: transform var(--dur-base) var(--ease-spring);
}

.industry__hex::before,
.industry__hex::after {
  content: "";
  position: absolute;
  clip-path: polygon(50% 0%, 95.5% 25%, 95.5% 75%, 50% 100%, 4.5% 75%, 4.5% 25%);
}

.industry__hex::before {
  inset: 0;
  background-color: var(--border-energised);
}

.industry__hex::after {
  inset: 1.5px;
  background-color: rgb(var(--rgb-primary) / 0.05);
}

.industry:hover .industry__hex {
  transform: scale(1.08);
}

.industry:hover .industry__hex::before {
  background-color: var(--c-primary);
}

.industry__icon {
  position: relative;
  z-index: 1;
  width: 1.25rem;
  height: 1.25rem;
  color: var(--c-primary);
}

.industry__title {
  font-size: var(--fs-body);
  font-weight: 500;
  margin-bottom: var(--sp-1);
}

.industry__text {
  font-size: var(--fs-xs);
  line-height: var(--lh-body);
  color: var(--text-secondary);
}

/* --------------------------------------------------------------------------
   16a. Corporate Training — heading beside the drawn scene, then the
   services across the full width, closing on a call-to-action strip.
   Everything stacks on mobile; the splits arrive at 1024px (responsive.css).
   -------------------------------------------------------------------------- */
/* Clips the backdrop to the section, and holds it a touch stronger than the
   Methodology one — this render is darker and more diffuse to begin with. */
.corporate {
  position: relative;
  overflow: hidden;
}

.corporate .section-backdrop {
  --backdrop-x: 70%;
  --backdrop-y: 45%;
  --backdrop-opacity: 0.7;
}

.corporate__intro {
  display: flex;
  flex-direction: column;
  gap: var(--sp-8);
}

/* Closing strip: the note and the action belong together rather than the
   note trailing off at the foot of the list.

   Stretched rather than flex-start while it is stacked: this label is long
   enough to wrap on a phone, and a shrink-to-fit pill around two centred
   lines reads as an accident. Full width across the panel reads as a
   deliberate block button. The row layout at 1024px sets align-items back to
   centre, which restores the original inline pill. */
.corporate__cta {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--sp-5);
  margin-top: var(--sp-9);
  padding: var(--sp-6);
  border-radius: var(--r-lg);
}

.corporate__note {
  max-width: 52ch;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
}

/* --------------------------------------------------------------------------
   16b. Career Development — the same intro-beside-services composition as
   Corporate Training above, mirrored so the art sits on the left. See
   career-development.php for why this replaced a sticky-art-beside-the-
   full-list layout: a short, landscape illustration left a tall column
   mostly empty once the six-item list ran past it.
   -------------------------------------------------------------------------- */
/* Backdrop held on the left, against Corporate Training's right, and a
   little softer — this render is the brighter of the two. */
.career {
  position: relative;
  overflow: hidden;
}

.career .section-backdrop {
  --backdrop-x: 30%;
  --backdrop-y: 45%;
  --backdrop-opacity: 0.6;
}

/* --------------------------------------------------------------------------
   18. Technologies network — hub-and-spoke diagram
   -------------------------------------------------------------------------- */
/* Clips the backdrop to the section, and holds it lower and further left
   than the Methodology one — the radial diagram and its spokes sit directly
   over this, so it has to stay well behind them. */
.tech-network-section {
  overflow: hidden;
}

.tech-network-section .section-backdrop {
  --backdrop-x: 45%;
  --backdrop-y: 62%;
  --backdrop-opacity: 0.5;
}

.tech-network {
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
  margin-top: var(--sp-9);
}

/* The diagram is now radial at every width, phones included. It used to fall
   back to a wrapped row of ten buttons below 768px, and that fallback was
   broken: the centre mark is absolutely positioned at the middle of the
   stage, so the flowing list ran straight underneath it and the logo sat on
   top of the icons. A smaller circle is both correct and what the section is
   actually for.

   --network-radius is how far each node sits from the centre. It scales with
   the breakpoints below; everything else is derived from it. */
.tech-network__stage {
  --network-radius: 7.75rem;

  position: relative;
  width: 100%;
  max-width: 21.5rem;
  aspect-ratio: 1;
  margin-inline: auto;
}

.tech-network__center {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: var(--z-raised);
  display: grid;
  place-items: center;
  width: 8rem;
  height: 8rem;
  border-radius: var(--r-full);
  text-align: center;
  box-shadow: var(--elev-2), var(--glow-md);
}

.tech-network__center-mark {
  width: 55%;
  max-width: 4rem;
  height: auto;
  filter: drop-shadow(0 0 10px rgb(var(--rgb-primary) / 0.45));
}

/* The orbiting ring. Sized to the stage, so the whole diagram scales from one
   variable. The idle rotation and the counter-rotation that keeps the labels
   upright are in animations.css, where the motion preference can switch them
   off in one place. */
.tech-network__nodes {
  position: absolute;
  inset: 0;
  display: block;
  list-style: none;
}

.tech-network__node {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 0;
  height: 0;
}

.tech-network__spoke {
  display: block;
  position: absolute;
  top: 0;
  left: 0;
  width: var(--network-radius);
  height: 1px;
  background: linear-gradient(90deg, var(--border-soft), transparent);
  transform-origin: 0 50%;
  transform: rotate(var(--angle));
}

/* Rotated out to the radius, then counter-rotated so the button itself is
   never on a slant, then pulled back by half its own size to sit centred on
   the point. */
.tech-network__node-btn {
  position: absolute;
  top: 0;
  left: 0;
  width: 4.75rem;
  padding: var(--sp-2);
  background: none;
  border: none;
  color: var(--text-secondary);
  cursor: pointer;
  transform: rotate(var(--angle)) translateX(var(--network-radius))
    rotate(calc(-1 * var(--angle))) translate(-50%, -50%);
  transition: color var(--dur-base) var(--ease-out);
}

/* Layout only at rest; from 768px up this also carries the counter-orbit
   animation that cancels .tech-network__nodes' own spin (see
   responsive.css), so icon and label stay upright while the node orbits. */
.tech-network__node-spin {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  width: 100%;
}

.tech-network__node-object {
  width: 2rem;
  height: 2rem;
  color: var(--text-tertiary);
  transition: color var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-spring);
}

/* Tight tracking and a small size on phones: ten labels around a 21rem circle
   is the constraint, and "Artificial Intelligence" has to wrap to two lines
   without touching its neighbours. */
.tech-network__node-label {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: var(--tracking-normal);
  text-align: center;
  line-height: var(--lh-heading);
}

.tech-network__node-btn:hover,
.tech-network__node-btn:focus-visible,
.tech-network__node-btn[aria-pressed="true"] {
  color: var(--text-primary);
}

.tech-network__node-btn:hover .tech-network__node-object,
.tech-network__node-btn:focus-visible .tech-network__node-object,
.tech-network__node-btn[aria-pressed="true"] .tech-network__node-object {
  color: var(--c-primary);
  transform: translateY(-3px) scale(1.08);
}

.tech-network__node-btn[aria-pressed="true"] .to-flow {
  opacity: 1;
}

.tech-network__panel {
  padding: var(--sp-7);
  border-radius: var(--r-lg);
}

.tech-network__panel-eyebrow {
  margin-bottom: var(--sp-3);
}

.tech-network__panel-title {
  font-size: var(--fs-h2);
  font-weight: 500;
  letter-spacing: var(--tracking-tight);
}

.tech-network__panel-overview {
  margin-top: var(--sp-3);
  color: var(--text-secondary);
  max-width: 60ch;
}

.tech-network__panel-meta {
  display: grid;
  gap: var(--sp-5);
  margin-top: var(--sp-6);
  padding-top: var(--sp-6);
  border-top: 1px solid var(--border-hairline);
}

.tech-network__panel-meta dt {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin-bottom: var(--sp-2);
}

.tech-network__panel-meta dd {
  color: var(--text-secondary);
  font-size: var(--fs-sm);
}

/* Replayed by network.js on every node change — a quick confirmation that
   the panel actually updated, not a permanent loop. */
.tech-network__panel.is-pulsing {
  animation: panel-pulse var(--dur-slow) var(--ease-out);
}

@keyframes panel-pulse {
  0% {
    box-shadow: 0 0 0 1px rgb(var(--rgb-primary) / 0.4), var(--elev-2);
  }
  100% {
    box-shadow: 0 0 0 0 rgb(var(--rgb-primary) / 0), var(--elev-2);
  }
}

@media (prefers-reduced-motion: reduce) {
  .tech-network__panel.is-pulsing {
    animation: none;
  }
}

/* --------------------------------------------------------------------------
   19. Career Development page

   A service page rather than a course listing, so it is built from wide
   editorial blocks instead of card grids. Eight numbered sections; the
   number is part of how the page reads, which is why it lives in the
   heading component rather than in each section's markup.
   -------------------------------------------------------------------------- */

/* --- Numbered section heading --------------------------------------------- */
.numbered-head {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}

.numbered-head__meta {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}

.numbered-head__rule {
  flex: 1;
}

/* --- Hero ------------------------------------------------------------------
 * Its own masthead rather than the shared page-head: this page opens with
 * two actions, and page-head carries none.
 */
.career-hero__layout {
  display: flex;
  flex-direction: column;
  gap: var(--sp-9);
}

.career-hero__body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}

/* Two short statements stacked, so the second lands as its own line rather
   than wrapping unpredictably behind the first. */
.career-hero__title em {
  display: block;
}

.career-hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  margin-top: var(--sp-3);
}

.career-hero__art {
  max-width: 20rem;
  margin-inline: auto;
}

/* --- 01 Why career development? ------------------------------------------- */
.career-why__layout {
  display: flex;
  flex-direction: column;
  gap: var(--sp-8);
}

.career-why__body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}

.career-why__text {
  font-size: var(--fs-lead);
  color: var(--text-secondary);
  max-width: 54ch;
}

/* The distinction the page rests on. On a rule rather than in a panel — it
   is one sentence, and a box around it would outweigh the argument. */
.career-why__note {
  margin-top: var(--sp-3);
  padding-left: var(--sp-5);
  border-left: 2px solid var(--c-primary);
  font-size: var(--fs-lead);
  color: var(--text-primary);
  max-width: 52ch;
}

/* --- 02 What we focus on ---------------------------------------------------
 * Wide rows, not cards: each area carries a real paragraph plus its focus
 * areas as chips, which a card of that size can only truncate.
 */
.focus__list {
  display: flex;
  flex-direction: column;
  margin-top: var(--sp-9);
  list-style: none;
}

.focus-row {
  display: flex;
  gap: var(--sp-5);
  padding-block: var(--sp-8);
  border-top: 1px solid var(--border-hairline);
}

.focus-row:last-child {
  border-bottom: 1px solid var(--border-hairline);
}

.focus-row__lead {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  flex: none;
}

.focus-row__badge {
  position: relative;
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  flex: none;
  transition: transform var(--dur-base) var(--ease-spring);
}

/* The same hex the Industries index uses, so every "here is a set of
   things" list on the site carries one badge shape. */
.focus-row__badge::before,
.focus-row__badge::after {
  content: "";
  position: absolute;
  clip-path: polygon(50% 0%, 95.5% 25%, 95.5% 75%, 50% 100%, 4.5% 75%, 4.5% 25%);
}

.focus-row__badge::before {
  inset: 0;
  background-color: var(--border-energised);
}

.focus-row__badge::after {
  inset: 1.5px;
  background-color: rgb(var(--rgb-primary) / 0.05);
}

.focus-row:hover .focus-row__badge {
  transform: scale(1.08);
}

.focus-row:hover .focus-row__badge::before {
  background-color: var(--c-primary);
}

.focus-row__icon {
  position: relative;
  z-index: 1;
  width: 1.375rem;
  height: 1.375rem;
  color: var(--c-primary);
}

.focus-row__num {
  display: none;
}

.focus-row__body {
  min-width: 0;
}

.focus-row__title {
  font-size: var(--fs-h3);
  font-weight: 500;
}

.focus-row__text {
  margin-top: var(--sp-3);
  max-width: 58ch;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
}

.focus-row__chips {
  margin-top: var(--sp-5);
}

.focus-row__link {
  margin-top: var(--sp-5);
}

/* --- 03 Who is it for? ----------------------------------------------------- */
.career-serve__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: var(--sp-4);
  margin-top: var(--sp-9);
  list-style: none;
}

.career-serve-card {
  padding: var(--sp-6);
  transition: border-color var(--dur-slow) var(--ease-out),
    transform var(--dur-fast) var(--ease-spring);
}

.career-serve-card:hover {
  border-color: var(--border-energised);
  transform: translateY(-3px);
}

.career-serve-card__icon {
  width: 1.75rem;
  height: 1.75rem;
  color: var(--c-primary);
}

.career-serve-card__title {
  margin-top: var(--sp-5);
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: 500;
}

.career-serve-card__text {
  margin-top: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

/* --- 04 Development journey ------------------------------------------------
 * Reuses .flow from the homepage's Learning Methodology; only the top
 * margin differs, since the numbered heading above it already carries the
 * section's own spacing.
 */
.journey__flow {
  margin-top: var(--sp-9);
}

/* --- 06 Technical + professional -------------------------------------------
 * The page's core argument, and deliberately its loudest block.
 */
.bridge__title {
  font-size: var(--fs-h1);
  font-weight: 500;
  line-height: var(--lh-snug);
  letter-spacing: var(--tracking-tight);
  max-width: 20ch;
}

.bridge__title em {
  display: block;
  font-style: normal;
  color: var(--text-energised);
}

.bridge__grid {
  display: grid;
  gap: var(--sp-5);
  align-items: center;
  margin-top: var(--sp-9);
}

.bridge__col {
  padding: var(--sp-7) var(--sp-6);
  height: 100%;
}

.bridge__col-label {
  color: var(--c-primary);
}

.bridge__items {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: var(--sp-5);
  list-style: none;
}

/* Set as pills rather than a bulleted list: these are labels, not
   sentences, and a column of eight bullets reads far heavier than it is. */
.bridge__item {
  display: inline-flex;
  align-items: center;
  padding: var(--sp-2) var(--sp-4);
  background-color: rgb(var(--rgb-text) / 0.05);
  border: 1px solid var(--border-hairline);
  border-radius: var(--r-full);
  font-size: var(--fs-sm);
  color: var(--text-primary);
}

.bridge__link {
  margin-top: var(--sp-6);
}

/* The join between the columns: pointing down while they are stacked, and
   turned across once they sit side by side (see responsive.css). */
.bridge__join {
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  margin-inline: auto;
  border: 1px solid var(--border-energised);
  border-radius: var(--r-full);
  background-color: rgb(var(--rgb-primary) / 0.08);
}

.bridge__join-icon {
  width: 1.25rem;
  height: 1.25rem;
  color: var(--c-primary);
  transform: rotate(90deg);
}

/* --- 07 For organizations --------------------------------------------------- */
.career-org__panel {
  display: grid;
  gap: var(--sp-8);
  padding: var(--sp-8) var(--sp-7);
  border-radius: var(--r-xl);
}

.career-org__text {
  margin-top: var(--sp-6);
  max-width: 52ch;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
}

.career-org__cta {
  margin-top: var(--sp-7);
}

.career-org__list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  list-style: none;
}

.career-org__item {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding-bottom: var(--sp-4);
  border-bottom: 1px solid var(--border-hairline);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.career-org__item:last-child {
  padding-bottom: 0;
  border-bottom: 0;
}

.career-org__item-icon {
  width: 0.875rem;
  height: 0.875rem;
  flex: none;
  margin-top: 0.35em;
  color: var(--c-primary);
}

/* --- 08 FAQ -----------------------------------------------------------------
 * Native <details>: no JavaScript, keyboard and screen-reader correct for
 * free, and findable by the browser's own in-page search while collapsed.
 */
.faq__list {
  margin-top: var(--sp-9);
  border-top: 1px solid var(--border-hairline);
}

.faq__item {
  border-bottom: 1px solid var(--border-hairline);
}

.faq__question {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-5);
  padding-block: var(--sp-6);
  font-size: var(--fs-h3);
  font-weight: 500;
  cursor: pointer;
  list-style: none;
  transition: color var(--dur-base) var(--ease-out);
}

/* Safari draws its own triangle on a summary unless this is cleared. */
.faq__question::-webkit-details-marker {
  display: none;
}

.faq__question:hover,
.faq__item[open] .faq__question {
  color: var(--text-energised);
}

.faq__caret {
  width: 1.25rem;
  height: 1.25rem;
  flex: none;
  color: var(--c-primary);
  transition: transform var(--dur-base) var(--ease-out);
}

.faq__item[open] .faq__caret {
  transform: rotate(180deg);
}

.faq__answer {
  padding-bottom: var(--sp-6);
  max-width: 68ch;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
}

/* --- Closing call to action -------------------------------------------------
 * This page's own rather than the shared cta section: the generic one asks
 * about training requirements, and this page has spent eight sections on
 * career direction.
 */
.career-cta__panel {
  padding: var(--sp-9) var(--sp-7);
  border-radius: var(--r-xl);
  text-align: center;
}

.career-cta__title {
  font-size: var(--fs-h1);
  font-weight: 500;
  line-height: var(--lh-snug);
  letter-spacing: var(--tracking-tight);
}

.career-cta__title em {
  font-style: normal;
  color: var(--text-energised);
}

.career-cta__text {
  margin: var(--sp-5) auto var(--sp-7);
  max-width: 58ch;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
}

/* --------------------------------------------------------------------------
   20. Contact — direct channels, and the footer's copy of them
   -------------------------------------------------------------------------- */

/* Given before the form, so someone who would rather phone does not have to
   read a form to discover they can. Each is a real mailto:/tel: link. */
/* 19rem rather than 17: at the smaller minimum, three cards squeezed onto one
   row from 768px up and left about 180px for the text, which is not enough
   for a 26-character address. The wider floor makes the third card drop to
   its own row instead of every card being too narrow to read. */
.channels__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
  gap: var(--sp-4);
  list-style: none;
}

.channel {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  height: 100%;
  /* Tightened from --sp-6 inline: the badge, the gap and the hover cue
     already claim ~110px of the card, and the address needs the rest. */
  padding: var(--sp-5);
  color: inherit;
  text-decoration: none;
  transition: border-color var(--dur-slow) var(--ease-out),
    transform var(--dur-fast) var(--ease-spring);
}

.channel:hover,
.channel:focus-visible {
  border-color: var(--border-energised);
  transform: translateY(-3px);
}

/* Same hex badge the Industries index and the career focus rows use, so
   every marked item on the site carries one badge shape. */
.channel__badge {
  position: relative;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  flex: none;
}

.channel__badge::before,
.channel__badge::after {
  content: "";
  position: absolute;
  clip-path: polygon(50% 0%, 95.5% 25%, 95.5% 75%, 50% 100%, 4.5% 75%, 4.5% 25%);
}

.channel__badge::before {
  inset: 0;
  background-color: var(--border-energised);
}

.channel__badge::after {
  inset: 1.5px;
  background-color: rgb(var(--rgb-primary) / 0.05);
}

.channel:hover .channel__badge::before,
.channel:focus-visible .channel__badge::before {
  background-color: var(--c-primary);
}

.channel__icon {
  position: relative;
  z-index: 1;
  width: 1.25rem;
  height: 1.25rem;
  color: var(--c-primary);
}

.channel__body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  min-width: 0;
}

.channel__label {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-tertiary);
}

/* Long addresses must wrap rather than push the card wide — but `anywhere`
   alone broke service.nexistek@gmail.com after the dot, leaving a line
   reading "com" under it, which looks like a truncated address rather than a
   wrapped one. Shrinking the type by a step on narrow screens fits both
   addresses on one line; the wrap stays as the fallback for anything longer
   still. */
.channel__value {
  font-size: 0.875rem;
  font-weight: 500;
  overflow-wrap: anywhere;
}

/* Hidden on the narrowest phones. It is a hover affordance, and a device that
   cannot hover was reserving 34px for it — which was the difference between
   service.nexistek@gmail.com sitting on one line and breaking after the dot.
   Restored at 30rem in responsive.css, where there is room for both. */
.channel__cue {
  display: none;
  margin-left: auto;
  flex: none;
  opacity: 0;
  transform: translateX(-4px);
  transition: opacity var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-out);
}

.channel:hover .channel__cue,
.channel:focus-visible .channel__cue {
  opacity: 1;
  transform: none;
}

.channel__cue-icon {
  width: 1.125rem;
  height: 1.125rem;
  color: var(--c-primary);
}

/* --- Form additions -------------------------------------------------------- */
.contact__aside-title {
  font-size: var(--fs-h3);
  font-weight: 500;
  margin-bottom: var(--sp-6);
}

/* Marks a field as optional without the weight of a required asterisk. */
.field__optional {
  margin-left: var(--sp-2);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-tertiary);
}

/* --- Footer channels ------------------------------------------------------- */
.site-footer__contact {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  margin-top: var(--sp-6);
  list-style: none;
}

.site-footer__contact-link {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  overflow-wrap: anywhere;
  transition: color var(--dur-base) var(--ease-out);
}

.site-footer__contact-link:hover,
.site-footer__contact-link:focus-visible {
  color: var(--text-primary);
}

.site-footer__contact-icon {
  width: 1rem;
  height: 1rem;
  flex: none;
  color: var(--c-primary);
}

/* --- Follow links -----------------------------------------------------------
 * Bordered pills rather than bare icons. Two accounts is too few for a row of
 * unlabelled glyphs to be worth deciphering, and naming them lets someone see
 * it is a WhatsApp *channel* — a broadcast to read, not another chat button
 * like the floating one.
 */
.site-footer__follow {
  margin-top: var(--sp-7);
}

.site-footer__follow-label {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin-bottom: var(--sp-4);
}

.site-footer__social {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  list-style: none;
}

/* Each platform carries its own colour. --brand is the official one, used for
   the border and the wash; --brand-ink is what the icon and the label are
   actually painted in.

   Those are the same value for WhatsApp, and deliberately not for LinkedIn:
   official LinkedIn blue (#0A66C2) against this near-black background gives
   about 3.5:1, which fails WCAG AA for text this size and reads as muddy
   next to the green. #70B5F9 is LinkedIn's own blue for dark interfaces —
   still their colour, and it clears 9:1. */
.social-link--whatsapp {
  --brand: #25d366;
  --brand-ink: #25d366;
  --brand-rgb: 37 211 102;
}

.social-link--linkedin {
  --brand: #0a66c2;
  --brand-ink: #70b5f9;
  --brand-rgb: 10 102 194;
}

.social-link {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-5);
  border: 1px solid rgb(var(--brand-rgb, var(--rgb-primary)) / 0.35);
  border-radius: var(--r-full);
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--brand-ink, var(--text-secondary));
  background-color: rgb(var(--brand-rgb, var(--rgb-primary)) / 0.06);
  transition: border-color var(--dur-base) var(--ease-out),
    background-color var(--dur-base) var(--ease-out),
    box-shadow var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-spring);
}

.social-link:hover,
.social-link:focus-visible {
  transform: translateY(-2px);
  border-color: var(--brand, var(--c-primary));
  background-color: rgb(var(--brand-rgb, var(--rgb-primary)) / 0.14);
  box-shadow: 0 0 22px rgb(var(--brand-rgb, var(--rgb-primary)) / 0.3);
}

/* Large enough to read as the platform's mark rather than a generic glyph —
   the logo is doing the recognising here, so it gets the room to. */
.social-link__icon {
  width: 1.5rem;
  height: 1.5rem;
  flex: none;
  color: var(--brand-ink, var(--c-primary));
}

/* --------------------------------------------------------------------------
   21. Journal — the post listing, and a post's own typography

   The reading column is the point here. Everything else on the site is
   composed; an article just has to be comfortable to read for ten minutes,
   which means one measure, generous leading, and headings that separate
   without shouting.
   -------------------------------------------------------------------------- */

/* --- Topic filter ---------------------------------------------------------- */
.journal__filter {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-bottom: var(--sp-9);
}

.journal__chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  text-decoration: none;
  transition: color var(--dur-base) var(--ease-out),
    border-color var(--dur-base) var(--ease-out),
    background-color var(--dur-base) var(--ease-out);
}

.journal__chip:hover,
.journal__chip:focus-visible {
  color: var(--text-primary);
  border-color: var(--border-energised);
}

.journal__chip.is-active {
  color: var(--text-on-primary);
  background-color: var(--c-primary);
  border-color: transparent;
}

.journal__count {
  font-variant-numeric: tabular-nums;
  opacity: 0.6;
}

/* A topic with nothing published yet. Still offered, still clickable, but it
   should not compete with the ones that have something to read. */
.journal__chip.is-empty {
  opacity: 0.55;
}

.journal__chip.is-empty:hover,
.journal__chip.is-empty:focus-visible {
  opacity: 1;
}

/* The message the empty ones lead to. */
.journal__none {
  padding: var(--sp-8);
  border: 1px dashed var(--border-soft);
  border-radius: var(--r-md);
  text-align: center;
}

/* --- Listing --------------------------------------------------------------- */
.journal__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr));
  gap: var(--sp-6);
  list-style: none;
}

.journal-card {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-radius: var(--r-lg);
  transition: transform var(--dur-base) var(--ease-spring),
    border-color var(--dur-base) var(--ease-out);
}

.journal-card:hover,
.journal-card:focus-within {
  transform: translateY(-4px);
  border-color: var(--border-energised);
}

.journal-card__media {
  display: block;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background-color: rgb(var(--rgb-text) / 0.04);
}

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

.journal-card:hover .journal-card__media img {
  transform: scale(1.04);
}

.journal-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-6);
  flex: 1;
}

.journal-card__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-tertiary);
}

.journal-card__topic {
  color: var(--text-energised);
}

.journal-card__title {
  font-size: var(--fs-h3);
  font-weight: 500;
  line-height: var(--lh-heading);
}

/* Stretched link: the whole card is clickable, but there is still exactly one
   thing in the tab order — the same treatment the discipline cards use. */
.journal-card__title a::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

.journal-card__title a {
  color: inherit;
}

.journal-card:hover .journal-card__title a,
.journal-card:focus-within .journal-card__title a {
  color: var(--text-energised);
}

.journal-card__excerpt {
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.journal-card__cue {
  margin-top: auto;
  padding-top: var(--sp-3);
  font-size: var(--fs-xs);
  color: var(--c-primary);
}

.journal--more {
  border-top: 1px solid var(--border-hairline);
}

.journal__more-title {
  margin-bottom: var(--sp-8);
}

/* --- A post ---------------------------------------------------------------- */
/* The article is one centred column.
 *
 * It used to inherit the full 84rem container: the heading and cover ran the
 * whole width while the body was capped at 68ch, so the text sat against
 * roughly 400px of empty margin on the right and looked misaligned rather
 * than composed. Narrowing the container instead means the heading, the
 * cover, the body and the footer all share one measure and one edge.
 */
.post .container {
  max-width: 58rem;
}

.post__head {
  padding-bottom: var(--sp-6);
}

.post__topic {
  color: var(--text-energised);
}

/* --fs-display is the masthead size — 96px at this width, which is right for
   two or three words and far too loud for a sentence-length article title.
   This tops out at 52px, and the column above decides where it wraps. */
.post__title {
  font-size: clamp(1.875rem, 4.2vw, 3.25rem);
  letter-spacing: var(--tracking-tight);
  max-width: none;
}

.post__byline {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  margin-top: var(--sp-5);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-wide);
  color: var(--text-tertiary);
}

.post__cover {
  margin-bottom: var(--sp-9);
  border-radius: var(--r-lg);
  overflow: hidden;
  border: 1px solid var(--border-hairline);
}

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

/* The column above already sets the measure, so these just fill it — a second
   max-width here is what pushed the body off-centre inside the wider one. */
.post__body {
  max-width: none;
}

/* The contents list in flow: a card above the article, which is what it is on
   a phone, a tablet and a laptop below the width where a margin rail fits. */
.post__reading {
  position: relative;
}

/* --- Article contents ------------------------------------------------------
 * The table of contents on a Journal post, between the cover and the body.
 *
 * A <details>, so the collapse works with no JavaScript. It ships open and
 * toc.js closes it below the breakpoint — a reader on a phone should not have
 * to scroll past eight links to reach the first paragraph, and a reader with
 * scripts blocked should still get the list.
 *
 * Numbering is a CSS counter over the top-level entries only: a sub-heading
 * belongs to the section above it rather than taking its own place in the
 * running order, so it gets a rule instead of a number.
 * -------------------------------------------------------------------------- */
.toc {
  counter-reset: toc;
  margin-bottom: var(--sp-9);
  border: 1px solid var(--border-hairline);
  border-radius: var(--r-lg);
  background-color: rgb(var(--rgb-text) / 0.02);
  overflow: hidden;
}

.toc__summary {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-secondary);
  transition: color var(--dur-base) var(--ease-out),
    background-color var(--dur-base) var(--ease-out);

  /* The default triangle is replaced by the chevron below, which can be
     positioned and rotated with the rest of the design. */
  list-style: none;
}

.toc__summary::-webkit-details-marker {
  display: none;
}

.toc__summary:hover,
.toc__summary:focus-visible {
  color: var(--text-primary);
  background-color: rgb(var(--rgb-text) / 0.03);
}

.toc__label {
  color: var(--text-energised);
}

/* Pushed to the right, and the chevron after it — so the row reads
   "Contents … 7 sections ⌄" at every width. */
.toc__meta {
  margin-left: auto;
  color: var(--text-tertiary);
  text-transform: none;
}

.toc__chevron {
  width: 1rem;
  height: 1rem;
  flex: none;
  color: var(--text-tertiary);
  transition: transform var(--dur-base) var(--ease-out);
}

.toc[open] .toc__chevron {
  transform: rotate(180deg);
}

.toc__nav {
  padding: 0 var(--sp-5) var(--sp-5);
  border-top: 1px solid var(--border-hairline);
}

.toc__list {
  list-style: none;
  margin-top: var(--sp-4);
}

.toc__item {
  position: relative;
}

.toc__link {
  display: flex;
  gap: var(--sp-3);
  padding: var(--sp-2) 0;
  font-size: var(--fs-sm);
  line-height: var(--lh-body);
  color: var(--text-secondary);
  transition: color var(--dur-base) var(--ease-out);
}

/* Top-level entries carry the running number. */
.toc__item--l2 {
  counter-increment: toc;
}

.toc__item--l2 > .toc__link::before {
  content: counter(toc, decimal-leading-zero);
  flex: none;
  min-width: 1.75rem;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-wide);
  color: var(--text-tertiary);
  transition: color var(--dur-base) var(--ease-out);
}

/* A sub-heading is part of the section above it: indented, and given a rule
   where the number would be rather than a number of its own. */
.toc__item--l3 > .toc__link,
.toc__item--l4 > .toc__link,
.toc__item--l5 > .toc__link {
  padding-left: var(--sp-6);
  font-size: var(--fs-xs);
  color: var(--text-tertiary);
}

.toc__item--l3 > .toc__link::before,
.toc__item--l4 > .toc__link::before,
.toc__item--l5 > .toc__link::before {
  content: "";
  flex: none;
  align-self: center;
  width: 0.75rem;
  height: 1px;
  background-color: var(--border-hairline);
  transition: background-color var(--dur-base) var(--ease-out);
}

.toc__item--l4 > .toc__link,
.toc__item--l5 > .toc__link {
  padding-left: var(--sp-8);
}

.toc__link:hover,
.toc__link:focus-visible {
  color: var(--text-primary);
}

.toc__link:hover::before,
.toc__link:focus-visible::before {
  color: var(--text-energised);
}

/* The section being read. Set by toc.js; the whole block degrades to a plain
   list of links without it. */
.toc__link.is-current {
  color: var(--text-energised);
}

.toc__link.is-current::before {
  color: var(--text-energised);
}

/* Only the sub-heading rule is a painted shape — colouring the ::before of a
   numbered entry filled the number's whole box with a solid block and hid the
   number inside it. */
.toc__item--l3 > .toc__link.is-current::before,
.toc__item--l4 > .toc__link.is-current::before,
.toc__item--l5 > .toc__link.is-current::before {
  background-color: var(--text-energised);
}

.toc__text {
  min-width: 0;
}

/* --- Share row -------------------------------------------------------------
 * Sits between the article and the author block. Built from the same
 * .social-link component the footer uses, so a share button and a follow
 * button are visibly the same kind of object and the brand colours are
 * defined once — see the --brand tokens further up.
 *
 * Slightly tighter than the footer's version: five of them sit in a reading
 * column rather than three in a wide footer, and at full size they wrapped to
 * three lines on a phone.
 */
.post__share {
  max-width: none;
  margin-top: var(--sp-9);
  padding-top: var(--sp-6);
  border-top: 1px solid var(--border-hairline);
}

.post__share-label {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin-bottom: var(--sp-4);
}

.post__share-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  list-style: none;
}

.post__share-list .social-link {
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4);
  font-size: var(--fs-xs);
  /* A <button> does not inherit the font the way an <a> does. */
  font-family: var(--font-body);
  cursor: pointer;
}

.post__share-list .social-link__icon {
  width: 1.125rem;
  height: 1.125rem;
}

/* Copy link has no brand of its own, so it takes the site's. */
.social-link--link {
  --brand: var(--c-primary);
  --brand-ink: var(--text-secondary);
  --brand-rgb: var(--rgb-primary);
}

/* Confirmation. Green rather than orange because it reports a result rather
   than offering an action, and the label has already changed to say so. */
.social-link--link.is-copied {
  --brand: var(--c-circuit);
  --brand-ink: var(--c-circuit);
  --brand-rgb: var(--rgb-circuit);
}

.social-link--x {
  --brand: #ffffff;
  --brand-ink: #e7e9ea;
  --brand-rgb: 231 233 234;
}

/* Facebook's #1877F2 is 4.1:1 on this near-black — under AA for text this
   size. #8AB4F8 is the same hue lifted to clear 8:1, the same adjustment the
   LinkedIn block above makes and for the same reason. */
.social-link--facebook {
  --brand: #1877f2;
  --brand-ink: #8ab4f8;
  --brand-rgb: 24 119 242;
}

/* Pinterest red is 4.2:1 here, so the label takes a lighter tint of the same
   hue at 6.1:1. Pinterest publishes no dark-interface red of its own, unlike
   LinkedIn, so this one is picked rather than borrowed — kept red rather than
   drifting pink, which is what happens if you only chase the contrast number. */
.social-link--pinterest {
  --brand: #e60023;
  --brand-ink: #ff4d5e;
  --brand-rgb: 230 0 35;
}

.post__foot {
  max-width: none;
  margin-top: var(--sp-7);
  padding-top: var(--sp-7);
  border-top: 1px solid var(--border-hairline);
}

/* Attribution block. A name on its own line under a rule read as a
   footnote; giving it a mark and a sentence of context makes it read as
   someone standing behind the article, which is the point of a byline. */
.post__author {
  display: flex;
  gap: var(--sp-5);
  margin-bottom: var(--sp-7);
}

.post__author-badge {
  display: grid;
  place-items: center;
  width: 3.5rem;
  height: 3.5rem;
  flex: none;
  border-radius: var(--r-full);
  border: 1px solid var(--border-energised);
  background-color: rgb(var(--rgb-primary) / 0.06);
  box-shadow: var(--glow-sm);
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: 500;
  color: var(--c-primary);
  overflow: hidden;
}

.post__author-badge img {
  width: 55%;
  height: auto;
}

.post__author-body {
  min-width: 0;
}

.post__author-name {
  font-family: var(--font-display);
  font-size: var(--fs-lead);
  font-weight: 500;
  color: var(--text-primary);
}

.post__author-org {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: 400;
  color: var(--text-tertiary);
}

.post__author-line {
  margin-top: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

/* Where a reader goes to check who wrote this. Two destinations, wrapping
   rather than shrinking — on a phone they stack, and neither is ever a
   half-line of underlined text. */
.post__author-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-5);
  margin-top: var(--sp-3);
  font-size: var(--fs-sm);
}

.post__author-icon {
  width: 1rem;
  height: 1rem;
  flex: none;
}

.post__author-ask {
  margin-top: var(--sp-3);
  padding-left: var(--sp-4);
  border-left: 2px solid var(--c-primary);
  font-size: var(--fs-sm);
  color: var(--text-primary);
}

.post__foot-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
}

/* --- Prose ----------------------------------------------------------------
 * Everything the Markdown renderer can emit. Spacing is set on the elements
 * themselves rather than with a universal margin rule, so a heading following
 * a list gets the right gap without a stack of adjacent-sibling selectors.
 */
.prose {
  font-size: var(--fs-lead);
  line-height: var(--lh-body);
  color: var(--text-secondary);
}

.prose__text {
  margin-bottom: var(--sp-6);
}

.prose__heading {
  margin-top: var(--sp-9);
  margin-bottom: var(--sp-4);
  font-family: var(--font-display);
  font-weight: 500;
  line-height: var(--lh-heading);
  color: var(--text-primary);
}

/* Sized between the body and the post title rather than reusing --fs-h2. The
   page's h1 is already very large; a section heading at the same scale would
   compete with it, and at --fs-h3 it was barely bigger than the paragraphs. */
h2.prose__heading {
  font-size: clamp(1.5rem, 2.6vw, 2rem);
}

h3.prose__heading {
  font-size: var(--fs-h3);
}

h4.prose__heading,
h5.prose__heading {
  font-size: var(--fs-lead);
}

.prose > .prose__heading:first-child {
  margin-top: 0;
}

.prose__list {
  margin-bottom: var(--sp-6);
  padding-left: var(--sp-6);
}

.prose__list li {
  margin-bottom: var(--sp-2);
}

.prose__list--ordered {
  list-style: decimal;
}

.prose__list li::marker {
  color: var(--c-primary);
}

.prose__quote {
  margin: var(--sp-8) 0;
  padding-left: var(--sp-6);
  border-left: 2px solid var(--c-primary);
  color: var(--text-primary);
}

.prose__code {
  margin-bottom: var(--sp-6);
  padding: var(--sp-5);
  border: 1px solid var(--border-hairline);
  border-radius: var(--r-md);
  background-color: rgb(var(--rgb-void) / 0.6);
  /* Long lines scroll inside the block instead of widening the page. */
  overflow-x: auto;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  line-height: 1.7;
  color: var(--text-secondary);
}

.prose__inline-code {
  padding: 0.15em 0.4em;
  border-radius: var(--r-xs);
  background-color: rgb(var(--rgb-text) / 0.08);
  font-family: var(--font-mono);
  font-size: 0.875em;
  color: var(--text-primary);
}

.prose__link {
  color: var(--text-energised);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  text-decoration-thickness: 1px;
}

.prose__link:hover,
.prose__link:focus-visible {
  color: var(--c-primary);
}

.prose__figure {
  margin: var(--sp-8) 0;
}

.prose__figure img,
.prose__inline-img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--r-md);
  border: 1px solid var(--border-hairline);
}

.prose__rule {
  margin: var(--sp-9) 0;
  border: 0;
  height: 1px;
  background-color: var(--border-hairline);
}

.prose strong {
  color: var(--text-primary);
  font-weight: 600;
}

/* --------------------------------------------------------------------------
   22. Legal and 404

   Both reuse .prose, so an article and a policy read the same way. Only the
   measure and two small pieces of furniture are new.
   -------------------------------------------------------------------------- */
.legal {
  max-width: 68ch;
}

.legal__updated {
  margin-bottom: var(--sp-8);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-tertiary);
}

.legal__note {
  margin-top: var(--sp-9);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--border-hairline);
  font-size: var(--fs-sm);
  color: var(--text-tertiary);
}

.notfound__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-6);
  list-style: none;
}
