/* =====================================================
   SSS Recompression Chamber Network - 2026 site
   ---- Handcrafted by <jag/> | Jorge Alberto Gutiérrez  
   =====================================================*/

   


@font-face{
  font-family:'Inter'; font-style:normal; font-weight:100 900; font-display:swap;
  src:url('/fonts/Inter-VariableFont_opsz_wght.woff2') format('woff2'),
      url('/fonts/Inter-VariableFont_opsz_wght.ttf') format('truetype');
}
@font-face{
  font-family:'Inter Tight'; font-style:normal; font-weight:100 900; font-display:swap;
  src:url('/fonts/InterTight-VariableFont_wght.woff2') format('woff2'),
      url('/fonts/InterTight-VariableFont_wght.ttf') format('truetype');
}
@font-face{
  font-family:'JetBrains Mono'; font-style:normal; font-weight:100 900; font-display:swap;
  src:url('/fonts/JetBrainsMono-VariableFont_wght.woff2') format('woff2'),
      url('/fonts/JetBrainsMono-VariableFont_wght.ttf') format('truetype');
}

:root {
  
  /* ---- Four basics ---- */
  --navy:   #152A63;   /* Abyss Navy   - brand, text, structure */
  --orange: #FF5A1E;   /* Rescue Orange - action/alert accent (fills/large) */
  --aqua:   #16BCE6;   /* Aqua          - water, links, highlights (fills/large) */
  --foam:   #F5F8FB;   /* Foam          - page surface */

  /* ---- Navy ramp ---- */
  --navy-900:#0B1838; --navy-800:#102249; --navy-700:#152A63; --navy-600:#1E3A82;
  --navy-500:#2B4DA6; --navy-400:#5470C0; --navy-300:#8D9FD6; --navy-200:#C2CCE9; --navy-100:#E7EBF5;

  /* ---- Orange ramp (action & alert ONLY) ---- */
  --or-700:#C73E0F; --or-600:#E84B12; --or-500:#FF5A1E; --or-400:#FF7A45;
  --or-300:#FF9E78; --or-200:#FFC4AC; --or-100:#FFE6DC;
  --orange-700:var(--or-700); --orange-600:var(--or-600);   /* legacy aliases */
  --orange-500:var(--or-500); --orange-400:var(--or-400);
  /* Emergency surfaces carry body-size WHITE text → must stay AA (>=4.5:1).
     Bright --orange is only 3.1:1 on white, so emergency uses the darker
     --or-700 (#C73E0F = 5.1:1). NEVER point --emergency at the bright orange. */
  --emergency: var(--or-700); --emergency-dk:#9A2E0B;   /* = #C73E0F, drawn from the design-system orange ramp */

  /* ---- Aqua ramp (water, links, highlights) ---- */
  --aq-800:#095B75;   /* AA-safe aqua for TEXT ON A TINTED SURFACE - see the
                         contrast note above .section-explorer .map-region-title.
                         --aq-700 #0A7B9C is 4.55:1 on white but drops to
                         4.41:1 on --aq-100 and 4.07:1 on --mist-2, i.e. under
                         AA. Do NOT repoint --cyan-600 at it: that alias has
                         a dozen consumers on untinted surfaces which must not
                         move. Scope it per-surface instead. */
  --aq-700:#0A7B9C; --aq-600:#109ABF; --aq-500:#16BCE6; --aq-400:#4ACCEC;
  --aq-300:#84DCF1; --aq-200:#BDEBF8; --aq-100:#E6F7FC;
  /* legacy "cyan" aliases - TEXT role (cyan-600) maps to AA-safe aq-700;
     lighter steps map to bright aqua for fills, borders, and tints. */
  --cyan-600:var(--aq-700); --cyan-500:var(--aq-500); --cyan-400:var(--aq-400);
  --cyan-200:var(--aq-200); --cyan-100:var(--aq-100);

  /* ---- Neutrals ---- */
  --ink:#0E1A2B; --slate:#3A4759; --steel:#6B788C; --cloud:#D8DEE8;
  --paper:#FFFFFF;
  --line: var(--cloud);          /* legacy: borders */
  --mist: var(--foam);           /* legacy: page background -> foam */
  --mist-2:#E7EBF5;              /* legacy: deeper section tint (navy-100) */

  /* ---- Type - Inter Tight (titles) · Inter (body) · JetBrains Mono (meta) ---- */
  --font-title:'Inter Tight','Inter',-apple-system,system-ui,sans-serif;
  --font-body: 'Inter',-apple-system,system-ui,sans-serif;
  --font-mono: 'JetBrains Mono',ui-monospace,Menlo,monospace;
  --font-brand: var(--font-body);   /* legacy alias */

  /* ---- Layout ---- */
  --maxw: 1180px;
  --r-sm:8px; --r-md:14px; --r-lg:22px;
  --radius: var(--r-md);            /* legacy alias -> 14px */
  --shadow-sm: 0 1px 2px rgba(11,24,56,.06), 0 2px 8px rgba(11,24,56,.07);
  --shadow-md: 0 18px 50px -24px rgba(11,24,56,.55);

  /* Bootstrap link colour → AA-safe aqua, hover navy */
  --bs-link-color-rgb: 10,123,156;
  --bs-link-hover-color-rgb: 21,42,99;
}

html {
  font-size: clamp(15px, 14px + 0.25vw, 17px);
  scroll-padding-top: 7rem; /* sticky chrome (band + nav) */
}
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } }
body {
  font-family: var(--font-brand);
  color: var(--ink);
  background: var(--mist);
  line-height: 1.6;
}
h1,h2,h3,h4,h5,h6 { font-family: var(--font-title); color: var(--navy-800); line-height: 1.15; font-weight: 700; letter-spacing: -.02em; }
a { color: rgb(var(--bs-link-color-rgb)); text-decoration: none; }
a:hover { color: rgb(var(--bs-link-hover-color-rgb)); text-decoration: underline; }

.skip-link {
  position: absolute; left: -999px; top: 0; z-index: 2000;
  background: var(--navy-800); color:#fff; padding:.6rem 1rem; border-radius: 0 0 var(--radius) 0;
}
.skip-link:focus { left: 0; }

/* ---- Brand helpers ---------------------------------------------------- */
.text-navy { color: var(--navy-700) !important; }
.text-cyan { color: var(--cyan-600) !important; }
.text-emergency { color: var(--emergency) !important; }
.bg-navy { background: var(--navy-800) !important; color:#fff; }
.bg-navy h1, .bg-navy h2, .bg-navy h3, .bg-navy h4 { color:#fff; }
.bg-navy .section-eyebrow { color: var(--cyan-200); }
.bg-navy .text-cyan { color: var(--cyan-200) !important; }
.bg-mist { background: var(--mist) !important; }
.bg-mist-2 { background: var(--mist-2) !important; }
.bg-aqua { background: var(--aq-200) !important; }

img {
  -webkit-touch-callout: none;
  -webkit-user-drag: none;
}

.site-chrome { position: sticky; top: 0; z-index: 1040; }
.chrome-sentinel { position:absolute; left:0; width:1px; height:1px; pointer-events:none; }
.chrome-sentinel-in  { top: 96px; }   /* passed going down -> condense */
.chrome-sentinel-out { top: 40px; }   /* returned going up -> expand   */

@media (prefers-reduced-motion: no-preference) {
  .brand-logo, .site-head .dan-badge { transition: height .18s ease; }
  .emergency-band .inner { transition: padding .18s ease, font-size .18s ease; }
  .site-head .navbar-brand { transition: padding .18s ease, opacity .18s ease, visibility .18s; }
  .brand-tag { display: block; overflow: hidden; height: 1.2em;
    transition: height .18s ease, opacity .18s ease; }
  html.is-condensed .brand-tag { height: 0; opacity: 0; }
}
html.is-condensed .emergency-band .inner { padding-top:.3rem; padding-bottom:.3rem; font-size:.9rem; }
html.is-condensed .brand-logo { height: 32px; }
html.is-condensed .site-head .dan-badge { height: 32px; }   /* matches .brand-logo condensed */
html.is-condensed .site-head .navbar-brand { padding: .15rem 0; }
html.hero-in-view .site-head .navbar-brand,
html.hero-in-view .site-head .dan-badge { opacity:0; visibility:hidden; }

@media (prefers-reduced-motion: reduce) {
  html.is-condensed .brand-tag { display: none; }
}

@media (max-width: 991.98px) {
  .site-chrome { max-height: 100vh; max-height: 100dvh; overflow-y: auto; }
}

.emergency-band {
  background: var(--emergency); color:#fff;
  font-weight: 600;
}
.emergency-band a { color:#fff; text-decoration: underline; text-underline-offset: 2px; }
.emergency-band a:hover { color:#fff; }
.emergency-band .inner {
  max-width: var(--maxw); margin:0 auto; padding:.55rem 1rem;
  display:flex; gap:.65rem; align-items:center; justify-content:center;
  flex-wrap:wrap; text-align:center; font-size:.98rem;
}
.emergency-band .pulse {
  width:.7rem; height:.7rem; border-radius:50%; background:#fff;
  box-shadow:0 0 0 0 rgba(255,255,255,.7); animation: pulse 1.8s infinite; flex:0 0 auto;
}
.emergency-band .tel { font-weight:800; white-space:nowrap; font-size:1.05rem; }
@keyframes pulse {
  0% { box-shadow:0 0 0 0 rgba(255,255,255,.6); }
  70% { box-shadow:0 0 0 .5rem rgba(255,255,255,0); }
  100% { box-shadow:0 0 0 0 rgba(255,255,255,0); }
}
@media (prefers-reduced-motion: reduce) { .emergency-band .pulse { animation: none; } }

/* =====================================================================
   Masthead + primary nav
   ===================================================================== */
.site-head { background:#fff; border-bottom:1px solid var(--line); }
.site-head .navbar-brand { display:flex; align-items:center; gap:.8rem; padding:.4rem 0; }
.brand-logo { height:46px; width:auto; }
/* DAN partner badge (includes/dan-badge.php) - one class, sized per context.
   In the navbar it is a SIBLING of .navbar-brand, so .navbar's own flex row
   places it just right of the lockup without making it part of the home link.

   HIDDEN BELOW XL, and that is measured, not taste: free space beside the nav
   is 262px at the xxl container and 82px at xl, but only 16-30px at lg - where
   the lockup already had ~3px of slack. A badge there pushes the nav to a
   second row. The footer badge still shows at those widths, so the credential
   is never lost from the page. Re-measure if nav items change. */
/* No margin-left: .navbar-brand already carries Bootstrap's 1rem margin-end,
   which matches .foot-brand's 1rem gap - adding more read as a detached badge. */
.site-head .dan-badge { height:46px; width:auto; flex:0 0 auto; }  /* matches .brand-logo */
@media (max-width:1199.98px) { .site-head .dan-badge { display:none; } }
.brand-text { line-height:1.05; }
/* Brand lockup in Inter Tight - the title face, matching .text-logo in the
   footer. "Subaquatic Safety Services" measures ~260px at 20.4px in a
   proportional face, against ~304px of free space beside the nav - inside the
   budget with room to spare. Re-measure if nav items are ever added.
   The slogan is simply a subordinate .77 of the title - a free value, tune it
   to taste. */
.brand-text { --brand-name-size: 1.2rem; }
.brand-name { font-family:var(--font-title); font-weight:800; color:var(--navy-800);
  font-size:var(--brand-name-size); letter-spacing:-.03em; }
.brand-tag { font-family:var(--font-title); color:var(--cyan-600); font-weight:700;
  font-size:calc(var(--brand-name-size) * .77); letter-spacing:0; }
/* One tight spot: the lg container (960px) is the narrowest the nav gets while
   still expanded, and 1.2rem overflows it by exactly 3px at a 992px viewport.
   Measured: 1.1rem is the largest size that fits with 0 overflow there. Scoped
   to the lg range only - xl and up carry the full 1.2rem. Re-measure if nav
   items are added. */
@media (min-width:992px) and (max-width:1199.98px) {
  .brand-text { --brand-name-size: 1.1rem; }
}

.navbar .nav-link {
  color: var(--navy-700); font-weight:600; font-size:.92rem;
  padding:.5rem .68rem !important; border-radius:6px;
}
/* AA: --cyan-600 (#0A7B9C) on --cyan-100 measures 4.41:1 at .92rem/600 - normal
   text, so it needs 4.5. --aq-800 takes the pair to 6.9:1. Scoped, not a token
   change: --cyan-600 is fine on the untinted surfaces its other consumers use. */
.navbar .nav-link:hover { color: var(--aq-800); background: var(--cyan-100); }
.navbar .nav-link.active { color: var(--navy-900); }
.navbar .nav-link.active::after {
  content:""; display:block; height:3px; border-radius:3px;
  background: var(--orange-500); margin-top:.15rem;
}
.nav-cta {
  background: var(--emergency); color:#fff !important; border-radius:6px;
}
.nav-cta:hover { background: var(--emergency-dk); color:#fff !important; }

/* =====================================================================
   Buttons
   ===================================================================== */
.btn-emergency { background: var(--emergency); border-color: var(--emergency); color:#fff; font-weight:700; }
.btn-emergency:hover, .btn-emergency:focus { background: var(--emergency-dk); border-color: var(--emergency-dk); color:#fff; }
.btn-navy { background: var(--navy-700); border-color: var(--navy-700); color:#fff; font-weight:600; }
.btn-navy:hover, .btn-navy:focus { background: var(--navy-800); border-color: var(--navy-800); color:#fff; }
.btn-outline-navy { border:2px solid var(--navy-600); color: var(--navy-700); font-weight:600; }
.btn-outline-navy:hover { background: var(--navy-700); color:#fff; }

/* =====================================================================
   Hero
   ===================================================================== */
.hero {
  background:
    linear-gradient(180deg, rgba(10,31,68,.86), rgba(10,31,68,.94)),
    radial-gradient(120% 90% at 80% -10%, var(--navy-500), transparent 60%),
    var(--navy-900);
  color:#fff; position:relative; overflow:hidden;
}
/* Location masthead behind the hero - .hero-photo (includes/city-landing.php).
   The page supplies --hero-photo / --hero-photo-sm inline; this owns everything
   else, so adding a ninth location is a file plus a node, never a CSS edit.

   THE SCRIM IS 72->82% AND THAT NUMBER IS MEASURED, NOT EYEBALLED. These are
   bright, high-key aerials - the text zone of the lightest of them averages
   rgb(157,188,178). Contrast was computed for all eight against the
   98th-percentile-BRIGHTEST pixel under the text (not the mean, which flatters
   it), for all three inks the hero uses:

       scrim .60   fails - #D7E3F2 lede drops to 3.56:1 on koh-tao
       scrim .68   passes, but the worst cell is 4.59:1, no headroom
       scrim .72   passes with room, and is still far lighter than the .86-.94
                   the plain .hero carries, so the photograph actually reads

   The lede is the binding constraint, not the h1: it is the lowest-contrast ink
   and it sits lowest in the block, which is why the gradient darkens downward.
   Re-measure before lightening this, and re-measure when a new masthead lands -
   a darker photo is free, a brighter one is not.

   The plain .hero's radial navy glow is deliberately NOT carried over: it exists
   to give an empty navy field some shape, and a photograph does that already. */
.hero-photo {
  /* Longhand, not the background shorthand. Two things vary independently - the
     image URL at 768 and the scrim shape at 1200 - and the shorthand would force
     each media query to restate both. As a custom property the scrim is redefined
     once, in one rule, and the image swap never mentions it. */
  --hero-scrim: linear-gradient(180deg, rgba(10,31,68,.72), rgba(10,31,68,.82));
  background-color: var(--navy-900);
  background-image: var(--hero-scrim), var(--hero-photo-sm);
  background-size: auto, cover;
  background-position: center, center;
  background-repeat: no-repeat, no-repeat;

  /* THE HEIGHT TRACKS THE VIEWPORT, and that is what stops `cover` zooming in.
     These mastheads are 3:1, and a box taller than the image's own ratio forces
     cover to scale up and crop the SIDES (a fixed 26rem height cropped 38% of
     the image width at 768px and 21% at 992px). 33.33vw holds the box at
     exactly 3:1, so the full width always survives and only height is ever
     trimmed - which for a panorama is the cheap direction. The clamp floor keeps
     the hero tall enough to hold its text on a phone, where a 3:1 box would be
     125px; the ceiling stops it becoming a 850px slab on a 2560px monitor. */
  min-height: clamp(17rem, 33.33vw, 30rem);
  display: flex;
  align-items: center;
}
.hero-photo > .container { width: 100%; }

@media (max-width: 767.98px) {
  /* PHONES SEE THE RIGHT-HAND END OF THE FRAME, NOT THE MIDDLE.
     Below about 816px the box is taller than the image's own 3:1, so `cover`
     scales to fill the HEIGHT and crops the sides. A 3:1 box at 390px is 130px
     tall and will not hold the h1 and the lede, so the crop is the price of a
     readable hero here and the only real question is WHICH part it keeps.
     Every masthead is composed with dark, empty fall-off on the LEFT for the
     headline and the subject in the RIGHT third; a centre crop keeps the seam
     between the two and loses both. Pinned right, the visible slice is 37.5% of
     the frame at 320px and 41.3% at 390px - still the whole right third, which
     is the number that matters. The scrim layer stays centred; it is a gradient
     and fills the box either way.

     THE EXTRA HEIGHT COSTS WIDTH, deliberately. Height drives the scale, so a
     taller box zooms further in: at 390px, 294px -> 315px of height moves the
     crop 55.7% -> 58.7%. Do not chase the crop back by growing this further;
     past ~24rem even the right third is zoomed past legibility.

     CONTRAST at 390x315, 98th-percentile-brightest pixel under each ink, scrim
     composited: worst cell 5.8:1 (eyebrow on /faq), worst lede 6.7:1, worst h1
     7.6:1 - all clear the 4.5 floor. The binding ink is the EYEBROW here, not
     the lede as at desktop: it is small, it sits high, and the vertical scrim is
     at its lightest (.72) at the top of the box. Re-measure before lightening
     the scrim, and when a masthead is replaced.

     THE `top` IN `right top` IS INERT TODAY, and it is written out so the intent
     is on the record. `cover` scales to whichever axis needs more, and on a
     phone that is always the height: at 390x315 a 1100x367 master lands
     944x315, i.e. 554px of horizontal overflow and ZERO vertical, so the Y
     component has nothing to move. It only starts to matter if the box ever
     gets wider than 3:1 or a master arrives at some other ratio. Real vertical
     framing on a phone needs a differently-shaped master out of
     tools/derive-location-mastheads.php, not a change to Y. */
  .hero-photo {
    background-position: center, right top;
    min-height: clamp(19rem, 88vw, 21rem);
  }
}

@media (min-width: 768px) {
  /* Only the image swaps here: the clamp above already scales with the
     viewport, so a second fixed min-height would just re-introduce the crop. */
  .hero-photo { background-image: var(--hero-scrim), var(--hero-photo); }
}

@media (min-width: 1200px) {
  /* THE SCRIM GOES HORIZONTAL, and this is what actually makes the photograph
     read. A uniform wash has to be dark enough for the text everywhere, including
     the two-thirds of the frame with no text on it. Left-aligned copy does not
     need that: the widest thing in the hero is the lede at 60ch (~552px), and at
     >=1200 the container puts it inside the left half at every width -
     1200 -> text ends at 49.5%, 1440 -> 46%, 1920 -> 45%.

     So the dark end is .88->.84 across that half - HEAVIER than the .72-.82 the
     uniform version used, so text contrast improves rather than degrades - and it
     falls to .28 at the right edge, where the photograph shows at ~70% and finally
     looks like a photograph.

     Gated at 1200 and not 768 deliberately: at 768 the container is 720px and the
     same 552px lede runs to 78% of the viewport, straight through the light end.
     Below 1200 the uniform scrim stays. */
  .hero-photo {
    --hero-scrim: linear-gradient(90deg,
      rgba(10,31,68,.88) 0%, rgba(10,31,68,.84) 48%,
      rgba(10,31,68,.55) 74%, rgba(10,31,68,.28) 100%);
  }
  /* THE LEDE IS CAPPED SHORTER ON A PHOTO HERO, and this is what keeps the
     gradient above honest.

     1ch is 0.680em in Inter, not the 0.5 a first pass assumes - measured off the
     '0' glyph, and worth checking rather than guessing, because it makes the
     site-wide 60ch lede box 751px and not 552px. That put its right edge at 57%
     of the viewport at 1440 and 66% at 1200, i.e. out in the falloff at ~.64
     scrim, under the .68 floor established for these photographs.

     48ch puts it back at 600px, inside the plateau at 1440 and at .78 in the
     worst case (1200px viewport, where the h1's own 607px box is now the widest
     thing in the hero). Both are above the .72 that was verified safe.

     Capping the text rather than flattening the gradient: the falloff is what
     makes the photograph read, and a 48ch measure is good typography anyway. The
     plain .hero keeps 60ch - it has no photograph to lose. */
  .hero-photo .lede { max-width: 48ch; }
}
.hero .container, .hero .container-fluid { position:relative; z-index:2; }
.hero .eyebrow { color: var(--cyan-200); text-transform:uppercase; letter-spacing:.08em; font-weight:700; font-size:.8rem; }
.hero h1 { color:#fff; font-weight:800; font-size: clamp(1.9rem, 1.3rem + 2.4vw, 3.1rem); letter-spacing:-.03em; max-width: 18ch; line-height: .9em; }
.hero .lede { color:#D7E3F2; font-size:1.15rem; max-width: 60ch; }

/* Hero brand lockup - mark left, three-line wordmark right.
   The compact 371×279 mark is built for dark surfaces (transparent PNG,
   white/orange waves) so it sits directly on the video/navy backdrop - no
   chip. 140px at desktop roughly matches the three-line wordmark; the ramp
   floors at 84px on phones, where the lockup stacks. flex-wrap is the
   safety net if a mid width runs out of room. While this lockup is on
   screen the nav logo hides - html.hero-in-view, next to the is-condensed
   brand rules. */
.hero-brand { display:flex; flex-wrap:wrap; align-items:center; gap:1rem 1.25rem; margin-bottom:1rem; }
.hero-brand-logo {
  flex:0 0 auto; width:auto; max-width:100%;
  height:clamp(84px, 10vw + 15px, 140px);
}
/* The hero wordmark is a TITLE, so it takes --font-title from the h1–h6 rule
   and the hero's own -.03em tracking from .hero h1 - both by inheritance, set
   nowhere here. */
.hero-brand h1 { margin-bottom:0; }
/* THE LOCKUP STAYS ON ONE ROW AT EVERY WIDTH - no column stack on phones.
   Measured, mark + gap + wordmark against the container:

       320px   needs 261px   has 298px
       390px   needs 263px   has 368px
       575px   needs 288px   has 552px

   The wordmark is three short words that wrap internally to three lines beside
   the mark - "Subaquatic / Safety / Services" - so the h1 only ever asks for
   ~145-172px however narrow the screen gets, while the mark floors at 84px tall
   and about 96px wide.

   flex-wrap is the safety net: if a longer wordmark or a bigger mark ever does
   exhaust the row, it drops to a second line by itself rather than pushing the
   hero sideways. Check 320px before changing the mark's clamp or the gap - that
   is where the margin is thinnest, at 37px. */
/* =====================================================================
   HERO TEXTURE - the wave field, default on every hero.

   A simplified, single-colour line motif *inspired by* the mark's three
   waves, not the mark itself (never recolour/rebuild the real logo - see
   brand-logo guidelines).

   THIS IS THE ONLY TEXTURE ON THE SITE: the headers, plus the same waves over
   the figures (same motif, same .06, dark ink for their light ground - that
   rule lives beside the .ph-media styles, not here). Every other body surface
   is a flat colour.

   ONE OPACITY, EVERYWHERE: .06, with strokes 1 / 0.7 / 0.45, so net alpha is
   .060 / .042 / .027 on every header. If a header ever needs its own rule, it
   must land on those three numbers - at .55 the waves read nine times stronger
   than the rest of the site.

   ONE OPT-IN VARIANT: /about (.hero-surface) re-inks the same waves dark
   (#0A7B9C) at the same .06, because it is a light aqua hero where the pale
   #BDEBF8 ink is invisible. Ink follows the surface; strength does not change.

   Single layer, so background-size/position are set explicitly.
   ===================================================================== */
.hero::after {
  content:""; position:absolute; inset:0; z-index:1; pointer-events:none;
  opacity:.06;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='48' viewBox='0 0 40 48'%3E%3Cpath d='M0,10 Q5,5 10,10 T20,10 T30,10 T40,10' fill='none' stroke='%23BDEBF8' stroke-width='1.5'/%3E%3Cpath d='M0,24 Q5,19.5 10,24 T20,24 T30,24 T40,24' fill='none' stroke='%23BDEBF8' stroke-width='1.5' opacity='0.7'/%3E%3Cpath d='M0,38 Q5,34 10,38 T20,38 T30,38 T40,38' fill='none' stroke='%23BDEBF8' stroke-width='1.5' opacity='0.45'/%3E%3C/svg%3E");
  background-repeat: repeat;
  background-size: auto;
  background-position: 0 0;
}

/* =====================================================================
   BODY SURFACES ARE FLAT - the only textured things below the header are
   the figures (see the wave overlay beside the .ph-media styles).
   =====================================================================
   The .hero-map class still on the /network* templates is inert: those heroes
   fall through to plain .hero::after and are identical to every other header.

   The scaffolding below stays. `position:relative` + `overflow:hidden` and the
   z-index:2 on children keep content above any pseudo-element, but they also
   constrain anything else these surfaces contain, so removing them is a
   separate change with its own layout risk - not a tidy-up.
   ===================================================================== */
.bg-navy, .site-footer { position: relative; overflow: hidden; }
.bg-navy > *, .site-footer > * { position: relative; z-index: 2; }

/* =====================================================================
   The network explorer band (/network/ only) - flat aqua tint, no texture.
   =====================================================================
   THE TINT IS NOT A TEXTURE AND STAYS. --aq-100 is one step lighter than the
   interactive map's own --aq-200 ocean, which is what still lets the map panels
   read as panels sitting ON the band rather than merging into it. Flattening this
   to --foam would lose that separation.
   ===================================================================== */
.section-explorer { background: var(--aq-100); position: relative; overflow: hidden; }
.section-explorer > * { position: relative; z-index: 2; }
/* This is about the TINT: --cyan-600 #0A7B9C scores 4.55:1 on foam but only
   4.41:1 on --aq-100, i.e. under AA. Deepened to #095B75 - still unmistakably
   aqua, 6.9:1 on the band. Scoped to this band on purpose - .map-region-title
   on an untinted surface is fine at 4.55:1, and --cyan-600 has 12 other
   consumers that must not move. If the band tint ever darkens, re-measure this
   pair together. */
.section-explorer .map-region-title { color: var(--aq-800); }

/* ---- HOME HERO SIZING - the two knobs live here ----------------------
   --hero-min-h : how much of the screen the hero fills (60vh = 60%).
                  Use calc(90vh - 6.5rem) if you'd rather it fit *below*
                  the sticky header rather than behind the fold.
   --hero-gap   : breathing room between the lede text and the buttons.
   Scoped to .hero-action so the About descent hero is untouched. */
.hero-action {
  --hero-min-h: 60vh;
  --hero-gap: 2.5rem;
  min-height: var(--hero-min-h);
  display: flex; align-items: center;
}
.hero-action > .container { width: 100%; }
.hero-action .lede { margin-bottom: var(--hero-gap); }
.hero-action .hero-actions { margin-bottom: 1.25rem; }
/* short screens (laptops, landscape phones): don't force 60vh of empty space */
@media (max-height: 620px) { .hero-action { --hero-min-h: auto; } }

/* =====================================================================
   Sections / cards

   SECTION RHYTHM - the whole site's vertical spacing is driven from the
   two custom properties below. To give every section more air, raise
   --section-pad. To make one section roomier, add .section-tall to it
   (or set --section-pad on that section alone).
       clamp(MIN, PREFERRED, MAX) → MIN at narrow screens,
       MAX at wide screens, fluid in between.

   REMEMBER THE GAP IS DOUBLE THE NUMBER: padding is top AND bottom, so two
   adjacent default sections sit ~90px apart at 1280px. That doubling is why a
   change that looks small here is large on the page.
   ===================================================================== */
:root {
  --section-pad:      clamp(1.75rem, 1rem + 2.25vw, 3.25rem); /* default rhythm */
  --section-pad-tall: clamp(2.5rem, 1.5rem + 3.5vw, 5rem);    /* .section-tall  */
}
.section { padding: var(--section-pad) 0; }
.section-tall { --section-pad: var(--section-pad-tall); }
.section-eyebrow { color: var(--cyan-600); text-transform:uppercase; letter-spacing:.07em; font-weight:700; font-size:.78rem; }
.lead-navy { color: var(--navy-700); }

/* AA on the tinted band only. --cyan-600 (#0A7B9C) is 4.55:1 on the default
   --mist body background - a pass, but by 0.05 - and only 4.07:1 on --mist-2
   (#E7EBF5), which is a fail. Both selectors below appear inside .bg-mist-2
   sections (home-content, faq, divers, city-landing), so the override is scoped
   to that surface and the untinted case keeps the lighter aqua. */
.bg-mist-2 .section-eyebrow,
.bg-mist-2 .coast-strip a:hover { color: var(--aq-800); }

.card-soft {
  background:#fff; border:1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-sm); height:100%;
  /* Flex column like Bootstrap's own .card. Without this .card-soft is a
     block: its .card-body only grows to content height, so on an h-100 card
     the stretch-to-row-height space fell BELOW the body and any mt-auto
     footer had no free space to push into (computed margin-top:auto → 0).
     That left button rows unaligned across a row of uneven-length cards
     (e.g. contact chambers with 2- vs 3-line addresses). */
  display:flex; flex-direction:column;
}
.card-soft > .card-body { padding:1.4rem; flex:1 1 auto; }
.card-link-tile { transition: transform .15s ease, box-shadow .15s ease; }
.card-link-tile:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); text-decoration:none; }

/* Emergency-first block - the 6 canonical steps (includes/emergency-steps.php).
   Each row: [numbered circle] [text]. The circle is absolutely positioned so it
   sits outside the flex flow; the row is display:flex so .es-text can take
   min-width:0 and wrap. --es-row-pad tunes the height of the block: raise
   it to make the steps breathe more. */
/* ---- SURFACE IS AQUA, ALERT CUES STAY ORANGE ----
   The 4px orange bar on this block and the --emergency numeral discs stay
   orange deliberately: role discipline in :root gives orange to action and
   alert ONLY, and this block is the first-aid procedure - so the surface can be
   calm while the cues that say "this is an emergency" stay orange. Recolouring
   the numerals would also cost contrast: white needs --aq-700 #0A7B9C (4.85:1)
   as its floor on aqua, against 5.09:1 on #C73E0F.

   Border and divider share one token on purpose - --aq-300 on --aq-100 is
   1.414:1, and the difference between the two would not be visible.

   Text on the fill keeps full headroom: --ink 15.88:1, --slate 8.57:1,
   --navy-800 14.16:1.

   CHANGING THESE ALSO CHANGES /contact. contact.php reuses the
   .emergency-steps shell for a banner with no list in it. That is intended
   consistency, not a leak - but check it after any edit here. */
.emergency-steps { --es-row-pad: 1rem;
  background: var(--aq-100); border:1px solid var(--aq-300); border-radius: var(--radius); }
.emergency-steps ol { counter-reset: step; list-style:none; padding-left:0; margin:0; }
.emergency-steps li { position:relative; border-top:1px dashed var(--aq-300);
  padding: var(--es-row-pad) 0 var(--es-row-pad) 3rem;
  display:flex; align-items:center; gap:.9rem; }
.emergency-steps li:first-child { border-top:0; }
.emergency-steps li::before {
  counter-increment: step; content: counter(step);
  position:absolute; left:0; top:50%; transform:translateY(-50%);
  width:2rem; height:2rem; border-radius:50%;
  background: var(--emergency); color:#fff; font-weight:800;
  display:flex; align-items:center; justify-content:center;
}
.es-text { min-width:0; }   /* let long text wrap instead of overflowing the flex row */

/* =====================================================================
   FAQ disclosures (faq.php)
   Native <details>/<summary>: keyboard operable, exposed to screen readers,
   and findable by in-page search - all without a line of JS. If CSS never
   loads, the browser's default disclosure still works.
   Card-soft in a different key: same white/line/shadow, plus an aqua left
   rule (aqua = water/highlights) that deepens when the answer is open.
   ===================================================================== */
.faq-list { display:grid; gap:.7rem; }
.faq-item {
  background:#fff; border:1px solid var(--line); border-left:3px solid var(--aqua);
  border-radius: var(--radius); box-shadow: var(--shadow-sm);
}
.faq-item[open] { border-left-color: var(--cyan-600); box-shadow: var(--shadow-md); }
.faq-q {
  display:flex; align-items:flex-start; justify-content:space-between; gap:1rem;
  padding:1rem 1.2rem; cursor:pointer;
  font-family: var(--font-title); font-weight:700; letter-spacing:-.01em;
  color: var(--navy-800); line-height:1.3;
}
.faq-q { list-style:none; }                             /* Firefox */
.faq-q::marker { content:""; }                          /* Firefox, older */
.faq-q::-webkit-details-marker { display:none; }        /* Safari */
.faq-q:hover { color: var(--cyan-600); }
.faq-q:focus-visible { outline:2px solid var(--cyan-600); outline-offset:2px; }
.faq-chev { flex:0 0 auto; width:1.15rem; height:1.15rem; margin-top:.15rem;
  color: var(--cyan-600); transition: transform .15s ease; }
.faq-item[open] .faq-chev { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .faq-chev { transition:none; } }
.faq-a { padding:0 1.2rem 1.2rem; color: var(--slate); }
.faq-a p { margin-bottom:.7rem; }

/* =====================================================================
   Schematic network map
   ===================================================================== */
.network-map { position:relative; background: var(--navy-900); border-radius: var(--radius); padding:1.2rem; }
.network-map svg { width:100%; height:auto; display:block; }
.map-node { cursor:pointer; }
/* Pin fill. The facility type still lives on every node in includes/nodes.php
   and on the map group's data-type attribute, but nothing renders it - every
   pin is one navy. Navy is also a legibility fix, not a style choice - see the
   stroke note under .map-light: a cyan-400 fill measures 1.47:1 against the
   light ocean, this is 10.63:1. */
.map-node .dotmark { fill: var(--navy-700); }

/* click-to-reveal overlay drawn over the map */
.map-overlay { position:absolute; inset:0; border-radius:inherit; z-index:3;
  display:flex; align-items:flex-start; justify-content:flex-end; padding:1rem;
  background:rgba(10,31,68,.55); }
.map-overlay[hidden] { display:none; }
.map-overlay-card { position:relative; background:#fff; border-radius:var(--radius);
  box-shadow: var(--shadow-md); padding:1.1rem 1.2rem; width:min(320px,100%);
  max-height:100%; overflow:auto; }
.map-overlay-close { position:absolute; top:.35rem; right:.55rem; border:0; background:transparent;
  font-size:1.5rem; line-height:1; color:var(--slate); cursor:pointer; padding:.15rem .35rem; }
.map-overlay-close:hover { color:var(--ink); }
@media (max-width:575.98px){ .map-overlay { align-items:flex-end; justify-content:stretch; } .map-overlay-card { width:100%; } }
/* The operator line in the overlay card. */
.map-detail-op { color: var(--cyan-600); font-weight:600; font-size:.9rem; margin-bottom:.5rem; }

/* =====================================================================
   Regional map panels - light cartographic theme (/network/)
   ===================================================================== */
.map-region-title { color: var(--cyan-600); text-transform:uppercase; letter-spacing:.07em;
  font-weight:700; font-size:.78rem; margin-bottom:.5rem; }

.network-explorer { position:relative; }                    /* positioning context for the shared overlay */
.network-explorer > .map-overlay { border-radius:var(--radius); align-items:center; justify-content:center; }
@media (max-width:991.98px){
  .network-explorer > .map-overlay { position:fixed; inset:0; border-radius:0;
    align-items:flex-end; justify-content:center; }
}

.map-light { background: var(--aq-200); border:1px solid var(--line); padding:.65rem; }
.map-light .land { fill:#FDFEFF; stroke:#5470C0; stroke-width:.8; }
.map-light .lbl { fill: var(--navy-800); font:600 13px "Helvetica Neue",Arial,sans-serif; }
.map-light .map-node:focus { outline:none; }
.map-light .map-node circle.hit { fill:transparent; }
/* The stroke no longer reads as a rim: the fill is --navy-700 at 10.63:1
   against this light ocean (a cyan-400 fill would be 1.47:1, a navy outline
   round a pale centre) and carries its own boundary, so the stroke is only
   1.15:1 against it. Kept because removing it would shrink every pin by 0.75px
   a side, which is a sizing change - not because it still does work. */
.map-light .map-node .dotmark { stroke:#102249; stroke-width:1.5; }
/* Hover/focus ring. Navy on navy still reads: the ring is r=16 and the dot r=7
   (includes/network-map.svg.php), so ~7px of light ocean separates them. Do not
   close that gap - the ring is the only hover affordance the map has. */
.map-light .map-node .ring { fill:none; stroke:#102249; stroke-width:0; opacity:0; transition:.15s; }
.map-light .map-node:hover .ring, .map-light .map-node:focus .ring,
.map-light .map-node.selected .ring { stroke-width:3; opacity:.85; }
.map-light .map-node.dimmed { opacity:.22; pointer-events:none; }

/* Riviera Maya detail inset */
.map-light .inset-frame { fill: var(--aq-200); stroke:#102249; stroke-width:1.5; }
.map-light .inset-src   { fill:none; stroke:#102249; stroke-width:1.2; }
.map-light .inset-lead  { stroke:#102249; stroke-width:1; stroke-dasharray:3 4; opacity:.6; }
.map-light .inset-title { fill: var(--navy-800); font:700 11px "Helvetica Neue",Arial,sans-serif;
  letter-spacing:.06em; text-transform:uppercase; }

/* Location cards - place lists linked to location pages */
.loc-list { list-style:none; padding:0; margin:0; }
.loc-list li { border-top:1px solid var(--line); }
.loc-list li:first-child { border-top:0; }
.loc-list a { display:flex; align-items:center; justify-content:space-between;
  padding:.5rem .25rem; color:var(--navy-700); font-weight:600; text-decoration:none; }
.loc-list a::after { content:"\203A"; color:var(--cyan-500); font-size:1.1rem; opacity:.7; }
.loc-list a:hover { color:var(--cyan-600); }
.loc-list a:hover::after { opacity:1; }

/* Home-page network teaser - one row per country. Same visual language as
   .loc-list (hairline rows, chevron, cyan hover) but three columns instead of
   one, because the row has to carry the coast and the count as well as the
   name. Colours are set explicitly here - there is no .text-slate utility; if
   you want slate text, set `color: var(--slate)` on the component. */
.coast-strip { list-style:none; padding:0; margin:0; border-top:1px solid var(--line); }
.coast-strip li { border-bottom:1px solid var(--line); }
.coast-strip a { display:flex; align-items:baseline; gap:.9rem; padding:.9rem .25rem;
  color:var(--ink); text-decoration:none; }
.coast-strip a:hover { color:var(--cyan-600); }
.coast-strip a::after { content:"\203A"; color:var(--cyan-500); font-size:1.1rem;
  opacity:.7; margin-left:auto; padding-left:.5rem; align-self:center; }
.coast-strip a:hover::after { opacity:1; }
.coast-strip .cs-name  { font-weight:700; flex:0 0 auto; min-width:10rem; }
.coast-strip .cs-coast { color:var(--slate); flex:0 1 auto; }
/* --steel would read better in the hierarchy here but measures 3.75:1 on
   --mist-2 at this size; --slate is 7.91:1. The mono face and the smaller
   size carry the hierarchy instead of the colour. */
.coast-strip .cs-meta  { color:var(--slate); font-family:var(--font-mono); font-size:.74rem;
  white-space:nowrap; margin-left:.6rem; }
/* Narrow: the name takes its own line and the coast becomes the second line,
   so the country is always the first thing scanned. */
@media (max-width:575.98px) {
  .coast-strip a { flex-wrap:wrap; gap:.25rem .9rem; }
  .coast-strip .cs-name { flex:1 0 100%; min-width:0; }
  .coast-strip a::after { margin-left:auto; }
}

/* Country slider (scroll-snap) */
.country-slider { display:flex; gap:1rem; overflow-x:auto; scroll-snap-type:x mandatory; padding-bottom:.6rem; }
.country-slider > * { scroll-snap-align:start; flex:0 0 clamp(240px, 78%, 320px); }

/* =====================================================================
   Location / QR landing
   ===================================================================== */
.call-bar .btn { font-size:1.05rem; }

/* =====================================================================
   Footer
   ===================================================================== */
/* The wave that carries the last band into the footer. The path is filled
   navy-900 - the footer's colour - so the SHAPE is the footer rising.

   ABOVE THE CURVE THE SVG IS TRANSPARENT, and transparent here means the PAGE
   BACKGROUND shows through, never the section above it. That is invisible only
   while the last band happens to be the page colour (--mist IS --foam IS the
   body background); any other last band needs its colour painted behind the
   wave. $waveSurface in includes/footer.php names the last band's colour; add a
   .wave-on-* here for any new one. */
/* THE -1px KILLS A PALE HAIRLINE ALONG THE FOOTER'S TOP EDGE. It is not a
   spacing tweak and removing it brings the line back.

   The wave and the footer already touch exactly - measured, the gap is 0. The
   seam is subpixel: the boundary lands on a fractional y (3128.515625 at one
   tested scroll position), and the 44-unit viewBox scaled into 40px means the
   path's last row is anti-aliased rather than solid. The browser blends that row
   against what sits behind it, which is this element's own background - so the
   line takes the colour of the band ABOVE the wave, not the footer below it.
   That is why it read as light blue: --aq-200 while home's trust band was aqua,
   and --foam on every other page, where no .wave-on-* is set and the background
   falls through to the page.

   Pulling the footer up one pixel covers the blended row with flat --navy-900.
   Nothing shifts visually: the row being covered is the bottom of a navy path
   sitting against a navy footer. Fixes every page at once, not just home. */
.wave-divider { display:block; width:100%; height:40px; margin-bottom:-1px; color: var(--navy-900); }
.wave-divider path { fill: currentColor; }
.wave-on-aqua   { background: var(--aq-200); }
/* /emergencies ends on .bg-mist-2 - a deeper tint than the page background, so
   without this the wave would paint a pale step. */
.wave-on-mist-2 { background: var(--mist-2); }
/* Home's last band. */
.wave-on-white { background: #fff; }
.site-footer { background: var(--navy-900); color:#C7D6EA; }
.site-footer a { color:#9FE0EE; }
.site-footer a:hover { color:#fff; }
.site-footer .foot-meta { color:#8198BA; font-size:.85rem; }

/* Footer brand - the mark beside the logotype (includes/footer.php).
   The 371x279 mark is the same asset the header and hero use, and it is built
   for dark surfaces (transparent PNG, white/orange waves), so it needs no chip
   on the navy footer. */
/* THREE-PART LOCKUP: SSS mark · wordmark · DAN badge, in that order, holding
   that arrangement at every width.

   The rule that keeps this honest: the two marks are images at a fixed height,
   so any width they lose is distortion, not scaling. Only .text-logo may shrink,
   and it absorbs the deficit by wrapping its own subtitle.

   align-items:center is what puts the wordmark visually between the two marks
   rather than hanging them off a shared top edge. */
.foot-brand {
  display:flex;
  align-items:center;
  /* THE ONE SIZE THAT DRIVES THE WHOLE LOCKUP. It sets the wordmark type, and
     the two marks are derived from it below, so they cannot drift apart. It
     lives HERE and not on .text-logo because custom properties inherit down,
     not sideways - the images are siblings of the text, so a token on the text
     would be invisible to them. */
  --foot-name-size: 1.2rem;
  /* nowrap IS CORRECT - keep it. Flex resolves wrapping BEFORE shrinking, so
     `wrap` means the line breaks the instant the natural widths exceed the
     column and .text-logo is never asked to give way at all. Measured: with
     wrap on, the DAN badge dropped to a second row at every width from 320 to
     1920, including 1440 where the shortfall was 14px out of 550. nowrap is
     what forces the shrink to happen instead. */
  flex-wrap:nowrap;
  gap:clamp(.5rem, 2.2vw, 1rem);
}
/* THE MARKS MUST NOT SHRINK. Both are fixed-height replaced elements, so their
   width is the flex main axis while height is pinned by the calc() below.
   A flex item's default `min-width:auto` resolves, for an image, to its
   content-derived width - height x intrinsic ratio - and that automatic minimum
   is the ONLY thing holding the ratio. `min-width:0` switches it off;
   flex-shrink:1 then compresses width while the fixed height refuses to move,
   and both PNGs squash horizontally (measured 34.4% at 1024px, 21.6% at 1280px,
   0% at 360px - the phone is the one width that stays right).

   flex:0 0 auto also matches .site-head .dan-badge, which uses the same asset
   at a fixed height. If the row overflows, fix it by giving the TEXT room to
   give way (.text-logo below is the shrinkable item), never by letting these
   two shrink. */
.foot-brand .foot-mark,
.foot-brand .dan-badge { flex:0 0 auto; width:auto; }
/* SIZED FROM THE TYPE, not from the viewport. THE MULTIPLIER IS THE NAVBAR'S
   OWN PROPORTION: .brand-logo is 46px against a 1.2rem (20.4px) name, i.e.
   2.25x. Reproducing that ratio here is what makes the two lockups look like
   the same object at different scales, and it is the reason this is a calc()
   rather than a hardcoded clamp - change the type size and the marks follow by
   construction. The badge keeps a slight step-down (72:74 of the mark, so
   2.19x), which compensates for its squarer aspect. */
.foot-brand .foot-mark { height: calc(var(--foot-name-size) * 2.25); }
.foot-brand .dan-badge { height: calc(var(--foot-name-size) * 2.19); }
/* 0 1 auto, NOT 1 1 auto: may shrink, must NOT grow. This is the one item in the
   lockup allowed to give way, but flex-grow:1 would make it claim the whole line
   and push the DAN badge onto a second row at EVERY width - even 1920px, where
   there is room to spare. Growing is not what "gives way" means here. */
.foot-brand .text-logo { flex:0 1 auto; min-width:0; }

/* Footer logotype lockup - .text-logo (includes/footer.php). Mirrors the
   header: "Subaquatic Safety Services" on one line in mixed case, tagline
   beneath.

   TYPOGRAPHY IS PORTED FROM THE NAVBAR, COLOUR IS NOT. Sizes, weights, tracking
   and the .77 subtitle factor all match .brand-name / .brand-tag. The navbar's
   navy-on-white colours do not come with them: this lockup sits on --navy-900
   and keeps the footer's own light palette.

   THE .77 FACTOR IS NOT ARBITRARY. It makes the tagline measure the SAME width
   as the name at every size (both 248px at 1.2rem, measured) - a near-flush
   right edge in a proportional face.

   FOOTER-SCOPED CLASS NAMES, DELIBERATELY. Do not "share" the navbar's
   .brand-name / .brand-tag here. .brand-tag is wired to the header scroll state
   (html.is-condensed .brand-tag { height:0; opacity:0 }), and is-condensed lives
   on <html>, so reusing it would collapse the FOOTER tagline every time the
   header condensed. */
.text-logo {
  font-family: var(--font-title);
  line-height: 1.05;              /* .brand-text */
  max-width: 100%;
}
.foot-brand-name {
  display: block;                 /* the line break - there is no <br> */
  font-size: var(--foot-name-size);
  font-weight: 800;
  letter-spacing: -.03em;
  color: #fff;
}
.foot-brand-tag {
  display: block;
  font-size: calc(var(--foot-name-size) * .77);
  font-weight: 700;
  letter-spacing: 0;
  /* The footer's own accent, the same one .site-footer a uses. It stands in for
     the navbar's --cyan-600 tagline, which is 3.0:1 on this navy and unusable. */
  color: #9FE0EE;
}

/* THE NAME SITS ON ONE LINE FROM 375px UP. The string wants 248px; deriving the
   mark heights from the type (the calc() above, 46px/45px marks) is what leaves
   the wordmark that much at every width from 992 up - oversized marks would eat
   the row and wrap it.

   MEASURE THE CONTENT BOX, NOT THE COLUMN RECT. A Bootstrap column carries
   12.75px of padding each side, so getBoundingClientRect() overstates the usable
   width by 25.5px. Use clientWidth minus padding.

   320px STILL WRAPS TO TWO LINES and that is left alone: the smallest phones
   cannot offer 248px at any mark size worth having.

   The step-down below mirrors the navbar's own at this band. It also steps the
   marks, since both derive from the same token. */
@media (min-width:992px) and (max-width:1199.98px) {
  .foot-brand { --foot-name-size: 1.1rem; }
}

/* =====================================================================
   Utilities
   ===================================================================== */
.eyebrow-rule { width:48px; height:3px; background: var(--orange-500); border-radius:3px; }
.small-print { color: var(--slate); font-size:.85rem; }
/* Colour is measured against the chip's OWN #FCE9DF background, not whatever
   surface the tag sits on - the chip is opaque, so the page behind it never
   reaches the glyphs. --orange-700 #C73E0F on that chip is 4.33:1, under AA at
   this size, and it was under everywhere the tag appears rather than on any one
   page. --emergency-dk #9A2E0B is 6.45:1 on the same chip.
   Keep any future colour change measured against #FCE9DF, not the page. */
.verify-tag { font-size:.7rem; font-weight:700; color: var(--emergency-dk); background:#FCE9DF; border-radius:4px; padding:.05rem .3rem; vertical-align:middle; }

/* =====================================================================
   Brand accent moments - surface the two vibrant basics (AA-safe).
   Rescue Orange #FF5A1E + Aqua #16BCE6 used ONLY as fills/affordances
   (both fail AA as body text on light). Maps to the manual's roles:
   orange = signal/CTA/alert · aqua = water/links/highlights.
   ===================================================================== */

/* Orange CTA - non-emergency primary action. Navy-900 text on orange is
   4.96:1 (passes AA); white would be only 3.1:1. Emergency CTAs keep the
   darker --emergency, so the two oranges never compete for "alert". */
.btn-orange { background: var(--orange); border-color: var(--orange); color: var(--navy-900); font-weight:700; }
.btn-orange:hover, .btn-orange:focus { background: var(--or-600); border-color: var(--or-600); color: var(--navy-900); }

/* Orange alert rule - bright accent down the LEFT edge of the emergency-first
   steps block. */
.emergency-steps { border-left: 4px solid var(--orange); }

/* =====================================================================
   Emergency step PLATES - the 6-up manual grid (includes/emergency-plates.php)
   /emergencies only. A second layout for the same six steps; the compact
   .emergency-steps list is unchanged and still used on 11+ other URLs.

   SEPARATE NAMESPACE ON PURPOSE. contact.php reuses the .emergency-steps
   class shell for a banner containing no list at all, so that namespace
   cannot absorb grid rules. Nothing below touches it.

   Columns (Bootstrap row-cols in the include): 1 → 2 (sm) → 3 (md) → 6 (xl).
   Six across only at xl, because the 1180px container divided six ways is
   ~180px per plate; at lg it would be ~155px, and step 4's lead ("If the diver
   is unresponsive but breathes normally, use the recovery position.") is long
   enough that 155px is worth eyeballing before committing to it.
   ===================================================================== */
.es-plates { list-style: none; padding-left: 0; margin: 0; counter-reset: none; }
.es-plate {
  height: 100%;
  display: flex; flex-direction: column;
  background: var(--aq-100);           /* same aqua as the compact block */
  border: 1px solid var(--aq-300);
  border-top: 4px solid var(--orange); /* same alert bar, same meaning */
  border-radius: var(--radius);
  padding: 1rem .9rem .6rem;
}
/* The numeral repeats the compact list's token exactly - 2rem disc, --emergency
   fill, white 800 - so a reader who met the steps on the home page recognises
   the same component here. aria-hidden in the markup: the <ol> already conveys
   order, and a screen reader announcing "1" twice is noise. */
.es-plate-num {
  width: 2rem; height: 2rem; flex: 0 0 auto; margin: 0 0 .6rem;
  border-radius: 50%;
  background: var(--emergency); color: #fff;
  font-weight: 800; font-size: .95rem; line-height: 2rem; text-align: center;
}
.es-plate-title {
  font-size: .95rem; font-weight: 700; line-height: 1.25;
  color: var(--navy-800); margin: 0 0 .35rem;
}
/* No bottom margin on the last element in the cell - the drawing that used to
   follow it is gone, so the .75rem it reserved would just pad the cell floor. */
.es-plate-body {
  font-size: .82rem; line-height: 1.45; color: var(--slate);
  margin: 0;
}
/* .es-plate-fig, its svg rule and .es-dash lived here and styled the 200x160
   drawing at the bottom of each plate; `margin: auto 0 0` pinned all six to a
   common baseline. Removed with the artwork on 2026-07-30. The plate is still a
   flex column - that is what keeps the six cells equal height via `height:100%`,
   independent of the drawings. */

/* THE PHONE BLOCK THAT USED TO BE HERE IS GONE, and the reason is worth keeping.
   It shrank the plate padding and both type sizes under 576px:

       .es-plate       { padding: .8rem .7rem .5rem; }
       .es-plate-title { font-size: .88rem; }
       .es-plate-body  { font-size: .78rem; }

   That existed because the grid was row-cols-2, so a 393px phone gave each plate
   about 180px and the text had to be trimmed to fit it. The grid is row-cols-1
   from 2026-07-31 - one step per row, full width - so each plate now has roughly
   369px and the shrink works against the reader instead of for them. Emergency
   copy read on a phone is the last place to be setting body text at .78rem.

   If the grid ever goes back to two across on a phone, this block comes back
   with it. They are one decision, not two. */

/* =====================================================================
   MANUAL FIGURES LIVED HERE - .manual-figs, .manual-fig, .manual-fig-stage and
   .fig-copy, the numbered technical drawings set beside body copy on
   /emergencies so the page read as a medical manual rather than an article.

   All of it went on 2026-08-05 with the six DCI illustrations and their renderer
   (includes/dci-figures.php). Nothing emitted these classes anywhere else - the
   treatment-profile charts on /standards used .tt-card from
   includes/treatment-tables.php and always did. Those charts and that include
   went too, on 2026-09-01, so /standards now draws no figures at all.

   THREE FINDINGS WORTH KEEPING, because any replacement meets them again:

   1. The figure number came from a CSS COUNTER on .manual-fig, reset per page,
      never typed. The .cm-mark markers on /standards were hand-numbered instead,
      and that file carried a shouted warning about renumbering when an item
      moved - the cost of the other choice. (They went on 2026-09-01 with the
      chamber specification; the finding is kept because it outlives them.)
      A counter cannot drift; prefer it.
   2. The copy/figure split was gated at xl, NOT lg, and that was load-bearing.
      These masters were 1536px with ~30px embedded lettering. Side by side a
      30px label lands near 14px at >=1400 and 12px at 1200 - small but readable;
      at lg the container is 936px, which puts it near 9px, past the point where
      the figure means anything. Moving such a split to -lg- silently breaks the
      figures on every 13" laptop.
   3. DOM order was always copy-then-figure, with .flex-xl-row-reverse doing the
      alternation visually. .row wraps, so a plain .flex-row-reverse would also
      reverse the STACKED order on phones - the classic row-reverse-plus-wrap
      trap - and stacked, copy-then-picture is the order that reads.

   Earlier still, these were hand-drawn inline SVG, and .fig-l, .fig-l-b,
   .fig-l-s, .fig-dash, .fig-fill and .fig-mark styled their labels. One thing
   from that era survives being written down: label sizes inside an SVG are in
   viewBox UNITS, so a 320-unit viewBox rendered at 257px silently shrinks every
   label to 0.80 of its declared size. That is what made those labels illegible,
   and it is not visible in the CSS.
   ===================================================================== */

/* .dan-excerpt - the quoted opening of the DAN article in the teaser card
   (includes/dan-back-to-basics.php).

   FOUR RULES CAME OUT HERE ON 2026-08-09 and this one replaced them. /emergencies
   carried DAN's article in full until then, and it needed .article-copy (a 70ch
   cap on paragraphs in a col-lg-9 column), .dan-fn (the footnote marker),
   .dan-gas-laws and .dan-gas-note (the gas-law <dl>). The article is now a
   one-paragraph excerpt in a card, so all four styled nothing. They are in the
   history if the full republication ever comes back; .article-copy in particular
   was measured rather than guessed - 990px at a 1440 viewport is about 115
   characters a line, half again past comfortable.

   THE MARGIN RESET IS THE WHOLE RULE. A bare <blockquote> carries the browser
   default of 1em 40px, which indents the excerpt against everything else in the
   card for no reason. The rest is the quotation reading as one: a cyan rule on
   the leading edge, and italics to separate DAN's voice from the SSS line under
   it. */
.dan-excerpt {
  margin: 0; padding-left: .9rem;
  border-left: 3px solid var(--cyan-200);
  font-style: italic; color: var(--navy-800);
}

/* AB-CABS key (emergencies.php). A <dl> because it is genuinely term/definition
   - each letter defines a step - and because that lets a screen reader announce
   the grouping instead of reading a flat run of letters. The two <dt>s are the
   two halves of the mnemonic: AB is the check, CABS is the cycle of care. */
.abcabs { margin: 0; }
.abcabs-group {
  font-family: var(--font-mono); font-weight: 700; font-size: .78rem;
  letter-spacing: .1em; color: var(--emergency);
  margin: .7rem 0 .3rem; padding-top: .55rem;
  border-top: 1px dashed var(--line);
}
.abcabs-group:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.abcabs-group span { font-weight: 500; letter-spacing: .02em; color: var(--slate); }
.abcabs dd {
  margin: 0 0 .25rem;
  font-size: .9rem; color: var(--navy-800);
}
/* The initial letter is what the reader matches against the mnemonic, so it is
   emphasised - but it stays INLINE and in the body face. An earlier version
   absolutely positioned it in a gutter and set it in the mono face, which broke
   the word in half on screen: "A  irway open?". The letter has to remain part
   of the word it starts. */
.abcabs dd b { color: var(--emergency); font-weight: 800; }

/* Aqua highlight - bright top-stripe.
   .card-link-tile is the linked form (see the hover rules further up);
   .card-topline is the same stripe with no hover, for cards that are not links -
   the "One standard of care" trio on the home page. Shared declaration rather
   than two copies, so the stripe cannot drift between them. */
.card-link-tile, .card-topline { border-top: 3px solid var(--aqua); }

/* DARK CARDS ON THE NAVY BAND (owner's call, 2026-08-02 - they were white for a
   day). Only the fill is set here: .bg-navy already hands down color:#fff and
   forces .text-cyan to cyan-200, and on this darker card both are exactly right,
   so the pair of overrides that made the white version legible is deleted rather
   than adjusted. Measured on --navy-900: #fff 17.47:1, --cyan-200 13.65:1.

   THE BORDER IS WHAT MAKES IT A CARD, not the fill. Every navy in the palette
   sits within 1.12-1.47:1 of the band, so no fill separates from it - measured:
   navy-900 1.12, navy-700 1.15, navy-600 1.47. navy-900 is the choice because it
   is an established surface (the footer) and gives the most text headroom, but
   on its own it would read as three floating captions rather than three cards.
   The 1px edge closes each shape (2.15:1 against the card, 1.92:1 against the
   band) and the aqua top stripe does the rest at 7.80:1.

   box-shadow off: --shadow-sm is a dark shadow, which on a dark band is either
   invisible or a smudge.

   THREE LONGHANDS AND NOT `border-color`. The shorthand sets all four sides, and
   this selector outweighs .card-topline (0,2,0 against 0,1,0) - so it silently
   repainted the aqua stripe in the same translucent white and the cards lost
   their top edge. Caught in the computed styles, not by eye: the stripe read
   `3px rgba(255,255,255,.24)`. Naming only the three sides leaves the stripe
   rule as the single place --aqua is set. */
.bg-navy .card-soft {
  background: var(--navy-900);
  border-right-color:  rgba(255, 255, 255, .24);
  border-bottom-color: rgba(255, 255, 255, .24);
  border-left-color:   rgba(255, 255, 255, .24);
  box-shadow: none;
}

/* Aqua links - vivid aqua underline on in-copy links; the link TEXT stays
   the AA-safe aqua-700, only the underline is the bright basic. */
.section p a, .section .lead a, .section a.fw-semibold {
  text-decoration: underline; text-decoration-color: var(--aqua);
  text-decoration-thickness: 2px; text-underline-offset: 2px;
}
.section p a:hover, .section .lead a:hover, .section a.fw-semibold:hover { text-decoration-color: var(--aq-700); }

/* =====================================================================
   Cross-document view transitions - progressive enhancement.
   Chrome/Edge 126+, Safari 18.2+; other browsers: normal loads.
   ===================================================================== */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
  .site-chrome { view-transition-name: site-chrome; }   /* chrome holds still; content cross-fades */
  ::view-transition-old(root), ::view-transition-new(root) { animation-duration: 180ms; }
}
/* belt & braces: if a UA ever applies @view-transition ignoring the media wrapper */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
}

/* =====================================================================
   Emergency action hero (index.php) - CTA + first-aid card, no map
   ===================================================================== */
.hero-action .hero-note { color:#9FB6D4; }

/* =====================================================================
   Video backgrounds - reusable on any section (js/video-bg.js)
   Usage: <section class="video-bg" data-video-src="/video/....mp4"
                   style="--vbg-poster:url('/video/....jpg')">
   Poster always paints first; the <video> is created by JS, on desktop widths
   only and never under prefers-reduced-motion, so nobody else downloads the
   file. Self-hosted since 2026-08-05 - what the YouTube embed cost, and why the
   element is not written in markup, is in the header of js/video-bg.js.
   ===================================================================== */
/* Poster is a CSS background, set per section via --vbg-poster.
   (A 2026 pass briefly moved this to a real <img class="vbg-poster"> for LCP
   reasons - a CSS background is invisible to the preload scanner and can carry
   no srcset. It broke the hero's appearance, so it was reverted. If it is tried
   again, the poster must be re-tested against the ::before scrim and the video
   frame at every breakpoint, not just at one width.) */
.video-bg { position:relative; overflow:hidden;
  background-image:var(--vbg-poster); background-size:cover; background-position:center; }
.video-bg::before { content:""; position:absolute; inset:0; z-index:1; pointer-events:none;
  /* left-weighted scrim: text column stays ≥4.5:1 even over a white video frame */
  background:linear-gradient(90deg, rgba(10,31,68,.9) 0%, rgba(10,31,68,.78) 45%, rgba(10,31,68,.5) 100%); }
.video-bg > .container, .video-bg > .container-fluid { position:relative; z-index:2; }
/* The frame IS the <video> now, not a div wrapping an iframe. width/height are
   NOT redundant with inset:0: an absolutely positioned REPLACED element with
   auto width falls back to its intrinsic size (1920x640 here) and ignores
   right/bottom, so inset:0 alone would leave the clip at its natural size.
   object-fit:cover replaces the width/height/left/top arithmetic video-bg.js
   used to recompute on every resize. pointer-events:none also suppresses
   Firefox's hover picture-in-picture toggle and any context menu on the clip. */
.video-bg .video-bg-frame { position:absolute; inset:0; z-index:0; pointer-events:none;
  width:100%; height:100%; object-fit:cover; opacity:0; }
@media (prefers-reduced-motion: no-preference) {
  .video-bg .video-bg-frame { transition:opacity .8s cubic-bezier(.2,.6,.2,1); }
}
.video-bg .video-bg-frame.playing { opacity:1; }
.vbg-toggle { position:absolute; z-index:3; right:16px; bottom:16px;
  width:40px; height:40px; border-radius:50%; border:1px solid rgba(159,224,238,.4);
  background:rgba(11,24,56,.55); color:#fff; font-size:.7rem; line-height:1;
  cursor:pointer; display:flex; align-items:center; justify-content:center; }
.vbg-toggle:hover { background:rgba(11,24,56,.8); }
.vbg-toggle:focus-visible { outline:none; box-shadow:0 0 0 3px rgba(22,188,230,.45); }
@media (prefers-reduced-motion: reduce), (max-width: 991.98px) {
  .video-bg .video-bg-frame, .vbg-toggle { display:none; }
}
/* THE POSTER ANCHORS RIGHT WHERE IT IS THE THING BEING SEEN.
   ------------------------------------------------------------------------
   Same fault as the mastheads and worse. The poster is 1920x640 - 3:1, like
   every masthead - and below lg the rule above hides the video, so this
   background IS the hero. In a phone-shaped box `cover` scales to fill the
   height and throws the width away: at 390x605 the poster draws 1816x605, so
   1426px of it is cropped and just 21.5% of the frame survives. Centred, that
   21.5% was the middle of a composition built for a 3:1 letterbox, which is why
   the home hero read as dark equipment with no subject in it on a phone.

   WIDTH ONLY, deliberately - this does NOT ride along in the block above. That
   one also fires for prefers-reduced-motion at any width, and a reduced-motion
   desktop reader would get a right-anchored poster in a wide box where the crop
   is mild and the left-weighted scrim expects the dark end under the text. The
   crop is a narrow-screen problem, so the fix is gated on narrow screens.

   991.98 and not 767.98, which is where .hero-photo switches: that class is
   choosing between two framings of a photograph, whereas this is tied to the
   video being hidden. The poster is exactly what a reader sees from 991.98px
   down, and it is cropping 39% even at the top of that range.

   `top` IS INERT HERE TOO, for the same reason it is on .hero-photo: the drawn
   height equals the box height exactly, so there is no vertical overflow to
   move. It is written out to match, and so the two heroes state their anchor the
   same way. See the .hero-photo note above for the full argument.

   CONTRAST RE-MEASURED after this, and it needed it more than the mastheads did:
   ::before is a LEFT-weighted scrim (.9 -> .5 across the box) built for a text
   column on the left, and on a phone the copy runs the full width into that .5
   end - so pulling the bright side of the poster rightward pushes brighter
   pixels under the widest part of the text. Measured at 390x605, 98th-percentile
   brightest pixel under each ink, scrim composited, anchored right vs centre:

       eyebrow       5.0:1   was 6.5    needs 4.5
       note          5.3:1   was 6.4    needs 4.5
       lede          9.7:1   was 8.0    needs 4.5   (the one that improves)
       h1           10.6:1   was 15.4   needs 3.0   large text
       outline btn   9.4:1   was 13.4   needs 3.0   large text

   EVERYTHING PASSES BUT THE HEADROOM IS THIN, and that is the real cost of this
   change: anchoring right spends contrast on four of the five inks, and the
   EYEBROW is now the binding one at 5.0:1 against a 4.5 floor - half a point.
   It is 12px, it sits at the top where the scrim is no darker, and it runs the
   full width into the .5 end. So: re-measure before lightening that gradient,
   before moving the eyebrow, and whenever the poster is replaced. If a future
   poster is brighter on its right-hand side than this one, this is the ink that
   will fail first, and the fix is the scrim - not moving the anchor back. */
@media (max-width: 991.98px) {
  .video-bg { background-position: right top; }
}

/* =====================================================================
   DEPTH descent - About page (scoped .depth-page)

   The page IS a dive: one row per idea, stepping from surface light down
   the navy ramp to the abyss at 140 ft. Six rows at 20-foot intervals,
   each tall enough to fill most of a screen, so scrolling the page feels
   like descending rather than reading.

   Each row opens with a short blend from its predecessor's END colour, so
   the ramp is continuous across section boundaries. Text flips from ink to
   light at .d-3, where the blend happens inside the top 20% - the row's
   generous height keeps the copy well below that transition band.

   --depth-row-h is the knob: raise it to make the descent longer.
   Scoped to .depth-page; these classes are used by about.php only.
   ===================================================================== */
.hero.hero-surface { background: linear-gradient(180deg, #E6F7FC, #BDEBF8); color: var(--ink); }
.hero-surface h1 { color: var(--navy-900); }
/* Same defect and same remedy as the network explorer band: --aq-700 #0A7B9C
   measures 4.41:1 on this gradient's top stop (#E6F7FC) and 3.79:1 on its
   bottom (#BDEBF8) - both under AA, and unlike .verify-tag this text has no
   chip behind it, so the gradient really is its backdrop. #095B75 gives
   6.90 / 5.93. Scoped here on purpose: --aq-700 / --cyan-600 has 12 other
   consumers and must not move globally. */
.hero-surface .eyebrow { color:var(--aq-800); }

/* This hero is a LIGHT aqua gradient, but it matches .hero::after and so was
   being painted with the DARK-surface texture - pale aqua #BDEBF8 ink on a pale
   aqua ground, i.e. invisible. It was `content: none` for that reason.

   NOW RE-INKED rather than dropped, because waves are wanted on every header
   (2026-07-30) and this was the only one without them. Same
   ink-follows-the-surface rule as .section-explorer and the two light descent
   rows: swap #BDEBF8 for #0A7B9C and keep everything else.

   The old note here warned that dark ink "would eat the contrast headroom the
   eyebrow above only just recovered". Measured rather than assumed, at the
   inherited .06 opacity - worst case is the full-strength first wave stroke, so
   effective alpha .06 of #0A7B9C over each gradient stop:

     top stop    #E6F7FC → composites to #D9F0F6
                 eyebrow 6.42:1 (was 6.90) · h1 14.76:1 · lede 7.97:1
     bottom stop #BDEBF8 → composites to #B2E4F2
                 eyebrow 5.52:1 (was 5.93) · h1 12.71:1 · lede 6.86:1

   The eyebrow is the binding case, as the old note predicted, and it clears AA
   (4.5:1) by 1.02 at the worst stop. Cost is 0.48 and 0.41 of ratio - real, and
   affordable. Re-measure if the gradient stops move, the opacity rises, or the
   eyebrow colour changes.

   NOTE the map layer is deliberately NOT added here. Waves only. The map is the
   subject of /network*, not of /about, and this page already carries the depth
   rail and gradient as its identity. */
.hero-surface::after {
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='48' viewBox='0 0 40 48'%3E%3Cpath d='M0,10 Q5,5 10,10 T20,10 T30,10 T40,10' fill='none' stroke='%230A7B9C' stroke-width='1.5'/%3E%3Cpath d='M0,24 Q5,19.5 10,24 T20,24 T30,24 T40,24' fill='none' stroke='%230A7B9C' stroke-width='1.5' opacity='0.7'/%3E%3Cpath d='M0,38 Q5,34 10,38 T20,38 T30,38 T40,38' fill='none' stroke='%230A7B9C' stroke-width='1.5' opacity='0.45'/%3E%3C/svg%3E");
}
.hero-surface .lede { color: var(--slate); }

.depth-page { --depth-row-h: 78vh; }
.depth-page .section {
  --section-pad: var(--section-pad-tall);
  min-height: var(--depth-row-h);
  display: flex; align-items: center;          /* copy sits mid-row, off the blend band */
}
.depth-page .section > .container { width: 100%; }
/* short screens: don't force a tall row that would clip the copy */
@media (max-height: 620px) { .depth-page { --depth-row-h: auto; } }

/* The six-step ramp - each row starts where the last ended.
   The hold colours are constrained by contrast, not taste: the depth mark is
   12.5px mono, so it needs 4.5:1. #4ACCEC only clears that below ~#1E3A82,
   which is why d-3 drops straight to it rather than pausing at #2B4DA6
   (that measured 4.10 and failed). Verified ratios are noted per row. */
.d-1 { background: linear-gradient(180deg, #BDEBF8 0%, #C9D2EB 22%, #C2CCE9 100%); color: var(--ink); }
.d-2 { background: linear-gradient(180deg, #C2CCE9 0%, #A9B8E2 22%, #A9B8E2 100%); color: var(--ink); }
.d-3 { background: linear-gradient(180deg, #A9B8E2 0%, #1E3A82 20%, #1E3A82 100%); color: #D7E3F2; }
.d-4 { background: linear-gradient(180deg, #1E3A82 0%, #152A63 18%, #152A63 100%); color: #D7E3F2; }
.d-5 { background: linear-gradient(180deg, #152A63 0%, #102249 18%, #102249 100%); color: #D7E3F2; }
.d-6 { background: linear-gradient(180deg, #102249 0%, #0B1838 15%, #0B1838 100%); color: #D7E3F2; }

.d-3 h2, .d-4 h2, .d-5 h2, .d-6 h2,
.d-3 h3, .d-4 h3, .d-5 h3, .d-6 h3 { color: #fff; }
.d-3 .depth-body, .d-4 .depth-body, .d-5 .depth-body, .d-6 .depth-body { color: #D7E3F2; }

/* =====================================================================
   THE DESCENT ROWS ARE FLAT - no texture (2026-07-30).
   =====================================================================
   All six rows carried the map at `cover` on their ::after: the -ink asset on
   the two light rows, the standard one once the water turns navy at .d-3, with
   the changeover hidden inside .d-3's blend band so the texture faded out light
   and re-emerged dark through the thermocline. Removed with every other body
   texture per "no textures, only in the headers".

   WHAT CARRIES THE DESCENT NOW: the six background holds themselves, the blend
   bands between them, the depth marks, the depth rail and the scroll reveals.
   The gradient IS the idea; the map was only ever a surface on top of it.

   The two opacities here (.30 light / .12 dark) were set by the dimmest text on
   each half - .stat-dials .lbl at 4.86:1 and .depth-mark #4ACCEC at 4.73:1, both
   only just clearing AA. Those texts now sit on flat colour and measure better
   than any textured value allowed. If a texture ever returns to this page, both
   halves need re-measuring from scratch against whatever it is; the old numbers
   in git describe a two-layer composite that no longer exists.

   The positioning scaffolding stays - see the note on .bg-navy above.
   ===================================================================== */
.depth-page .section { position: relative; }
.depth-page .section > * { position: relative; z-index: 2; }

/* Depth mark: navy on the two light rows (aq-700 measured 3.03 / 2.46 there -
   a failure the previous 4-row design also had), aqua once the water darkens. */
.depth-mark { font-family: var(--font-mono); font-weight: 600; font-size: .78rem;
  letter-spacing: .14em; text-transform: uppercase; color: var(--navy-700); margin-bottom: .5rem; }
.d-3 .depth-mark, .d-4 .depth-mark, .d-5 .depth-mark, .d-6 .depth-mark { color: #4ACCEC; }
/* the metric conversion, quieter than the feet it follows */
.depth-mark .alt { opacity: .72; font-weight: 400; }

/* The recreational-limit line, drawn in the page where the descent crosses it.
   Aqua, not orange - it marks a boundary, not an alert. */
.depth-limit-note {
  display: flex; align-items: center; gap: .75rem; flex-wrap: wrap;
  font-family: var(--font-mono); font-size: .74rem; letter-spacing: .06em;
  color: var(--aq-300); margin: 0 0 clamp(2rem, 5vh, 3.5rem);
}
.depth-limit-note .lim-rule {
  flex: 1 1 3rem; min-width: 2rem; height: 0;
  border-top: 1px dashed rgba(132,220,241,.55);
}
.depth-limit-note .alt { opacity: .72; }

/* instrument stat strip */
.stat-dials { display: flex; flex-wrap: wrap; gap: 0; border-top: 2px solid var(--navy-900);
  margin-top: 2.5rem; }
.stat-dials .dial { flex: 1 1 150px; padding: 1.1rem 1.4rem 0 0; border-right: 1px solid rgba(11,24,56,.25); }
.stat-dials .dial + .dial { padding-left: 1.4rem; }
.stat-dials .dial:last-child { border-right: 0; }
.stat-dials .val { font-family: var(--font-title, inherit); font-weight: 800; font-size: clamp(1.6rem, 3vw, 2.3rem);
  letter-spacing: -.02em; color: var(--navy-900); font-variant-numeric: tabular-nums; }
.stat-dials .lbl { display: block; font-family: var(--font-mono); font-size: .72rem;
  letter-spacing: .12em; text-transform: uppercase; color: var(--slate); margin-top: .3rem; }

/* mission glass cards (abyss) */
.glass-card { background: rgba(255,255,255,.055); border: 1px solid rgba(159,224,238,.22);
  border-radius: var(--r-md); padding: 1.3rem 1.4rem; height: 100%; }
.glass-card h3 { color: #4ACCEC; font-size: .95rem; margin-bottom: .4rem; }
.glass-card p { color: #D7E3F2; font-size: .95rem; margin: 0; }

/* descent reveals - CSS scroll-driven only, static everywhere else */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .depth-page .rise { animation: dp-rise linear both;
      animation-timeline: view(); animation-range: entry 0% entry 55%; }
    @keyframes dp-rise { from { opacity: .15; transform: translateY(26px); } to { opacity: 1; transform: none; } }
  }
}

/* depth gauge rail - fixed scroll-depth indicator on the descent pages.
   Decorative (aria-hidden, pointer-events:none); hidden below 1480px
   where it would crowd the content column. */
.depth-rail { position:fixed; left:20px; top:50%; transform:translateY(-50%);
  z-index:40; height:56vh; display:none; pointer-events:none; }
@media (min-width:1480px) { .depth-rail { display:block; } }
/* Keeping the gauge on mid-size screens.
   Below 1480px the rail hides for want of room beside the content column.
   Instead of losing the instrument there, the descent zooms out: at .8 a
   1184px viewport lays out as 1480 CSS px, so the rail fits again and the
   whole 1184–1480 band keeps it.

   The 1184px floor is load-bearing, not a round number - it is 1480 × .8,
   the width below which zooming no longer reaches the rail's threshold. Under
   it the page would shrink its type and still have no gauge to show for it,
   so the zoom stops and the rail stays hidden.

   TRADE-OFF, on purpose: everything in this band renders ~20% smaller
   (16px body copy → 12.8px effective). That is a real readability cost paid
   to keep the gauge; raise the floor (or drop this block) to buy it back.
   `zoom` is what makes it work - it changes layout size, so the column
   re-flows into the freed space, which `transform: scale()` would not do. */
@media (min-width: 1184px) and (max-width: 1479.98px) {
  .depth-page { zoom: .8; }
  .depth-page .depth-rail { display: block; }
}
.depth-rail-body { position:relative; height:100%; width:72px;
  background:rgba(11,24,56,.62); backdrop-filter:blur(6px);
  border:1px solid rgba(74,204,236,.2); border-radius:999px; padding:20px 0; }
.depth-rail-track { position:relative; height:100%; margin-left:34px; width:2px;
  background:linear-gradient(180deg, var(--aq-400), var(--navy-500) 60%, var(--navy-800)); }
.depth-rail-tick { position:absolute; left:-5px; width:12px; height:1px;
  background:rgba(189,235,248,.55); }
.depth-rail-tick span { position:absolute; left:16px; top:-7px;
  font-family:var(--font-mono); font-size:9px; letter-spacing:.08em;
  color:var(--navy-300); white-space:nowrap; }
.depth-rail-needle { position:absolute; left:-27px; top:0; display:flex;
  align-items:center; gap:4px; transform:translateY(-50%); transition:top .1s linear; }
.depth-rail-needle .nd { width:0; height:0; border-top:4px solid transparent;
  border-bottom:4px solid transparent; border-left:6px solid var(--aq-400); }
.depth-rail-needle .nv { font-family:var(--font-mono); font-size:10px; font-weight:600;
  color:var(--aq-200); letter-spacing:.04em; white-space:nowrap;
  writing-mode:vertical-rl; transform:rotate(180deg); }
/* The recreational limit (130 ft / 40 m) - a boundary, not an alert, so it is
   drawn in aqua: orange stays reserved for action/alert. Everything below this
   line on the page is the network's non-recreational work, which is the point. */
.depth-rail-limit { position:absolute; left:-9px; width:20px; height:0;
  border-top:1px dashed var(--aq-300); }
.depth-rail-limit span { position:absolute; left:24px; top:-6px;
  font-family:var(--font-mono); font-size:8px; letter-spacing:.05em;
  color:var(--aq-300); white-space:nowrap; }
@media (prefers-reduced-motion: reduce) {
  .depth-rail-needle { transition:none; }
}

/* =====================================================================
   Chamber technology - cross-section diagram + numbered callouts
   ===================================================================== */
.chamber-figure { background: var(--navy-900); border-radius: var(--radius);
  padding: clamp(.8rem, 2vw, 1.6rem); margin:0; }
.chamber-figure svg { width:100%; height:auto; display:block; }

/* =====================================================================
   Photo slots - .ph-media
   One frame for real photos AND pending placeholders. Placeholder mode
   (default): navy/steel frame, camera glyph, visible shot label. The
   data-shot attribute holds the full photographer brief. Shot list:
     grep -rn 'data-shot="[^"]*"' --include='*.php' .
   Replacement list for provisional archive photos:
     grep -rn 'provisional-photo' --include='*.php' .
   Real-photo mode: add .has-img and drop the <img> in; the frame keeps
   the .ph-credit caption strip for the archive-photo note.
   Aspect presets: .ph-16x9 (default) · .ph-4x3 · .ph-1x1 · .ph-21x9
   ===================================================================== */
.ph-media {
  position: relative; margin: 0; width: 100%;
  border-radius: var(--radius); overflow: hidden;
  aspect-ratio: 16 / 9;
  border: 1px dashed var(--navy-400);
  background:
    repeating-linear-gradient(45deg, transparent 0 16px, rgba(159,224,238,.05) 16px 32px),
    linear-gradient(160deg, var(--navy-800), var(--navy-900));
  display: flex; align-items: flex-end;
}
.ph-16x9 { aspect-ratio: 16 / 9; }
.ph-4x3  { aspect-ratio: 4 / 3;  }
.ph-1x1  { aspect-ratio: 1 / 1;  }
.ph-21x9 { aspect-ratio: 21 / 9; }
.ph-media .ph-glyph {           /* camera icon, centered, decorative */
  position: absolute; inset: 0; margin: auto;
  width: min(64px, 20%); height: auto; color: var(--cyan-200); opacity: .38;
}
.ph-media .ph-label {           /* visible shot label = real text content */
  position: relative; width: 100%;
  font-family: var(--font-mono); font-size: .78rem; line-height: 1.45;
  padding: .55rem .85rem;
  background: rgba(11,24,56,.78); color: var(--navy-100);
}
.ph-media .ph-label::before {
  content: "Photo pending - "; font-weight: 700; letter-spacing: .04em;
  color: var(--cyan-200); text-transform: uppercase;
}
/* Real-photo mode ------------------------------------------------------ */
.ph-media.has-img {
  display: block; aspect-ratio: auto;
  border: 1px solid var(--line); background: #fff;
}
.ph-media.has-img img { display: block; width: 100%; height: auto; }
.ph-media .ph-credit {          /* archive-photo caption strip */
  display: block; padding: .4rem .85rem;
  font-size: .78rem; color: var(--slate); background: #fff;
  border-top: 1px solid var(--line);
}
/* Wave texture over the figures --------------------------------------- */
/* THE HEADER MOTIF, CARRIED ONTO THE PICTURES (2026-08-01, owner's request).
   Until now the waves were headers-only - see the block at the top of this file,
   which has been amended rather than left saying otherwise.

   SAME MOTIF, SAME .06, DARK INK. The rule this site already follows is
   "ink follows the surface; strength does not change" - .hero-surface, the
   explorer band and the two light descent rows all re-ink these exact waves
   #0A7B9C for a light ground and change nothing else. These stages are white
   plates under mostly light illustrations, so they take the dark ink for the
   same reason: #BDEBF8 at .06 on a white plate is not faint, it is absent.

   pointer-events:none IS LOAD-BEARING, not hygiene. The overlay is stacked above
   the picture, and the picture is now a lightbox trigger - without this the
   texture would swallow every click and the enlarge would silently stop working.

   border-radius:inherit because .manual-fig-stage rounds its corners without
   overflow:hidden, so a square overlay would post out past the curve. .ph-media
   clips anyway; inheriting costs nothing and covers both.

   APPLIED IN THE LIGHTBOX TOO (owner's call, 2026-08-01). This was left out at
   first on the argument that the enlarged view exists to resolve lettering that
   renders at ~12px in the column, so a texture over it works against the one
   thing it is for. Overruled, and the argument was thin at .06: at the enlarged
   size the artwork is 3-4x bigger while the wave field repeats at the same
   40x48px, so the texture is relatively finer there than it is on the plate. */
.ph-media.has-img,
.manual-fig-stage { position: relative; }
.ph-media.has-img::after,
.manual-fig-stage::after {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  border-radius: inherit;
  opacity: .06;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='48' viewBox='0 0 40 48'%3E%3Cpath d='M0,10 Q5,5 10,10 T20,10 T30,10 T40,10' fill='none' stroke='%230A7B9C' stroke-width='1.5'/%3E%3Cpath d='M0,24 Q5,19.5 10,24 T20,24 T30,24 T40,24' fill='none' stroke='%230A7B9C' stroke-width='1.5' opacity='0.7'/%3E%3Cpath d='M0,38 Q5,34 10,38 T20,38 T30,38 T40,38' fill='none' stroke='%230A7B9C' stroke-width='1.5' opacity='0.45'/%3E%3C/svg%3E");
  background-repeat: repeat;
  background-size: auto;
  background-position: 0 0;
}
/* The caption strip is a sibling inside the same box, so it would sit under the
   overlay and pick up a tint on text that is meant to be plain. Lift it out. */
.ph-media .ph-credit { position: relative; z-index: 2; }

/* SOMEBODY ELSE'S PHOTOGRAPH DOES NOT TAKE OUR MOTIF (2026-08-09).
   The waves above are the SSS header motif. Every figure that carries them holds
   commissioned SSS artwork, where branding the picture is just branding our own
   work. The DAN teaser on /emergencies is the first figure here holding an image
   another organisation owns, and laying our texture over it is the same move as
   compositing our logo onto it - which tools/derive-location-mastheads.php
   already refuses to do, in those words, for photographs whose provenance is not
   ours. Subtler at .06 than a watermark, and the same thing in kind.
   One class, applied once. Reach for it on the next third-party picture. */
.ph-media.is-third-party::after { content: none; }

/* Print: keep the label legible on paper, drop the dark fill ----------- */
@media print {
  .ph-media { background: #fff; border: 1px solid #888; }
  .ph-media .ph-label { background: #fff; color: #333; }
  .ph-media .ph-label::before { color: #333; }
}

/* The .contact-groups / .contact-group rules lived here - CSS multi-column, so
   the five uneven country groups packed without a grid aligning their rows.
   Both the markup and these rules went on 2026-08-01 when the owner rolled the
   contact hub back to one <section> per country. Nothing references them now;
   `git show adfd6bb` has the whole approach if the density is ever wanted back. */

