/* ==========================================================================
   Bakaluba Engineering Consults
   Design system: "Datum"

   The site is drawn, not decorated. It borrows the discipline of engineering
   documentation: measured rules, dimension lines, tabular figures, schedules
   instead of feature cards, and photographs presented as captioned plates.
   It does not fake document apparatus and it never presents invented data.

   Brand colours are taken from the company logo: green #1E5B38, orange #F08820.
   Orange never carries text on a light ground. It is a graphic colour only.

   Sections
     01  Fonts
     02  Tokens
     03  Reset and base
     04  Typography
     05  Layout
     06  Section index and dimension rules
     07  Header and navigation
     08  Buttons and links
     09  Plates, the photograph treatment
     10  Cards
     11  Schedules and specification tables
     12  Filters
     13  Forms
     14  Sizing helper
     15  Quotes
     16  Footer
     17  Reveal
     18  Utilities
     19  Print
   ========================================================================== */

/* -- 01  Fonts ----------------------------------------------------------- */
/* Archivo and IBM Plex Mono, SIL Open Font License 1.1, self hosted as Latin
   subsets. Nothing is fetched from a third party host. */

@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 400 700;
  font-stretch: 100%;
  font-display: swap;
  src: url(../fonts/archivo-400-700.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(../fonts/ibm-plex-mono-400.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(../fonts/ibm-plex-mono-500.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* -- 02  Tokens ---------------------------------------------------------- */

:root {
  /* surfaces */
  --paper: #FBFAF6;
  --paper-sunk: #F1EFE7;
  --paper-raise: #FFFFFF;

  /* text */
  --ink: #131714;
  --ink-2: #3B443E;
  --ink-3: #5A645D;

  /* rules. --rule is decorative, --rule-mid draws boxes, --rule-strong measures */
  --rule: #D3D0C4;
  --rule-mid: #AEAB9C;
  --rule-strong: #6F6D61;

  /* brand */
  --green: #1E5B38;
  --green-deep: #164229;
  --green-wash: #E6EEE9;
  --on-green: #FFFFFF;
  --surface-brand: #1E5B38;
  --on-brand: #FFFFFF;

  --orange: #F08820;
  --orange-ink: #8A4B08;
  --orange-wash: #FCEFDF;

  --focus: #8A4B08;
  --shadow: none;

  /* type scale */
  --step--2: clamp(0.6875rem, 0.665rem + 0.11vw, 0.75rem);
  --step--1: clamp(0.8125rem, 0.79rem + 0.11vw, 0.875rem);
  --step-0: clamp(1rem, 0.972rem + 0.14vw, 1.0625rem);
  --step-1: clamp(1.1875rem, 1.12rem + 0.34vw, 1.375rem);
  --step-2: clamp(1.4375rem, 1.31rem + 0.63vw, 1.875rem);
  --step-3: clamp(1.75rem, 1.5rem + 1.25vw, 2.625rem);
  --step-4: clamp(2.125rem, 1.6rem + 2.62vw, 3.75rem);
  --step-5: clamp(2.5rem, 1.75rem + 3.75vw, 4.5rem);

  /* space */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 24px;
  --s-6: 32px;
  --s-7: 48px;
  --s-8: 64px;
  --s-9: 96px;
  --s-10: 128px;

  --gutter: clamp(20px, 4.5vw, 56px);
  --content: 1200px;
  --wide: 1400px;
  --prose: 68ch;
  --section-y: clamp(56px, 8vw, 116px);

  --radius: 0px;

  /* Font stacks as tokens, so a rule can reference one without repeating it. */
  --sans: 'Archivo', 'Segoe UI', Roboto, system-ui, sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, 'Cascadia Mono', Consolas, monospace;
  --ease: cubic-bezier(.22, .61, .36, 1);
  --ease-reveal: cubic-bezier(.22, 1, .36, 1);
}

@media (prefers-color-scheme: dark) {
  :root {
    --paper: #101512;
    --paper-sunk: #0A0E0C;
    --paper-raise: #18201C;

    --ink: #E9EDE8;
    --ink-2: #B5BEB7;
    --ink-3: #8E988F;

    --rule: #2A342E;
    --rule-mid: #3C4740;
    --rule-strong: #727E76;

    --green: #6FD79C;
    --green-deep: #8FE3B4;
    --green-wash: #16281E;
    --on-green: #0A0E0C;
    --surface-brand: #16281E;
    --on-brand: #E9EDE8;

    --orange: #F7A24A;
    --orange-ink: #F7A24A;
    --orange-wash: #241705;

    --focus: #F7A24A;
    --shadow: none;

    color-scheme: dark;
  }
}

/* -- 03  Reset and base --------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 96px;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: 'Archivo', 'Segoe UI', Roboto, system-ui, sans-serif;
  font-size: var(--step-0);
  line-height: 1.6;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-wrap: break-word;
}

html.nav-open, html.nav-open body { overflow: hidden; }

img, svg, video { max-width: 100%; height: auto; display: block; }
img { background: var(--paper-sunk); }

ul, ol { margin: 0; padding: 0; }
li { list-style: none; }

figure, blockquote, p, h1, h2, h3, h4, dl, dd { margin: 0; }

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

table { border-collapse: collapse; width: 100%; }

hr {
  border: 0;
  border-top: 1px solid var(--rule);
  margin: var(--s-7) 0;
}

::selection { background: var(--green); color: var(--on-green); }

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

.skip-link {
  position: absolute;
  left: var(--s-4);
  top: -100px;
  z-index: 200;
  background: var(--green);
  color: var(--on-green);
  padding: var(--s-3) var(--s-5);
  font-weight: 600;
  transition: top .18s var(--ease);
}
.skip-link:focus { top: var(--s-4); }

/* -- 04  Typography ------------------------------------------------------- */

h1, h2, h3, h4 {
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.018em;
  text-wrap: balance;
}

h1 { font-size: var(--step-4); }
h2 { font-size: var(--step-3); }
h3 { font-size: var(--step-2); }
h4 { font-size: var(--step-1); letter-spacing: -0.01em; line-height: 1.25; }

p { text-wrap: pretty; }

.lead {
  font-size: var(--step-1);
  line-height: 1.5;
  color: var(--ink-2);
  max-width: 58ch;
}

.prose { max-width: var(--prose); }
.prose > * + * { margin-top: var(--s-5); }
.prose h2 { margin-top: var(--s-8); }
.prose h3 { margin-top: var(--s-7); }
.prose a { color: var(--green); text-decoration-thickness: 1px; text-underline-offset: 3px; }

/* Mono is reserved for quantities, units, model numbers, codes and micro labels. */
.mono, .label, .figure-value, td.num, th.num {
  font-family: 'IBM Plex Mono', ui-monospace, 'Cascadia Mono', Consolas, monospace;
  font-variant-numeric: tabular-nums;
}

.label {
  font-size: var(--step--2);
  font-weight: 500;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
  display: inline-block;
}

.label--brand { color: var(--orange-ink); }

.small { font-size: var(--step--1); color: var(--ink-3); }

/* -- 05  Layout ----------------------------------------------------------- */

.wrap {
  width: min(100% - var(--gutter) * 2, var(--content));
  margin-inline: auto;
}
.wrap--wide { width: min(100% - var(--gutter) * 2, var(--wide)); }

.section { padding-block: var(--section-y); position: relative; }
.section--sunk { background: var(--paper-sunk); }
.section--brand { background: var(--surface-brand); color: var(--on-brand); }
.section--tight { padding-block: clamp(40px, 5vw, 72px); }

.section + .section:not(.section--sunk):not(.section--brand) { border-top: 1px solid var(--rule); }
.section--sunk + .section--sunk { border-top: 1px solid var(--rule); }

/* Faint measure grid on sunken bands. Decorative only. */
.section--sunk::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: repeating-linear-gradient(to right, color-mix(in srgb, var(--rule-strong) 16%, transparent) 0 1px, transparent 1px 48px);
  opacity: .5;
  mask-image: linear-gradient(to bottom, transparent, #000 12%, #000 88%, transparent);
}
.section--sunk > * { position: relative; }

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

/* Grid and flex children default to min-width: auto, which lets a wide table or
   a long unbreakable word push the whole column past the viewport. Every layout
   child is allowed to shrink instead, so overflow happens inside the scroller
   that is meant to handle it. */
.grid > *,
.split > *,
.district > *,
.hero__grid > *,
.sizer__body > *,
.form-grid > *,
.post > * { min-width: 0; }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); }
.grid--4 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 210px), 1fr)); }

.stack > * + * { margin-top: var(--s-5); }
.stack-sm > * + * { margin-top: var(--s-3); }
.stack-lg > * + * { margin-top: var(--s-7); }

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

.split {
  display: grid;
  gap: clamp(var(--s-6), 5vw, var(--s-8));
  align-items: start;
}
@media (min-width: 900px) {
  .split { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .split--wide-first { grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); }
  .split--wide-last { grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); }
  .split--sticky > :last-child { position: sticky; top: 110px; }
}

/* -- 06  Section index and dimension rules -------------------------------- */

/* A heading led by a measured rule with end ticks, plus a mono index number.
   The index is written into the HTML, so it is present without JavaScript. */

.section-head {
  display: grid;
  gap: var(--s-4);
  margin-bottom: clamp(var(--s-6), 4vw, var(--s-8));
  max-width: 62ch;
}

.section-head__index {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: var(--step--2);
  font-weight: 500;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.section-head__index::after {
  content: '';
  flex: 1;
  height: 7px;
  border-top: 1px solid var(--rule-strong);
  border-left: 1px solid var(--rule-strong);
  border-right: 1px solid var(--rule-strong);
  opacity: .55;
}

.section--brand .section-head__index { color: color-mix(in srgb, var(--on-brand) 78%, transparent); }
.section--brand .section-head__index::after { border-color: color-mix(in srgb, var(--on-brand) 45%, transparent); opacity: 1; }
.section--brand .lead { color: color-mix(in srgb, var(--on-brand) 84%, transparent); }

.rule-tick {
  height: 9px;
  border-top: 1px solid var(--rule-strong);
  border-left: 1px solid var(--rule-strong);
  border-right: 1px solid var(--rule-strong);
  opacity: .5;
}

/* -- 07  Header and navigation -------------------------------------------- */

.header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--paper);
  border-bottom: 1px solid var(--rule);
  transition: border-color .2s var(--ease);
}
.header.is-scrolled { border-bottom-color: var(--rule-strong); }

.header__inner {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  min-height: 72px;
  padding-block: var(--s-3);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  text-decoration: none;
  color: inherit;
  flex: 0 0 auto;
  margin-right: auto;
}
.brand img { width: auto; height: 42px; background: none; }
/* Do not set display on `.brand picture`. A class plus an element outranks the
   single class rules below, which would show both logos at once. */
.brand picture { line-height: 0; }
.brand__light { display: block; }
.brand__dark { display: none; }
@media (prefers-color-scheme: dark) {
  .brand__light { display: none; }
  .brand__dark { display: block; }
}

.nav-toggle {
  display: none;
  align-items: center;
  gap: var(--s-2);
  background: none;
  border: 1px solid var(--rule-mid);
  border-radius: var(--radius);
  padding: 10px 14px;
  cursor: pointer;
  font-size: var(--step--1);
  font-weight: 600;
  min-height: 44px;
}
.nav-toggle__bars {
  display: grid;
  gap: 4px;
  width: 18px;
}
.nav-toggle__bars span {
  height: 2px;
  background: currentColor;
  transition: transform .2s var(--ease), opacity .2s var(--ease);
}
.nav-toggle[aria-expanded="true"] .nav-toggle__bars span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle__bars span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] .nav-toggle__bars span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

.nav {
  display: flex;
  align-items: center;
  gap: clamp(var(--s-3), 1.6vw, var(--s-5));
}
.nav a {
  color: var(--ink-2);
  text-decoration: none;
  font-size: var(--step--1);
  font-weight: 500;
  padding: 8px 2px;
  position: relative;
  white-space: nowrap;
}
.nav a::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--orange);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .22s var(--ease);
}
.nav a:hover { color: var(--ink); }
.nav a:hover::after, .nav a[aria-current="page"]::after { transform: scaleX(1); }
.nav a[aria-current="page"] { color: var(--ink); }
.nav a.btn { display: none; }
.nav a.btn::after { content: none; }
.nav a.btn--primary { color: var(--on-green); }

.header__cta { flex: 0 0 auto; }

@media (max-width: 1040px) {
  .header__cta .btn { padding-inline: var(--s-4); }
}

@media (max-width: 900px) {
  .js .nav-toggle { display: inline-flex; }
  .js .nav {
    display: none;
    position: absolute;
    left: 0;
    right: 0;
    top: 100%;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    background: var(--paper);
    border-bottom: 1px solid var(--rule-strong);
    padding: var(--s-3) var(--gutter) var(--s-5);
    max-height: calc(100dvh - 72px);
    overflow-y: auto;
  }
  .js .nav.is-open { display: flex; }
  .nav a {
    padding: 14px 0;
    border-bottom: 1px solid var(--rule);
    font-size: var(--step-0);
  }
  .nav a::after { display: none; }
  .nav a[aria-current="page"] { color: var(--green); }
  .header__inner { position: relative; flex-wrap: wrap; }
  .header__cta { display: none; }
  .js .nav a.btn { display: inline-flex; margin-top: var(--s-4); border-bottom: 0; }
  html:not(.js) .nav a.btn { display: inline-flex; border-bottom: 0; }
}

@media (max-width: 900px) {
  html:not(.js) .nav {
    flex-wrap: wrap;
    gap: var(--s-2) var(--s-4);
    width: 100%;
    padding-bottom: var(--s-3);
  }
  html:not(.js) .header__inner { flex-wrap: wrap; }
}

/* -- 08  Buttons and links ------------------------------------------------ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 48px;
  padding: 12px var(--s-5);
  border: 1px solid transparent;
  border-radius: var(--radius);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: .005em;
  text-decoration: none;
  cursor: pointer;
  transition: background .18s var(--ease), border-color .18s var(--ease), color .18s var(--ease), transform .18s var(--ease);
}
.btn:active { transform: translateY(1px); }

.btn--primary { background: var(--green); color: var(--on-green); }
.btn--primary:hover { background: var(--green-deep); }

.btn--ghost {
  background: transparent;
  color: var(--ink);
  border-color: var(--rule-mid);
}
.btn--ghost:hover { border-color: var(--ink-2); background: var(--paper-raise); }

.btn--on-brand { background: var(--on-brand); color: var(--surface-brand); }
.btn--on-brand:hover { background: color-mix(in srgb, var(--on-brand) 86%, var(--surface-brand)); }
.btn--ghost-on-brand {
  background: transparent;
  color: var(--on-brand);
  border-color: color-mix(in srgb, var(--on-brand) 50%, transparent);
}
.btn--ghost-on-brand:hover { border-color: var(--on-brand); background: color-mix(in srgb, var(--on-brand) 10%, transparent); }

.btn--sm { min-height: 40px; padding: 8px var(--s-4); font-size: var(--step--2); }

/* A text link with a small leading tick, used to leave a section. */
.link-more {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  color: var(--green);
  font-weight: 600;
  font-size: var(--step--1);
  text-decoration: none;
  padding: 6px 0;
}
.link-more::after {
  content: '';
  width: 18px;
  height: 1px;
  background: currentColor;
  transition: width .22s var(--ease);
}
.link-more:hover { text-decoration: underline; text-underline-offset: 4px; }
.link-more:hover::after { width: 28px; }

/* -- 09  Plates, the photograph treatment --------------------------------- */

/* A photograph is evidence. It sits in a frame with a mat and a caption.
   It is never a background, never full bleed, and never used twice. */

.plate { margin: 0; }

.plate__frame {
  background: var(--paper-raise);
  border: 1px solid var(--rule-mid);
  padding: 8px;
  position: relative;
}
.plate__frame img {
  width: 100%;
  display: block;
  aspect-ratio: var(--plate-ratio, 4 / 5);
  object-fit: cover;
}
.plate--wide { --plate-ratio: 16 / 10; }
.plate--square { --plate-ratio: 1 / 1; }
.plate--tall { --plate-ratio: 3 / 4; }

.plate__caption {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
  justify-content: space-between;
  align-items: baseline;
  margin-top: var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px solid var(--rule);
  font-size: var(--step--2);
  color: var(--ink-3);
  line-height: 1.45;
}
.plate__caption b {
  font-weight: 500;
  color: var(--ink-2);
}
.plate__meta {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  letter-spacing: .04em;
  white-space: nowrap;
}

/* A corner tick on the frame, the one piece of pure ornament allowed. */
.plate__frame::after {
  content: '';
  position: absolute;
  right: -1px;
  bottom: -1px;
  width: 22px;
  height: 22px;
  border-right: 2px solid var(--orange);
  border-bottom: 2px solid var(--orange);
  pointer-events: none;
}

/* -- 10  Cards ------------------------------------------------------------ */

.card {
  background: var(--paper-raise);
  border: 1px solid var(--rule-mid);
  border-radius: var(--radius);
  padding: clamp(var(--s-5), 2.4vw, var(--s-6));
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  height: 100%;
  position: relative;
  transition: border-color .18s var(--ease), transform .18s var(--ease), box-shadow .18s var(--ease);
}
.card h3 { font-size: var(--step-1); }
.card p { color: var(--ink-2); font-size: var(--step--1); line-height: 1.55; }

.card--link::before {
  content: '';
  position: absolute;
  left: -1px;
  top: -1px;
  bottom: -1px;
  width: 3px;
  background: var(--orange);
  transform: scaleY(0);
  transform-origin: top;
  transition: transform .22s var(--ease);
}
.card--link:hover, .card--link:focus-within { border-color: var(--ink-3); }
.card--link:hover::before, .card--link:focus-within::before { transform: scaleY(1); }
.card--link a::before { content: ''; position: absolute; inset: 0; }
.card--link a { text-decoration: none; color: inherit; }

.card__no {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: var(--step--2);
  font-weight: 500;
  color: var(--orange-ink);
  letter-spacing: .08em;
}

.card__list { display: grid; gap: 6px; margin-top: auto; padding-top: var(--s-3); }
.card__list li {
  font-size: var(--step--1);
  color: var(--ink-2);
  padding-left: 16px;
  position: relative;
  line-height: 1.5;
}
.card__list li::before {
  content: '';
  position: absolute;
  left: 0;
  top: .68em;
  width: 8px;
  height: 1px;
  background: var(--rule-strong);
}

/* Figures strip: three or four claims with a source line. No animation. */
.figures {
  display: grid;
  gap: 1px;
  background: var(--rule-mid);
  border: 1px solid var(--rule-mid);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
}
.figures > div {
  background: var(--paper);
  padding: var(--s-5) var(--s-5) var(--s-4);
  display: grid;
  gap: 4px;
  align-content: start;
}
.figure-value {
  font-size: var(--step-3);
  font-weight: 500;
  line-height: 1;
  letter-spacing: -.02em;
  color: var(--ink);
}
.figures dt { font-size: var(--step--1); color: var(--ink-2); }
.figures .small { font-size: var(--step--2); }

/* -- 11  Schedules and specification tables ------------------------------- */

.table-scroll {
  overflow-x: auto;
  border: 1px solid var(--rule-mid);
  background: var(--paper-raise);
}
.table-scroll:focus-visible { outline-offset: 0; }

.schedule { font-size: var(--step--1); min-width: 640px; }
.schedule caption {
  text-align: left;
  padding: var(--s-4) var(--s-5);
  border-bottom: 1px solid var(--rule-mid);
  font-size: var(--step--2);
  letter-spacing: .1em;
  text-transform: uppercase;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  color: var(--ink-3);
}
.schedule th, .schedule td {
  text-align: left;
  padding: var(--s-3) var(--s-5);
  border-bottom: 1px solid var(--rule);
  vertical-align: top;
}
.schedule thead th {
  font-size: var(--step--2);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-3);
  font-weight: 500;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  border-bottom: 1px solid var(--rule-strong);
  white-space: nowrap;
}
.schedule tbody tr:nth-child(even) { background: color-mix(in srgb, var(--paper-sunk) 55%, transparent); }
.schedule tbody tr:last-child td { border-bottom: 0; }
.schedule td.num, .schedule th.num { white-space: nowrap; }
.schedule a { color: var(--green); font-weight: 600; text-underline-offset: 3px; }

/* Specification list used on product pages. */
.specs { border-top: 1px solid var(--rule-strong); }
.specs > div {
  display: grid;
  gap: 2px var(--s-5);
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--rule);
}
@media (min-width: 560px) {
  .specs > div { grid-template-columns: minmax(140px, 34%) 1fr; }
}
.specs dt {
  font-size: var(--step--2);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-3);
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  padding-top: 2px;
}
.specs dd { font-size: var(--step--1); color: var(--ink); }

/* -- 12  Filters ---------------------------------------------------------- */

.filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  padding-bottom: var(--s-5);
  border-bottom: 1px solid var(--rule);
  margin-bottom: var(--s-6);
}
.filter {
  background: transparent;
  border: 1px solid var(--rule-mid);
  border-radius: 0;
  padding: 9px var(--s-4);
  min-height: 40px;
  font-size: var(--step--2);
  font-weight: 600;
  letter-spacing: .02em;
  cursor: pointer;
  color: var(--ink-2);
  transition: background .16s var(--ease), color .16s var(--ease), border-color .16s var(--ease);
}
.filter:hover { border-color: var(--ink-3); color: var(--ink); }
.filter[aria-pressed="true"] {
  background: var(--green);
  border-color: var(--green);
  color: var(--on-green);
}

.view-toggle { margin-left: auto; display: flex; gap: var(--s-2); }

.empty-note {
  padding: var(--s-7) 0;
  text-align: center;
  color: var(--ink-3);
}

/* -- 13  Forms ------------------------------------------------------------ */

.field { display: grid; gap: 6px; }
.field > label {
  font-size: var(--step--2);
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-3);
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
}
.field .req { color: var(--orange-ink); }

.input, .select, .textarea {
  width: 100%;
  background: var(--paper-raise);
  border: 1px solid var(--rule-mid);
  border-radius: var(--radius);
  padding: 12px var(--s-4);
  min-height: 48px;
  font-size: var(--step-0);
  transition: border-color .16s var(--ease), box-shadow .16s var(--ease);
}
.textarea { min-height: 148px; resize: vertical; line-height: 1.55; }
.input:hover, .select:hover, .textarea:hover { border-color: var(--ink-3); }
.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--green);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--green) 20%, transparent);
}
.select {
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 20px) 21px, calc(100% - 14px) 21px;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
  padding-right: 44px;
}
.form-grid { display: grid; gap: var(--s-5); }
@media (min-width: 640px) { .form-grid--2 { grid-template-columns: 1fr 1fr; } }

.honey { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* -- 14  Sizing helper ---------------------------------------------------- */

/* An honest calculator. It shows its own arithmetic and says plainly that it is
   indicative. It replaces the old site's dashboard of invented live numbers. */

.sizer {
  border: 1px solid var(--rule-mid);
  background: var(--paper-raise);
}
.sizer__head {
  padding: var(--s-5) clamp(var(--s-5), 3vw, var(--s-7));
  border-bottom: 1px solid var(--rule-mid);
}
.sizer__body {
  display: grid;
  gap: 0;
}
@media (min-width: 860px) {
  .sizer__body { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .sizer__out { border-left: 1px solid var(--rule-mid); border-top: 0 !important; }
}
.sizer__in, .sizer__out { padding: clamp(var(--s-5), 3vw, var(--s-7)); }
.sizer__out { border-top: 1px solid var(--rule-mid); background: var(--paper-sunk); }

.sizer input[type="range"] {
  width: 100%;
  accent-color: var(--green);
  height: 44px;
}
.sizer__readout {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
}
.sizer__readout output { font-size: var(--step-2); font-weight: 500; color: var(--ink); }

.sizer__result { display: grid; gap: var(--s-4); }
.sizer__result > div {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  padding-bottom: var(--s-3);
  border-bottom: 1px solid var(--rule);
}
.sizer__result dt { font-size: var(--step--1); color: var(--ink-2); }
.sizer__result dd {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-size: var(--step-2);
  font-weight: 500;
  white-space: nowrap;
}
.sizer__working {
  font-size: var(--step--2);
  color: var(--ink-3);
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  line-height: 1.7;
}
.sizer__note { font-size: var(--step--2); color: var(--ink-3); line-height: 1.6; }

/* -- 15  Quotes ----------------------------------------------------------- */

.quote {
  background: var(--paper-raise);
  border: 1px solid var(--rule-mid);
  border-top: 3px solid var(--orange);
  padding: clamp(var(--s-5), 2.4vw, var(--s-6));
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  height: 100%;
}
.quote blockquote {
  font-size: var(--step-0);
  line-height: 1.6;
  color: var(--ink);
}
.quote figcaption { margin-top: auto; padding-top: var(--s-3); border-top: 1px solid var(--rule); }
.quote__name { font-weight: 600; font-size: var(--step--1); }
.quote__role { font-size: var(--step--2); color: var(--ink-3); }

.marks {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}
.mark {
  border: 1px solid var(--rule-mid);
  border-radius: var(--radius);
  padding: 8px var(--s-4);
  font-size: var(--step--2);
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  letter-spacing: .06em;
  color: var(--ink-2);
  background: var(--paper-raise);
  white-space: nowrap;
}
.section--brand .mark {
  background: transparent;
  border-color: color-mix(in srgb, var(--on-brand) 34%, transparent);
  color: var(--on-brand);
}

/* -- 16  Footer ----------------------------------------------------------- */

.footer {
  background: #0E1512;
  color: #DCE3DD;
  padding-block: var(--s-9) var(--s-6);
  border-top: 3px solid var(--orange);
}
@media (prefers-color-scheme: dark) {
  .footer { background: var(--paper-sunk); border-top-color: var(--orange); }
}
.footer a { color: #DCE3DD; text-decoration: none; }
.footer a:hover { color: #FFFFFF; text-decoration: underline; text-underline-offset: 3px; }
.footer h2 {
  font-size: var(--step--2);
  letter-spacing: .12em;
  text-transform: uppercase;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-weight: 500;
  color: #8FA396;
  margin-bottom: var(--s-4);
}
.footer__grid {
  display: grid;
  gap: var(--s-7) var(--s-6);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));
}
.footer__brand img { height: 52px; width: auto; background: none; }
.footer__brand picture { display: block; line-height: 0; }
.footer__grid p, .footer__grid li { font-size: var(--step--1); color: #B4C0B7; line-height: 1.7; }
.footer__grid li + li { margin-top: 6px; }
.footer__bar {
  margin-top: var(--s-8);
  padding-top: var(--s-5);
  border-top: 1px solid #24322B;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3) var(--s-5);
  justify-content: space-between;
  font-size: var(--step--2);
  color: #8FA396;
}
.footer :focus-visible { outline-color: var(--orange); }

/* -- 17  Reveal ----------------------------------------------------------- */

/* Without JavaScript nothing is hidden. The hidden state only exists once the
   script has confirmed it is running. */
.js [data-reveal] {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity .5s var(--ease), transform .5s var(--ease);
  will-change: opacity, transform;
}
.js [data-reveal].is-revealed {
  opacity: 1;
  transform: none;
  will-change: auto;
}

/* -- 18  Utilities -------------------------------------------------------- */

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  align-items: center;
  font-size: var(--step--2);
  color: var(--ink-3);
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  padding-block: var(--s-5) 0;
}
.breadcrumb a { color: var(--ink-3); text-decoration: none; }
.breadcrumb a:hover { color: var(--ink); text-decoration: underline; }
.breadcrumb li + li::before { content: '/'; margin-right: var(--s-2); color: var(--rule-mid); }
.breadcrumb li { display: inline; }

.page-head { padding-block: clamp(var(--s-7), 6vw, var(--s-9)) clamp(var(--s-6), 4vw, var(--s-8)); }
.page-head h1 { max-width: 20ch; }
.page-head .lead { margin-top: var(--s-5); }

p.tag,
.tag {
  display: inline-block;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: var(--step--2);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--green);
  background: var(--green-wash);
  border-radius: 2px;
  padding: 4px 8px;
}

.price {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  font-size: var(--step-1);
  letter-spacing: -.01em;
}

.flow-tight > * + * { margin-top: var(--s-3); }
.text-center { text-align: center; }
.mt-6 { margin-top: var(--s-6); }
.mt-7 { margin-top: var(--s-7); }
.mt-8 { margin-top: var(--s-8); }
.max-prose { max-width: var(--prose); }
.mx-auto { margin-inline: auto; }

/* -- 19  Print ------------------------------------------------------------ */

@media print {
  :root {
    --paper: #FFFFFF; --paper-sunk: #FFFFFF; --paper-raise: #FFFFFF;
    --ink: #000000; --ink-2: #222222; --ink-3: #444444;
    --rule: #BBBBBB; --rule-mid: #999999; --rule-strong: #555555;
    --green: #14432A; --on-green: #FFFFFF;
    --on-brand: #000000; --surface-brand: #FFFFFF;
    --orange: #8A4B08; --orange-ink: #8A4B08;
  }
  .header, .footer__grid, .nav, .nav-toggle, .filters, .view-toggle, .sizer__in, .skip-link { display: none !important; }
  .section { padding-block: 18pt; border-top: 1px solid #CCC; }
  .section--brand { background: #FFF !important; color: #000 !important; }
  .section--sunk::before { display: none; }
  body { font-size: 10.5pt; background: #FFF; }
  a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-family: 'IBM Plex Mono', monospace;
    font-size: 8pt;
    color: #444;
  }
  .card, .quote, .plate__frame, .table-scroll, .post, figure { break-inside: avoid; border-color: #999; }
  .btn { background: #FFF !important; color: #000 !important; border: 1px solid #000 !important; }
  thead { display: table-header-group; }
  .card--link::before, .plate__frame::after { display: none; }
  .js [data-reveal] { opacity: 1 !important; transform: none !important; }
}

/* -- 20  Reduced motion --------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  .js [data-reveal] { opacity: 1; transform: none; }
}

/* -- 21  Discipline codes -------------------------------------------------- */

/* A discipline colour appears in exactly two places: the tag on a project record
   and the marker on the district plate. It is never a button, link or heading. */

:root {
  --disc-power: #8A4B08;
  --disc-solar: #8A4B08;
  --disc-water: #1E5B38;
  --disc-civil: #3F5670;
  --disc-telecom: #5A3D6B;
  --disc-security: #7A2B2B;
}
@media (prefers-color-scheme: dark) {
  :root {
    --disc-power: #F7A24A;
    --disc-solar: #F7A24A;
    --disc-water: #6FD79C;
    --disc-civil: #9FB4CE;
    --disc-telecom: #C4A0D6;
    --disc-security: #E79A9A;
  }
}

.tag--power   { color: var(--disc-power);   background: var(--orange-wash); }
.tag--solar   { color: var(--disc-solar);   background: var(--orange-wash); }
.tag--water   { color: var(--disc-water);   background: var(--green-wash); }
.tag--civil   { color: var(--disc-civil);   background: color-mix(in srgb, var(--disc-civil) 12%, var(--paper)); }
.tag--telecom { color: var(--disc-telecom); background: color-mix(in srgb, var(--disc-telecom) 12%, var(--paper)); }
.tag--security{ color: var(--disc-security);background: color-mix(in srgb, var(--disc-security) 12%, var(--paper)); }

/* -- 22  District plate ---------------------------------------------------- */

/* A simplified outline of Uganda carrying one marker per project. Hovering or
   focusing a marker highlights the matching row in the schedule beside it.
   There is no floating card, so nothing appears only on hover. */

.district {
  display: grid;
  gap: var(--s-6);
  align-items: start;
}
@media (min-width: 1000px) {
  .district { grid-template-columns: minmax(320px, 40%) minmax(0, 1fr); }
  .district__map { position: sticky; top: 104px; }
}

.district__map {
  border: 1px solid var(--rule-mid);
  background: var(--paper-raise);
  padding: var(--s-5);
}
.district__map svg { width: 100%; height: auto; display: block; }
.district__outline { fill: var(--paper-sunk); stroke: var(--rule-strong); stroke-width: 1.5; }
.district__lake { fill: color-mix(in srgb, var(--disc-civil) 22%, var(--paper)); stroke: var(--rule-mid); stroke-width: 1; }
.district__label { font-family: 'IBM Plex Mono', ui-monospace, monospace; font-size: 9px; fill: var(--ink-3); letter-spacing: .04em; }


.schedule tr.is-active,
.schedule tr:hover { background: var(--green-wash); }
.schedule tr.is-active td:first-child { box-shadow: inset 3px 0 0 var(--orange); }

.legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
  margin-top: var(--s-4);
  padding-top: var(--s-4);
  border-top: 1px solid var(--rule);
  font-size: var(--step--2);
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  color: var(--ink-3);
}
.legend span { display: inline-flex; align-items: center; gap: 6px; }
.legend i { width: 10px; height: 10px; display: inline-block; }

/* -- 23  Scale band -------------------------------------------------------- */

/* Five things we build, drawn at true relative height against a common ground
   line, so the reader can see the difference rather than read it. */

.scale-band {
  border: 1px solid var(--rule-mid);
  background: var(--paper-raise);
  padding: var(--s-5) var(--s-5) var(--s-4);
  overflow-x: auto;
}
.scale-band svg { min-width: 620px; width: 100%; height: auto; }
.scale-band .ground { stroke: var(--rule-strong); stroke-width: 1.5; }
.scale-band .earth { fill: none; stroke: var(--rule); stroke-width: 1; }
.scale-band .body { fill: none; stroke: var(--ink-2); stroke-width: 1.5; stroke-linejoin: round; }
.scale-band .body--fill { fill: color-mix(in srgb, var(--ink-2) 10%, transparent); }
.scale-band .dim { stroke: var(--rule-strong); stroke-width: 1; }
.scale-band .dim-text {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 11px;
  fill: var(--ink-2);
  font-weight: 500;
}
.scale-band .name {
  font-family: 'Archivo', sans-serif;
  font-size: 11px;
  fill: var(--ink-3);
}
.scale-band .person { fill: var(--orange); stroke: none; }

/* The dash offset is applied by the script, never here, so a script failure
   leaves the drawing finished rather than blank. */
@media (prefers-reduced-motion: reduce) {
  [data-schematic] .draw { stroke-dasharray: none !important; stroke-dashoffset: 0 !important; }
}

/* -- 24  Posts ------------------------------------------------------------- */

.post {
  display: grid;
  gap: var(--s-5);
  padding-block: clamp(var(--s-6), 4vw, var(--s-8));
  border-top: 1px solid var(--rule);
}
@media (min-width: 800px) {
  .post { grid-template-columns: minmax(0, 360px) minmax(0, 1fr); gap: var(--s-7); }
}
.post:first-of-type { border-top: 0; padding-top: 0; }
.post__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: center;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: var(--step--2);
  color: var(--ink-3);
  letter-spacing: .04em;
}
.post h2 { font-size: var(--step-2); margin-block: var(--s-3) var(--s-4); }
.post p { color: var(--ink-2); max-width: 62ch; }

/* -- 25  Product page ------------------------------------------------------ */

.gallery__thumbs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-top: var(--s-3);
}
.gallery__thumb {
  padding: 0;
  border: 1px solid var(--rule-mid);
  background: var(--paper-raise);
  cursor: pointer;
  width: 76px;
  height: 76px;
  overflow: hidden;
}
.gallery__thumb img { width: 100%; height: 100%; object-fit: cover; }
.gallery__thumb[aria-pressed="true"] { border-color: var(--green); outline: 1px solid var(--green); }

.buybox {
  border: 1px solid var(--rule-mid);
  background: var(--paper-raise);
  padding: clamp(var(--s-5), 3vw, var(--s-6));
  display: grid;
  gap: var(--s-4);
}
.buybox .price { font-size: var(--step-3); }
.buybox__note { font-size: var(--step--2); color: var(--ink-3); line-height: 1.6; }

/* -- 26  Callout ----------------------------------------------------------- */

.callout {
  border: 1px solid var(--rule-mid);
  border-left: 3px solid var(--orange);
  background: var(--paper-raise);
  padding: var(--s-5);
  font-size: var(--step--1);
  color: var(--ink-2);
}
.callout strong { color: var(--ink); }

/* -- 27  Steps ------------------------------------------------------------- */

.steps { counter-reset: step; display: grid; gap: 0; border-top: 1px solid var(--rule-strong); }
.steps > li {
  display: grid;
  gap: var(--s-2) var(--s-5);
  padding: var(--s-5) 0;
  border-bottom: 1px solid var(--rule);
}
@media (min-width: 700px) {
  .steps > li { grid-template-columns: 72px minmax(0, 26ch) minmax(0, 1fr); align-items: baseline; }
}
.steps__no {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: var(--step--2);
  font-weight: 500;
  color: var(--orange-ink);
  letter-spacing: .1em;
}
.steps h3 { font-size: var(--step-1); }
.steps p { color: var(--ink-2); font-size: var(--step--1); }

/* ==========================================================================
   28  Entrance and display type

   Three devices borrowed from a motion led reference and rebuilt without a
   framework: a clip reveal on the page heading, a short staggered entrance on
   the header, and a display setting where the unit sits smaller than the
   numeral. All three are switched off under prefers-reduced-motion and none of
   them hides content when JavaScript does not run.
   ========================================================================== */

/* -- Display heading ------------------------------------------------------ */

.display {
  font-size: var(--step-5);
  line-height: .95;
  letter-spacing: -.032em;
  text-wrap: balance;
}
@media (max-width: 640px) {
  .display { line-height: 1.02; letter-spacing: -.024em; }
}

/* -- Word clip reveal ----------------------------------------------------- */

/* The wrapper spans are created by JavaScript. Without it the heading is plain
   text and nothing is hidden. */
.rw {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  padding-bottom: .06em;
  margin-bottom: -.06em;
}
.rw > span {
  display: inline-block;
  transform: translateY(105%);
  transition: transform .72s var(--ease-reveal);
  transition-delay: calc(var(--i, 0) * 62ms);
}
.is-revealed .rw > span,
.rw.is-revealed > span { transform: none; }

@media (prefers-reduced-motion: reduce) {
  .rw > span { transform: none; transition: none; }
}

/* -- Header entrance ------------------------------------------------------ */

@media (prefers-reduced-motion: no-preference) {
  .js .header .brand,
  .js .header .nav > *,
  .js .header .nav-toggle,
  .js .header .header__cta {
    animation: fade-down .5s var(--ease-reveal) both;
  }
  .js .header .brand { animation-delay: 0ms; }
  .js .header .nav > *:nth-child(1) { animation-delay: 60ms; }
  .js .header .nav > *:nth-child(2) { animation-delay: 110ms; }
  .js .header .nav > *:nth-child(3) { animation-delay: 160ms; }
  .js .header .nav > *:nth-child(4) { animation-delay: 210ms; }
  .js .header .nav > *:nth-child(5) { animation-delay: 260ms; }
  .js .header .nav > *:nth-child(6) { animation-delay: 310ms; }
  .js .header .nav-toggle,
  .js .header .header__cta { animation-delay: 330ms; }
}

@keyframes fade-down {
  from { opacity: 0; transform: translateY(-14px); }
  to { opacity: 1; transform: none; }
}

/* -- Figures: numeral large, unit small and in the accent ----------------- */

.figure-value {
  display: flex;
  align-items: baseline;
  gap: .28em;
  letter-spacing: -.03em;
}
.figure-value .unit {
  font-size: .46em;
  text-decoration: none;
  color: var(--orange-ink);
  letter-spacing: .02em;
  font-weight: 500;
  transform: translateY(-.06em);
}

/* -- Hero ----------------------------------------------------------------- */

/* The composition sits on a datum: both columns share a baseline and a hairline
   runs the full width beneath them. It is a title block, not a centred banner. */

.hero { padding-top: clamp(32px, 5vw, 72px); padding-bottom: 0; }
.hero + .section { border-top: 0; }
.hero__grid {
  display: grid;
  gap: clamp(var(--s-6), 4vw, var(--s-8));
  align-items: end;
  padding-bottom: clamp(var(--s-6), 4vw, var(--s-8));
  border-bottom: 1px solid var(--rule-strong);
}
@media (min-width: 900px) {
  .hero__grid { grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); }
}
.hero__col { display: grid; gap: var(--s-5); align-content: end; }
.hero__foot {
  display: grid;
  gap: var(--s-5);
  align-items: end;
  margin-top: var(--s-3);
}
@media (min-width: 620px) {
  .hero__foot { grid-template-columns: minmax(0, 1fr) auto; }
}
.hero__note {
  max-width: 34ch;
  font-size: var(--step--1);
  color: var(--ink-2);
  line-height: 1.55;
  border-left: 2px solid var(--orange);
  padding-left: var(--s-4);
}

/* -- 29  District map markers ---------------------------------------------- */

.pin { stroke: var(--paper-raise); stroke-width: 1.5; transition: r .18s var(--ease), opacity .18s var(--ease); }
.pin--power, .pin--solar { fill: var(--disc-power); }
.pin--water { fill: var(--disc-water); }
.pin--civil { fill: var(--disc-civil); }
.pin--telecom { fill: var(--disc-telecom); }
.pin--security { fill: var(--disc-security); }
.pin.is-active { r: 10px; stroke: var(--orange); stroke-width: 2.5; }
.pin.is-dim { opacity: .14; }

.schedule tbody tr[data-pin-row] { cursor: default; }
.schedule tbody tr.is-active { background: var(--green-wash); }
.schedule tbody tr.is-active td:first-child { box-shadow: inset 3px 0 0 var(--orange); }

/* -- 30  Target size ------------------------------------------------------- */

/* Links that sit one per line are not "inside a sentence", so the inline
   exception to the minimum target size does not apply to them. Padding brings
   every one of them to at least 24px tall without changing how they look. */

.footer__grid li a,
.footer__bar a,
.stack-sm li a,
.breadcrumb a {
  display: inline-block;
  padding-block: 5px;
}

/* ==========================================================================
   31  Three dimensional page heads
   --------------------------------------------------------------------------
   Every page opens with a rotating object that says what the page is about.
   These are real CSS 3D transforms, not images and not a library: planes
   positioned in space inside a preserve-3d scene. That keeps them a few
   kilobytes of markup instead of a WebGL payload, which matters on a slow
   mobile connection.

   Rules that keep them cheap and safe:
     - only transform and opacity are animated, so they stay on the GPU
     - the whole object is aria-hidden, because it is decoration. The heading
       beside it carries the meaning
     - the script pauses them when they scroll out of view. With no script they
       simply keep running, which is the correct fallback
     - prefers-reduced-motion freezes every one of them at a composed angle
   ========================================================================== */

.hero3d {
  position: relative;
  padding-block: clamp(var(--s-7), 7vw, var(--s-9)) clamp(var(--s-6), 5vw, var(--s-8));
  overflow: hidden;
  border-bottom: 1px solid var(--rule);
  background: var(--paper);
}

/* A faint measure grid behind the object, the same device the sunk sections use. */
.hero3d::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(to right, var(--rule) 1px, transparent 1px),
    linear-gradient(to bottom, var(--rule) 1px, transparent 1px);
  background-size: 64px 64px;
  opacity: .32;
  -webkit-mask-image: radial-gradient(ellipse 80% 70% at 62% 45%, #000 20%, transparent 78%);
  mask-image: radial-gradient(ellipse 80% 70% at 62% 45%, #000 20%, transparent 78%);
  pointer-events: none;
}

.hero3d__inner {
  position: relative;
  display: grid;
  gap: clamp(var(--s-6), 4vw, var(--s-8));
  align-items: center;
}

@media (min-width: 900px) {
  .hero3d__inner { grid-template-columns: minmax(0, 1.02fr) minmax(0, 1fr); }
}

.hero3d__inner > * { min-width: 0; }
.hero3d__copy > * + * { margin-top: var(--s-5); }
.hero3d__copy .lead { max-width: 46ch; }

/* -- The stage ------------------------------------------------------------- */

.stage {
  position: relative;
  display: grid;
  place-items: center;
  min-height: clamp(240px, 30vw, 360px);
  perspective: 1100px;
  perspective-origin: 50% 46%;
}

.stage__scene {
  position: relative;
  transform-style: preserve-3d;
  width: 1px;
  height: 1px;
  will-change: transform;
  /* Size lives here as a number, never as a separate `transform: scale()` rule.
     See the note on stage-spin below for why that matters. */
  --stage-scale: 1;
}

/* Paused by the script once the object leaves the viewport. Without the script
   the class is never added and the object simply keeps turning. */
.stage.is-idle .stage__scene,
.stage.is-idle .stage__scene * { animation-play-state: paused !important; }

.stage__caption {
  position: absolute;
  left: 0;
  bottom: 0;
  font: 500 var(--step--2) / 1 var(--mono);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.plane {
  position: absolute;
  transform-style: preserve-3d;
  backface-visibility: visible;
}

/* Both keyframes must list the SAME transform functions in the same order.
   If one side is a bare `scale()` and the other a bare `rotateY()`, the browser
   gives up on interpolating function by function and interpolates the matrices
   instead. rotateY(360deg) as a matrix is the identity matrix, so the object
   would tween from scale to identity and never actually turn. That is what
   happened on phones when the size was set by a separate transform rule. */
/* The sweep starts at an angle rather than face on. A browser that refuses to
   animate at all, Opera Mini among them, then still shows the object turned
   into three quarter view instead of flat. */
@keyframes stage-spin {
  from { transform: scale(var(--stage-scale)) rotateY(-20deg); }
  to   { transform: scale(var(--stage-scale)) rotateY(340deg); }
}

/* ==========================================================================
   Object 1  Lattice tower, for the home page
   Two silhouettes crossed at ninety degrees. As the scene turns, the eye reads
   a single lattice mast rather than two flat cards, which is the cheapest
   convincing three dimensional structure there is.
   ========================================================================== */

.obj-tower { animation: stage-spin 34s linear infinite; }
.obj-tower .plane { width: 190px; height: 300px; margin: -150px 0 0 -95px; }
.obj-tower .plane--b { transform: rotateY(90deg); }
.obj-tower svg { width: 100%; height: 100%; overflow: visible; }

.obj-tower .lattice { fill: none; stroke: var(--green); stroke-width: 2.4; stroke-linecap: round; }
.obj-tower .lattice-fine { fill: none; stroke: var(--green); stroke-width: 1.2; opacity: .55; }
.obj-tower .insulator { fill: var(--orange); }

/* The conductor pulse. A dashed stroke whose offset animates, so a bright
   packet appears to travel along the line. */
.obj-tower .wire { fill: none; stroke: var(--rule-mid); stroke-width: 1.4; }
.obj-tower .pulse {
  fill: none;
  stroke: var(--orange);
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-dasharray: 16 190;
  animation: wire-run 3.2s linear infinite;
}
.obj-tower .pulse--b { animation-delay: 1.6s; }
@keyframes wire-run { from { stroke-dashoffset: 206; } to { stroke-dashoffset: 0; } }

/* ==========================================================================
   Object 2  Extruded gear, for the about page
   The gear is lifted straight from their own logo. Ten copies of one outline,
   each pushed a little further back, give it real thickness.
   ========================================================================== */

.obj-gear { animation: stage-spin 40s linear infinite; }
.obj-gear .slab {
  position: absolute;
  width: 220px; height: 220px;
  margin: -110px 0 0 -110px;
  transform-style: preserve-3d;
}
.obj-gear .slab svg { width: 100%; height: 100%; }
.obj-gear .gear-path { fill: var(--green-deep); }
.obj-gear .slab:last-child .gear-path { fill: var(--green); }
.obj-gear .bolt { fill: var(--orange); }

.obj-gear .slab:nth-child(1)  { transform: translateZ(-18px); }
.obj-gear .slab:nth-child(2)  { transform: translateZ(-14px); }
.obj-gear .slab:nth-child(3)  { transform: translateZ(-10px); }
.obj-gear .slab:nth-child(4)  { transform: translateZ(-6px); }
.obj-gear .slab:nth-child(5)  { transform: translateZ(-2px); }
.obj-gear .slab:nth-child(6)  { transform: translateZ(2px); }
.obj-gear .slab:nth-child(7)  { transform: translateZ(6px); }
.obj-gear .slab:nth-child(8)  { transform: translateZ(10px); }
.obj-gear .slab:nth-child(9)  { transform: translateZ(14px); }
.obj-gear .slab:nth-child(10) { transform: translateZ(18px); }

/* ==========================================================================
   Object 3  Fanned discipline plates, for the services page
   Six plates, one per discipline, hanging in space and turning together.
   ========================================================================== */

.obj-plates { animation: stage-spin 44s linear infinite; }
.obj-plates .card3 {
  position: absolute;
  width: 168px; height: 106px;
  margin: -53px 0 0 -84px;
  border: 1px solid var(--green);
  background: var(--paper-raise);
  display: grid;
  align-content: center;
  gap: 4px;
  padding: var(--s-4);
  transform-style: preserve-3d;
}
.obj-plates .card3 b {
  font: 600 var(--step--1) / 1.2 var(--sans);
  color: var(--ink);
}
.obj-plates .card3 i {
  font: 500 var(--step--2) / 1 var(--mono);
  font-style: normal;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--orange-ink);
}
.obj-plates .card3::after {
  content: "";
  position: absolute;
  left: var(--s-4); right: var(--s-4); bottom: var(--s-3);
  height: 2px;
  background: var(--orange);
  transform: scaleX(.34);
  transform-origin: left;
}
.obj-plates .card3:nth-child(1) { transform: rotateY(0deg)   translateZ(120px); }
.obj-plates .card3:nth-child(2) { transform: rotateY(60deg)  translateZ(120px); }
.obj-plates .card3:nth-child(3) { transform: rotateY(120deg) translateZ(120px); }
.obj-plates .card3:nth-child(4) { transform: rotateY(180deg) translateZ(120px); }
.obj-plates .card3:nth-child(5) { transform: rotateY(240deg) translateZ(120px); }
.obj-plates .card3:nth-child(6) { transform: rotateY(300deg) translateZ(120px); }

/* ==========================================================================
   Object 4  Survey plane with markers, for the projects page
   A grid lying flat in perspective with pins standing on it. The pins are
   counter rotated so they stay upright while the ground turns underneath.
   ========================================================================== */

.obj-survey { animation: stage-spin 46s linear infinite; }
.obj-survey .ground {
  position: absolute;
  width: 300px; height: 300px;
  margin: -150px 0 0 -150px;
  transform: rotateX(74deg);
  transform-style: preserve-3d;
  background-image:
    linear-gradient(to right, var(--rule-mid) 1px, transparent 1px),
    linear-gradient(to bottom, var(--rule-mid) 1px, transparent 1px);
  background-size: 30px 30px;
  border: 1px solid var(--green);
  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 42%, transparent 72%);
  mask-image: radial-gradient(circle at 50% 50%, #000 42%, transparent 72%);
}
.obj-survey .marker {
  position: absolute;
  width: 3px;
  height: 54px;
  background: linear-gradient(to top, var(--green) 40%, var(--orange));
  transform-origin: bottom center;
  transform: rotateX(-74deg);
}
.obj-survey .marker::after {
  content: "";
  position: absolute;
  top: -5px; left: -3.5px;
  width: 10px; height: 10px;
  background: var(--orange);
  animation: marker-pop 3.4s var(--ease) infinite;
}
@keyframes marker-pop {
  0%, 62%, 100% { transform: scale(1);   opacity: 1; }
  30%           { transform: scale(1.7); opacity: .55; }
}
.obj-survey .marker:nth-child(2) { left: 74px;  top: 96px; }
.obj-survey .marker:nth-child(3) { left: 140px; top: 60px; }
.obj-survey .marker:nth-child(4) { left: 196px; top: 132px; }
.obj-survey .marker:nth-child(5) { left: 108px; top: 176px; }
.obj-survey .marker:nth-child(6) { left: 226px; top: 196px; }
.obj-survey .marker:nth-child(3)::after { animation-delay: .7s; }
.obj-survey .marker:nth-child(4)::after { animation-delay: 1.4s; }
.obj-survey .marker:nth-child(5)::after { animation-delay: 2.1s; }
.obj-survey .marker:nth-child(6)::after { animation-delay: 2.8s; }

/* ==========================================================================
   Object 5  Solar array, for the hardware pages
   Three tilted modules on a frame. The cell grid is drawn with a gradient
   rather than an image, so it costs nothing to load.
   ========================================================================== */

.obj-array { animation: stage-spin 38s linear infinite; }
.obj-array .module {
  position: absolute;
  width: 150px; height: 96px;
  margin: -48px 0 0 -75px;
  transform-style: preserve-3d;
  background:
    repeating-linear-gradient(to right,  transparent 0 22px, rgba(255,255,255,.55) 22px 23px),
    repeating-linear-gradient(to bottom, transparent 0 22px, rgba(255,255,255,.55) 22px 23px),
    linear-gradient(150deg, #21406b, #16294a 58%, #101d36);
  border: 2px solid var(--rule-mid);
}
.obj-array .module::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(118deg, rgba(255,255,255,.42) 12%, transparent 34%);
  animation: glint 6s var(--ease) infinite;
}
@keyframes glint { 0%, 70%, 100% { opacity: .25; } 20% { opacity: .85; } }
.obj-array .module:nth-child(1) { transform: rotateX(58deg) translateY(-84px); }
.obj-array .module:nth-child(2) { transform: rotateX(58deg); }
.obj-array .module:nth-child(3) { transform: rotateX(58deg) translateY(84px); }
.obj-array .module:nth-child(2)::after { animation-delay: .8s; }
.obj-array .module:nth-child(3)::after { animation-delay: 1.6s; }

/* ==========================================================================
   Object 6  Signal rings, for the news page
   Rings expanding outward across a flat plane. It reads as transmission,
   which is what a news page does.
   ========================================================================== */

.obj-signal { animation: stage-spin 50s linear infinite; }
.obj-signal .ring {
  position: absolute;
  width: 220px; height: 220px;
  margin: -110px 0 0 -110px;
  border: 2px solid var(--green);
  border-radius: 50%;
  transform: rotateX(72deg) scale(.2);
  opacity: 0;
  animation: ring-out 4.4s var(--ease) infinite;
}
.obj-signal .ring:nth-child(2) { animation-delay: 1.1s; }
.obj-signal .ring:nth-child(3) { animation-delay: 2.2s; }
.obj-signal .ring:nth-child(4) { animation-delay: 3.3s; }
@keyframes ring-out {
  0%   { transform: rotateX(72deg) scale(.16); opacity: 0; }
  14%  { opacity: .9; }
  100% { transform: rotateX(72deg) scale(1.5); opacity: 0; }
}
.obj-signal .mast {
  position: absolute;
  width: 3px; height: 96px;
  margin: -96px 0 0 -1.5px;
  background: linear-gradient(to top, var(--green), var(--orange));
}
.obj-signal .mast::after {
  content: "";
  position: absolute;
  top: -6px; left: -3.5px;
  width: 10px; height: 10px;
  background: var(--orange);
  border-radius: 50%;
  animation: marker-pop 2.2s var(--ease) infinite;
}

/* ==========================================================================
   Object 7  Location marker, for the contact page
   ========================================================================== */

.obj-place { animation: stage-spin 42s linear infinite; }
.obj-place .ground {
  position: absolute;
  width: 260px; height: 260px;
  margin: -130px 0 0 -130px;
  transform: rotateX(74deg);
  background-image:
    linear-gradient(to right, var(--rule-mid) 1px, transparent 1px),
    linear-gradient(to bottom, var(--rule-mid) 1px, transparent 1px);
  background-size: 26px 26px;
  border: 1px solid var(--green);
  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 40%, transparent 70%);
  mask-image: radial-gradient(circle at 50% 50%, #000 40%, transparent 70%);
}
.obj-place .pin {
  position: absolute;
  width: 34px; height: 34px;
  margin: -96px 0 0 -17px;
  border: 4px solid var(--orange);
  border-radius: 50% 50% 50% 0;
  transform: rotate(-45deg);
  animation: pin-hover 3s var(--ease) infinite;
}
@keyframes pin-hover {
  0%, 100% { transform: rotate(-45deg) translate(0, 0); }
  50%      { transform: rotate(-45deg) translate(5px, -5px); }
}
.obj-place .halo {
  position: absolute;
  width: 90px; height: 90px;
  margin: -45px 0 0 -45px;
  border: 2px solid var(--orange);
  border-radius: 50%;
  transform: rotateX(74deg);
  animation: ring-out 3s var(--ease) infinite;
}

/* -- Small screens --------------------------------------------------------- */

/* Perspective moves with the scale, so the depth reads the same on a phone as
   it does on a desktop rather than flattening out. */
@media (max-width: 640px) {
  .stage { min-height: 210px; perspective: 790px; }
  .stage__scene { --stage-scale: .72; }
  .hero3d::before { background-size: 44px 44px; }
}

@media (max-width: 400px) {
  .stage { min-height: 180px; perspective: 640px; }
  .stage__scene { --stage-scale: .58; }
}

/* -- Reduced motion -------------------------------------------------------- */
/* Frozen at an angle that still reads as three dimensional, rather than hidden. */

@media (prefers-reduced-motion: reduce) {
  .stage__scene,
  .stage__scene * { animation: none !important; }
  .obj-tower  { transform: scale(var(--stage-scale)) rotateY(24deg); }
  .obj-gear   { transform: scale(var(--stage-scale)) rotateY(18deg) rotateX(6deg); }
  .obj-plates { transform: scale(var(--stage-scale)) rotateY(28deg); }
  .obj-survey { transform: scale(var(--stage-scale)) rotateY(16deg); }
  .obj-array  { transform: scale(var(--stage-scale)) rotateY(20deg); }
  .obj-signal { transform: scale(var(--stage-scale)) rotateY(12deg); }
  .obj-place  { transform: scale(var(--stage-scale)) rotateY(14deg); }
  .obj-signal .ring:nth-child(1) { opacity: .85; transform: rotateX(72deg) scale(.55); }
  .obj-signal .ring:nth-child(2) { opacity: .5;  transform: rotateX(72deg) scale(.95); }
  .obj-signal .ring:nth-child(3) { opacity: .25; transform: rotateX(72deg) scale(1.3); }
  .obj-signal .ring:nth-child(4) { opacity: 0; }
  .obj-place .halo { opacity: .5; transform: rotateX(74deg) scale(1); }
  .obj-tower .pulse { stroke-dasharray: none; opacity: .9; }
}

/* ==========================================================================
   32  Interaction polish
   Small, cheap movements that make the page feel answerable rather than
   printed. Everything here is transform or opacity only, and every one of
   them is switched off under prefers-reduced-motion at the end of the file.
   ========================================================================== */

/* Cards lift very slightly and their rule extends. No shadow, because the
   design has none anywhere else. */
.card {
  transition: transform .28s var(--ease), border-color .28s var(--ease);
}
.card--link:hover,
.card--link:focus-within {
  transform: translateY(-3px);
  border-color: var(--green);
}
.card--link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -1px;
  height: 2px;
  width: 100%;
  background: var(--orange);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .32s var(--ease);
}
.card--link:hover::after,
.card--link:focus-within::after { transform: scaleX(1); }

/* Buttons: the fill sweeps in from the left rather than just changing colour. */
.btn { position: relative; overflow: hidden; z-index: 0; }
.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  background: currentColor;
  opacity: 0;
  transform: translateX(-101%);
  transition: transform .34s var(--ease), opacity .34s var(--ease);
  z-index: -1;
}
.btn:hover::before { opacity: .1; transform: translateX(0); }

/* Plates lift their caption rule on hover. */
.plate__frame { transition: transform .4s var(--ease); }
.plate:hover .plate__frame { transform: translateY(-2px); }

/* Staggered entry for anything inside a revealed grid. */
.grid > [data-reveal] { transition-delay: calc(var(--stagger, 0) * 70ms); }

/* The section index number draws its own rule as the section arrives. */
.section-head__index span:first-child { position: relative; }
.section-head__index span:first-child::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -4px;
  height: 2px;
  background: var(--orange);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .5s var(--ease-reveal) .1s;
}
.section-head.is-revealed .section-head__index span:first-child::after { transform: scaleX(1); }

/* Inline links underline from the left on hover instead of just appearing. */
.prose a,
.link-more {
  background-image: linear-gradient(var(--orange), var(--orange));
  background-size: 0% 2px;
  background-repeat: no-repeat;
  background-position: 0 100%;
  transition: background-size .3s var(--ease);
}
.prose a:hover,
.link-more:hover { background-size: 100% 2px; }

/* ==========================================================================
   33  Mobile action bar
   On a phone the fastest route to a real enquiry is a thumb on a call or a
   WhatsApp button, so those sit permanently within reach. It replaces nothing:
   the same actions exist in the page.
   ========================================================================== */

.actionbar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 60;
  display: none;
  border-top: 1px solid var(--rule);
  background: var(--paper-raise);
  padding-bottom: env(safe-area-inset-bottom, 0);
}

.actionbar__inner {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}

.actionbar a {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  min-height: 58px;
  padding: var(--s-2);
  text-decoration: none;
  color: var(--ink);
  font: 600 var(--step--1) / 1 var(--sans);
  border-left: 1px solid var(--rule);
  transition: background-color .2s var(--ease);
}
.actionbar a:first-child { border-left: 0; }
.actionbar a:hover,
.actionbar a:focus-visible { background: var(--green-wash); }

.actionbar a svg { width: 20px; height: 20px; fill: none; stroke: var(--green); stroke-width: 1.9; }
.actionbar a.is-primary { background: var(--green); color: var(--on-green); }
.actionbar a.is-primary svg { stroke: var(--on-green); }
.actionbar a.is-primary:hover,
.actionbar a.is-primary:focus-visible { background: var(--green-deep); }

@media (max-width: 900px) {
  .actionbar { display: block; }
  /* Keep the bar clear of the last thing on the page. */
  body { padding-bottom: 62px; }
}

@media print { .actionbar { display: none; } }

/* ==========================================================================
   34  Reduced motion
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .card,
  .btn::before,
  .plate__frame,
  .grid > [data-reveal],
  .section-head__index span:first-child::after,
  .prose a,
  .link-more {
    transition: none !important;
  }
  .card--link:hover,
  .card--link:focus-within,
  .plate:hover .plate__frame { transform: none; }
  .section-head.is-revealed .section-head__index span:first-child::after { transform: scaleX(1); }
}

/* ==========================================================================
   35  Three dimensional heads: browser reach and extra motion
   --------------------------------------------------------------------------
   Two jobs here. Make the objects survive older mobile engines, and give each
   one more to do than turn on the spot.

   The rule that governs every animation below, learned the hard way: if an
   element already carries a transform, every keyframe must repeat that whole
   transform chain. Animate a bare rotate() against a static scale() and the
   browser stops interpolating function by function, falls back to matrix
   interpolation, and the motion silently collapses. Per element differences go
   in custom properties so the chain stays identical in both keyframes.
   ========================================================================== */

/* -- Older WebKit ---------------------------------------------------------- */
/* Android WebView before 5 and old iOS Safari need the prefix to build a 3D
   context at all. Harmless everywhere else. */

.stage { -webkit-perspective: 1100px; }
.stage__scene,
.plane,
.obj-gear .slab,
.obj-plates .card3,
.obj-survey .ground,
.obj-array .module { -webkit-transform-style: preserve-3d; }
.plane { -webkit-backface-visibility: visible; }

@media (max-width: 640px) { .stage { -webkit-perspective: 790px; } }
@media (max-width: 400px) { .stage { -webkit-perspective: 640px; } }

/* -- No 3D at all ---------------------------------------------------------- */
/* If the engine cannot do preserve-3d, every depth copy collapses onto one
   plane and the object turns into a smear. Drop the duplicates and let the
   remaining face turn in plain 2D, which has worked in every browser since
   about 2011. */

@supports not (transform-style: preserve-3d) {
  .obj-tower .plane--b { display: none; }
  .obj-gear .slab:not(:last-child) { display: none; }
  .obj-plates .card3:nth-child(n+2) { display: none; }
  .obj-plates .card3:nth-child(1) { transform: none; }
  .stage__scene { animation-name: stage-spin-flat; }
}

@keyframes stage-spin-flat {
  from { transform: scale(var(--stage-scale)) rotate(0deg); }
  to   { transform: scale(var(--stage-scale)) rotate(360deg); }
}

/* ==========================================================================
   Extra motion, object by object
   ========================================================================== */

/* Tower: a warning beacon at the mast head, the way a real one carries. */
.obj-tower .beacon {
  fill: var(--orange);
  animation: beacon 2.4s steps(1, end) infinite;
}
@keyframes beacon {
  0%, 45%  { opacity: 1; }
  46%, 100% { opacity: .12; }
}

/* Gear: a second, smaller gear meshing against the first and turning the
   opposite way, which is what makes it read as a mechanism. */
.obj-gear .pinion {
  position: absolute;
  width: 96px; height: 96px;
  margin: -132px 0 0 34px;
  animation: pinion 9s linear infinite reverse;
}
.obj-gear .pinion svg { width: 100%; height: 100%; }
.obj-gear .pinion .gear-path { fill: var(--green); }
@keyframes pinion {
  from { transform: translateZ(20px) rotate(0deg); }
  to   { transform: translateZ(20px) rotate(360deg); }
}

/* Plates: each one breathes on its own phase, so the ring is never static. */
.obj-plates .card3 {
  --spin: 0deg;
  animation: plate-bob 5.5s ease-in-out infinite;
  animation-delay: calc(var(--n, 0) * -0.9s);
}
.obj-plates .card3:nth-child(1) { --spin: 0deg;   --n: 0; }
.obj-plates .card3:nth-child(2) { --spin: 60deg;  --n: 1; }
.obj-plates .card3:nth-child(3) { --spin: 120deg; --n: 2; }
.obj-plates .card3:nth-child(4) { --spin: 180deg; --n: 3; }
.obj-plates .card3:nth-child(5) { --spin: 240deg; --n: 4; }
.obj-plates .card3:nth-child(6) { --spin: 300deg; --n: 5; }
@keyframes plate-bob {
  0%, 100% { transform: rotateY(var(--spin)) translateZ(120px) translateY(-7px); }
  50%      { transform: rotateY(var(--spin)) translateZ(120px) translateY(7px); }
}

/* Survey: a sweep arm crossing the grid, like a site being scanned. */
.obj-survey .sweep {
  position: absolute;
  width: 150px; height: 2px;
  margin: -1px 0 0 0;
  transform-origin: left center;
  background: linear-gradient(to right, var(--orange), transparent);
  animation: sweep 6s linear infinite;
}
@keyframes sweep {
  from { transform: rotateX(74deg) rotate(0deg); }
  to   { transform: rotateX(74deg) rotate(360deg); }
}

/* Array: the modules track the sun across the day, then reset. */
.obj-array .module {
  --ty: 0px;
  animation: track 11s ease-in-out infinite;
}
.obj-array .module:nth-child(1) { --ty: -84px; }
.obj-array .module:nth-child(2) { --ty: 0px; }
.obj-array .module:nth-child(3) { --ty: 84px; }
@keyframes track {
  0%, 100% { transform: rotateX(50deg) translateY(var(--ty)); }
  50%      { transform: rotateX(66deg) translateY(var(--ty)); }
}

/* Signal: a rotating scan arm under the expanding rings. */
.obj-signal .scan {
  position: absolute;
  width: 130px; height: 2px;
  transform-origin: left center;
  background: linear-gradient(to right, var(--green), transparent);
  animation: sweep 4s linear infinite;
}

/* Place: a marker orbiting the pin, so the plate is never still. */
.obj-place .orbit {
  position: absolute;
  width: 10px; height: 10px;
  margin: -5px 0 0 -5px;
  background: var(--green);
  animation: orbit 7s linear infinite;
}
@keyframes orbit {
  from { transform: rotateX(74deg) rotate(0deg) translateX(86px); }
  to   { transform: rotateX(74deg) rotate(360deg) translateX(86px); }
}

/* -- Reduced motion -------------------------------------------------------- */
/* Everything above stops. The objects keep their composed angle. */

@media (prefers-reduced-motion: reduce) {
  .obj-tower .beacon,
  .obj-gear .pinion,
  .obj-plates .card3,
  .obj-survey .sweep,
  .obj-array .module,
  .obj-signal .scan,
  .obj-place .orbit { animation: none !important; }

  .obj-gear .pinion { transform: translateZ(20px) rotate(18deg); }
  .obj-plates .card3 { transform: rotateY(var(--spin)) translateZ(120px); }
  .obj-array .module { transform: rotateX(58deg) translateY(var(--ty)); }
  .obj-survey .sweep { transform: rotateX(74deg) rotate(38deg); }
  .obj-signal .scan  { transform: rotateX(72deg) rotate(24deg); }
  .obj-place .orbit  { transform: rotateX(74deg) rotate(52deg) translateX(86px); }
  .obj-tower .beacon { opacity: 1; }
}

/* ==========================================================================
   36  Social icons and the floating WhatsApp button
   --------------------------------------------------------------------------
   The footer lists the company's profiles as brand marks rather than words,
   because a person scanning a footer recognises the shape before they read
   the name. Each one still carries its name for a screen reader.

   The floating button is the one piece of furniture that follows the reader
   down the page. WhatsApp is how most enquiries in Kampala actually start,
   so it stays in reach without taking over the screen. Its label stays shut
   until the pointer arrives, so the resting state is a quiet disc.

   Brand glyphs are from Simple Icons, CC0, embedded rather than fetched.
   ========================================================================== */

/* Text that only a screen reader needs. */
.vh {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.social {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3);
  list-style: none;
  padding: 0;
}

/* The footer's list rules put a top margin on every item after the first,
   which leaves the first icon sitting proud of the row. */
.footer__grid .social li + li,
.social li { margin: 0; }

/* Doubled with .footer__grid so the one-link-per-line padding rule above
   (display: inline-block; padding-block: 5px) cannot push the glyph off centre. */
.social a,
.footer__grid .social li a {
  display: grid;
  place-items: center;
  padding: 0;
  width: 40px; height: 40px;
  border: 1px solid rgba(255, 255, 255, .22);
  border-radius: 50%;
  color: #DCE3DD;
  transition: color .2s var(--ease), background-color .2s var(--ease),
              border-color .2s var(--ease), transform .2s var(--ease);
}

.social a svg { width: 17px; height: 17px; fill: currentColor; }

.social a:hover,
.social a:focus-visible {
  color: #FFFFFF;
  background: rgba(255, 255, 255, .12);
  border-color: rgba(255, 255, 255, .55);
  transform: translateY(-2px);
  text-decoration: none;
}

.wafloat {
  position: fixed;
  right: clamp(14px, 2.5vw, 26px);
  bottom: clamp(14px, 2.5vw, 26px);
  z-index: 55;
  display: inline-flex;
  align-items: center;
  height: 54px;
  padding-inline: 14px;
  border-radius: 999px;
  background: var(--green);
  color: var(--on-green);
  text-decoration: none;
  box-shadow: 0 6px 22px rgba(19, 23, 20, .24);
  animation: wafloat-in .5s var(--ease) .6s both;
  transition: background-color .2s var(--ease), transform .2s var(--ease),
              box-shadow .2s var(--ease);
}

.wafloat svg { width: 26px; height: 26px; fill: currentColor; flex: none; }

.wafloat__label {
  max-width: 0;
  overflow: hidden;
  white-space: nowrap;
  font: 600 var(--step--1) / 1 var(--sans);
  transition: max-width .32s var(--ease), margin-left .32s var(--ease);
}

.wafloat:hover,
.wafloat:focus-visible {
  background: var(--green-deep);
  transform: translateY(-2px);
  box-shadow: 0 10px 26px rgba(19, 23, 20, .3);
}

.wafloat:hover .wafloat__label,
.wafloat:focus-visible .wafloat__label {
  max-width: 10rem;
  margin-left: 10px;
}

@keyframes wafloat-in {
  from { opacity: 0; transform: translateY(14px) scale(.92); }
  to   { opacity: 1; transform: none; }
}

/* Clear of the mobile action bar, which owns the bottom edge on a phone. */
@media (max-width: 900px) {
  .wafloat {
    height: 50px;
    bottom: calc(62px + 24px + env(safe-area-inset-bottom, 0));
  }
}

@media (prefers-reduced-motion: reduce) {
  .wafloat { animation: none; }
  .wafloat:hover,
  .wafloat:focus-visible { transform: none; }
  .social a:hover,
  .social a:focus-visible { transform: none; }
  .wafloat,
  .wafloat__label,
  .social a { transition: none; }
}

@media print { .wafloat { display: none; } }

/* "Find us on Social Media" band on the home page. Same icons as the footer,
   on the light surface, each in a circle with its name underneath. */
.social-find { text-align: center; }
.social-find h2 { margin-bottom: var(--s-6); }

.social-find__list {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--s-6) var(--s-7, var(--s-6));
  list-style: none;
  margin: 0; padding: 0;
}

.social-find__list li { margin: 0; }

.social-find__list a {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
  min-width: 72px;
  color: var(--ink-2);
  font-size: var(--step--1);
  text-decoration: none;
}

.social-find__list svg {
  box-sizing: content-box;
  width: 22px; height: 22px;
  padding: 17px;
  border: 1px solid var(--rule);
  border-radius: 50%;
  background: var(--paper-raise);
  color: var(--green);
  fill: currentColor;
  transition: color .2s var(--ease), background-color .2s var(--ease),
              border-color .2s var(--ease), transform .2s var(--ease);
}

.social-find__list a:hover svg,
.social-find__list a:focus-visible svg {
  background: var(--green);
  border-color: var(--green);
  color: var(--paper-raise);
  transform: translateY(-2px);
}

.social-find__list a:hover,
.social-find__list a:focus-visible { color: var(--ink); text-decoration: none; }

@media (prefers-reduced-motion: reduce) {
  .social-find__list svg { transition: none; }
  .social-find__list a:hover svg,
  .social-find__list a:focus-visible svg { transform: none; }
}
