@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/Inter-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: 'Inter';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(fonts/Inter-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;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(fonts/Inter-600.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: 'Inter Tight';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(fonts/InterTight-600.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: 'Inter Tight';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(fonts/InterTight-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: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(fonts/JetBrainsMono-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;
}

/* The artifact host supplied these; a file server does not. */
html { color-scheme: dark; }
img { max-width: 100%; }
[hidden] { display: none !important; }


/* ===========================================================================
   Navvex — public site
   ---------------------------------------------------------------------------
   Palette lifted from client/design/tokens.css so the site and the operator
   console are one brand rather than two that resemble each other:

     --surface-rail        #101a26   deep water
     --brand-stroke        #16697f   sub-surface scatter
     --accent              #0b5fbf   every call to action
     --text-on-rail        #e8eef5
     --readiness-*                   the status marks on cards and charts

   The sea is rendered, not decorated: a real horizon, a real sun, Fresnel
   reflection and a specular glitter path. Sky tones are introduced around the
   brand values; the brand values themselves are untouched.

   One committed appearance — a working sea at low sun — so there is no light
   theme and no prefers-color-scheme block. Every colour is painted explicitly,
   body included, so the page holds its ground whatever the host is set to.
   =========================================================================== */

:root {
  --rail: #101a26;
  --rail-raised: #1a2634;
  --teal: #16697f;
  --accent: #0b5fbf;
  --accent-hover: #1a6fd4;
  --on-rail: #e8eef5;
  --on-rail-muted: #93a3b6;
  --ready: #2fc08a;
  --gaps: #c28a00;
  --lamp: #ffc178;           /* interior light spilling from a compartment */

  --abyss: #050a12;
  --glass: rgba(11, 20, 32, 0.5);
  --glass-strong: rgba(6, 14, 26, 0.76);
  --hairline: rgba(232, 238, 245, 0.14);
  --hairline-soft: rgba(232, 238, 245, 0.08);

  --font-display: "Inter Tight", Inter, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-body: Inter, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;
  --ease: cubic-bezier(0.2, 0, 0, 1);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--abyss);
  /* The fallback if WebGL never starts: sky over sea, split at the horizon the
     shader also uses, so the ships still sit on a waterline. */
  background-image: linear-gradient(180deg, #2b4c60 0%, #5d7f8d 44%, #2a4354 47%, #101a26 72%, #050a12 100%);
  color: var(--on-rail);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

h1, h2, h3 { font-family: var(--font-display); text-wrap: balance; margin: 0; }
p { margin: 0; }
a { color: inherit; text-decoration: none; }
:focus-visible { outline: 2px solid var(--accent-hover); outline-offset: 3px; border-radius: 4px; }

/* ===========================================================================
   THE STAGE
   ---------------------------------------------------------------------------
   One fixed viewport. The camera moves through the opening, not the scroll
   position -- so the stage does not scroll away, it hands over. When the
   visitor reaches the content, the stage stays behind it as the environment
   rather than being replaced by a flat background.

   There is no horizon constant here any more. The old sheet anchored floating
   things to 47.25% of the viewport because the shader's horizon was fixed at
   that height; the camera is free now and the hulls are projected through it
   in JS, so anything that tried to guess the horizon in CSS would be wrong the
   moment the shot moved.
   =========================================================================== */

.stage { position: fixed; inset: 0; z-index: 0; overflow: hidden; }
#ocean { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

.hulls { position: absolute; inset: 0; pointer-events: none; }

/* Silhouettes, in four values and two lines.
   ---------------------------------------------------------------------------
   Nearly flat, because at this light a hull between the camera and a low sun
   IS a silhouette and shading it would be inventing light that is not falling
   on it. But not *actually* flat: the topsides see a little sky, the deck edge
   catches the last of it, and the boot-top is a different colour from the
   plate above. One value across the whole hull was fine while a ship was forty
   pixels wide; the `aboard` beat puts the hero across eleven hundred, and at
   that size a single fill is paper. */
.vessel .hull    { fill: #090f17; }
.vessel .topside { fill: #121d2a; }
.vessel .boot    { fill: #171c23; }
.vessel .house   { fill: #16212f; }
.vessel .house-lit { fill: #1d2937; }
/* Deck fittings are markedly lighter than the hull, and have to be. A hull
   side at this hour is in shadow; a deck is a horizontal surface looking
   straight up at the entire sky. That one relationship is most of what gives a
   silhouette a top instead of just an outline -- and at four values apart,
   which is where these started, none of it was visible at all. */
.vessel .deck    { fill: #1e2a38; }
.vessel .tier    { fill: #26333f; opacity: .7; }

/* Rails and the deck edge are lines, not shapes. A 0.7-unit filled rectangle
   at this scale is a smear; a stroke is a rail. */
.vessel .rail { fill: none; stroke: #2b3745; stroke-width: .6; stroke-linecap: round; opacity: .6; }
.vessel .rim  { fill: none; stroke: #43556a; stroke-width: .8; opacity: .38; }

/* Lit windows. The wheelhouse and the cabin decks are the brightest thing in
   the frame at this hour, and they are what tells you the ship is crewed. */
.vessel .lit   { fill: #ffd28f; opacity: calc(.8 * var(--lamp-on, 1)); }
/* Unlit glass. Still an opening in the front of a deckhouse, still darker than
   the plate around it -- and drawing it is what keeps the lit windows reading
   as windows rather than as dots stuck on a wall. */
.vessel .glass { fill: #0a1017; opacity: .8; }

.vessel--hero .hull    { fill: #0b121c; }
.vessel--hero .topside { fill: #152231; }
.vessel--hero .house   { fill: #192637; }
.vessel--hero .house-lit { fill: #212f42; }
.vessel--hero .deck    { fill: #223041; }
.vessel--hero .rim     { stroke: #62768d; opacity: .6; }
.bird .wing { fill: none; stroke: #0d1826; stroke-width: 1.4; stroke-linecap: round; }

/* Deck lights. The one thing that makes a silhouette read as a ship with
   people on it rather than a shape cut out of the horizon. */
/* --lamp-on is written on #hulls from the sun's elevation, and inherits down
   to every hull and every reflection. At noon these are off. */
.vessel .lamp      { fill: #ffd694; opacity: var(--lamp-on, 1); }
.vessel .lamp-halo { fill: url(#lampGlow); opacity: var(--lamp-on, 1); }
/* The column a light draws in the water. It exists on the hull only so the
   reflection -- which is a clone of the hull -- inherits it; on the ship
   itself there is nothing for a light to streak down. */
.vessel .lamp-streak { display: none; }

/* The bow wave, opacity driven per-frame from her speed and her angle. */
.vessel .foam { fill: url(#bowWave); opacity: var(--foam, .35); }

/* ---------------------------------------------------------------------------
   The reflection
   ---------------------------------------------------------------------------
   A ship with none is a sticker on a photograph. The eye reads the missing
   reflection before it reads anything else, and no amount of water quality
   underneath repairs it.

   It is the same SVG, cloned, hinged at the waterline and flipped -- so a hull
   can never reflect as a different ship. Three things then turn a flipped copy
   into a reflection:

   Foreshortened, not mirrored at full height. The flip is scaled to 0.58 in
   JS, because a reflection seen from a few metres above the surface is short.
   A full-length one reads as a ship hanging upside down under the water.

   Banded. The first mask layer tiles a stripe down the image and drifts, which
   is what a slightly rough surface does to a reflection: it does not blur it
   evenly, it cuts it into moving strips. Cheap, too -- a turbulence
   displacement filter would cost a full noise evaluation per hull per frame to
   say the same thing less clearly at this size.

   Faded along its length. The second layer takes it away with distance from
   the waterline, so the masthead dissolves and the hull stays.
   --------------------------------------------------------------------------- */

.vessel--mirror {
  filter: blur(1.1px);
  -webkit-mask-image:
    linear-gradient(to bottom, #000 0 62%, transparent 62% 100%),
    linear-gradient(to bottom, transparent 2%, rgba(0,0,0,.26) 34%, rgba(0,0,0,.7) 64%, #000 82%);
  mask-image:
    linear-gradient(to bottom, #000 0 62%, transparent 62% 100%),
    linear-gradient(to bottom, transparent 2%, rgba(0,0,0,.26) 34%, rgba(0,0,0,.7) 64%, #000 82%);
  -webkit-mask-size: 100% 6.5%, 100% 100%;
  mask-size: 100% 6.5%, 100% 100%;
  -webkit-mask-repeat: repeat-y, no-repeat;
  mask-repeat: repeat-y, no-repeat;
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}

/* Reflected light loses more at the surface than direct light does, so the
   mirror's lamps are dimmer and smeared wider than the ones on deck. */
.vessel--mirror .lamp-streak { display: block; fill: url(#lampStreak);
                               opacity: var(--lamp-on, 1); }
.vessel--mirror .lamp      { fill: #f6c079; }
.vessel--mirror .lamp-halo { opacity: calc(.55 * var(--lamp-on, 1)); }
.vessel--mirror .foam      { opacity: .1; }
.vessel--mirror .lit       { fill: #e0ae6b; opacity: calc(.6 * var(--lamp-on, 1)); }
/* No rim in the reflection. The deck edge is lit from above; the underside of
   it, which is what a reflection shows, is not. */
.vessel--mirror .rim       { stroke: none; }
.vessel--mirror .rail      { stroke: none; }

/* ---------------------------------------------------------------------------
   The opening statement
   --------------------------------------------------------------------------- */

.opening {
  position: absolute;
  left: clamp(20px, 6vw, 92px);
  right: clamp(20px, 6vw, 92px);
  top: clamp(88px, 16vh, 168px);
  /* No max-width here.
   *
   * It was 24ch, and `ch` is measured against the element's own font size --
   * 16px on this block -- so the box came out about two hundred pixels wide
   * while the headline inside it was set at 62. Every word wrapped onto its
   * own line. The h1 below already caps itself at 18ch of its *own* type,
   * which is the measurement that was intended. */
  z-index: 3;
  transition: opacity 900ms var(--ease), transform 900ms var(--ease);
}
.opening h1 {
  font-size: clamp(30px, 4.6vw, 62px);
  line-height: 1.06;
  letter-spacing: -0.02em;
  font-weight: 700;
  max-width: 18ch;
  text-shadow: 0 2px 26px rgba(5, 10, 18, 0.55);
}
.eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--on-rail-muted);
  margin-bottom: 14px;
}

/* It steps back once the camera is alongside -- it has said its piece, and
   leaving it over the crew would be two things asking to be read at once. */
[data-beat="aboard"] .opening { opacity: 0; transform: translateY(-14px); pointer-events: none; }

/* ---------------------------------------------------------------------------
   Act II — aboard
   --------------------------------------------------------------------------- */

/* The scrim.
   White type over a sun glitter path is white on white, and the first render
   of this section was unreadable for exactly that reason. A gradient from the
   foot of the stage carries the text and leaves the horizon -- which is the
   part worth looking at -- untouched. Dimming the whole sea to rescue one
   paragraph would be fixing the wrong thing. */
.stage::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 72%;
  z-index: 2;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1100ms var(--ease);
  background: linear-gradient(
    180deg,
    rgba(4, 9, 16, 0) 0%,
    rgba(4, 9, 16, 0.42) 42%,
    rgba(4, 9, 16, 0.78) 78%,
    rgba(4, 9, 16, 0.92) 100%
  );
}
[data-beat="aboard"] .stage::after { opacity: 1; }

/* The opening statement needs it too, at the top, for the same reason. */
.stage::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 46%;
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(4, 9, 16, 0.72) 0%, rgba(4, 9, 16, 0) 100%);
}

.aboard {
  position: absolute;
  inset: auto clamp(20px, 6vw, 92px) clamp(28px, 7vh, 76px) clamp(20px, 6vw, 92px);
  display: grid;
  grid-template-columns: minmax(0, clamp(180px, 20vw, 280px)) minmax(0, 1fr);
  gap: clamp(20px, 3vw, 46px);
  align-items: end;
  z-index: 3;
  opacity: 0;
  transform: translateY(18px);
  pointer-events: none;
  transition: opacity 1100ms var(--ease), transform 1100ms var(--ease);
}
[data-beat="aboard"] .aboard { opacity: 1; transform: none; pointer-events: auto; }

/* The scrim.
 *
 * Six lines of white type were being read across the brightest band in the
 * frame -- the horizon haze and the sun's own bloom -- and the rank line, set
 * in teal, was invisible against it.
 *
 * Darkening the sea would have worked and would have been the wrong trade:
 * the sea is the reason anybody is still on the page. So the shadow goes
 * behind the words only, wide and soft enough that its edge is never a visible
 * shape, and it is attached to the block so it retires when the block does. */
.aboard::before {
  content: "";
  position: absolute;
  inset: -12% -8% -40% -6%;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(120% 90% at 34% 46%, rgba(4, 9, 16, 0.82) 0%, rgba(4, 9, 16, 0.55) 42%, rgba(4, 9, 16, 0) 72%);
}

/* ---------------------------------------------------------------------------
   The crew member
   --------------------------------------------------------------------------- */

.crew { margin: 0; position: relative; }

/* The photograph slot.
   Empty, and saying so. A placeholder that pretended to be a person would be
   the only dishonest thing on a page whose entire argument is that what it
   shows is real -- and a stock photograph of somebody in a clean polo shirt on
   a stock deck would be worse, because it would be dishonest and look it.

   crew.js probes for img/crew-deck.jpg on load and replaces the contents of
   this plate with an <img> if it finds one. Supplying the file is the whole
   integration; nothing here needs editing. */
.crew-plate {
  aspect-ratio: 3 / 4;
  border: 1px dashed var(--hairline-soft);
  border-radius: 14px;
  background: rgba(8, 15, 25, 0.42);
  backdrop-filter: blur(14px) saturate(115%);
  -webkit-backdrop-filter: blur(14px) saturate(115%);
  display: grid;
  place-items: center;
  padding: 14px;
  overflow: hidden;
}
.crew-plate img { width: 100%; height: 100%; object-fit: cover; border-radius: 13px; display: block; }

/* Once it is a real photograph the dashed frame goes and a low gradient comes
   up from the bottom edge, so he sits in the scene instead of in a box. */
.crew--real .crew-plate {
  border: 1px solid var(--hairline-soft);
  background: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  padding: 0;
  box-shadow: 0 18px 50px rgba(3, 8, 15, 0.55);
}
.crew--real .crew-plate::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 32%;
  border-radius: 0 0 13px 13px;
  background: linear-gradient(180deg, rgba(5, 10, 18, 0), rgba(5, 10, 18, 0.5));
  pointer-events: none;
}
.crew--real .crew-plate { position: relative; }

.crew-slot-note {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: #93a3b6;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--on-rail-muted);
  text-align: center;
  line-height: 1.7;
}
.crew-slot-note span { display: block; text-transform: none; letter-spacing: 0; opacity: 0.7; }
.crew-slot-file { margin-top: 10px; opacity: 0.55 !important; }
.crew-slot-file code { font-family: inherit; color: var(--teal); }

/* Where his tablet is. Zero-size, positioned by two custom properties on the
   element itself, because when the real photograph arrives those two numbers
   are the only thing that has to move to line the menu up with his hand. */
/* The gradient definitions carry no paint of their own; they are only here to
   be referenced. Out of flow, and not an inline attribute, because the
   production policy refuses those. */
.defs { position: absolute; width: 0; height: 0; }

/* Where his tablet is.
   ---------------------------------------------------------------------------
   Measured off the photograph: it is 1537x1023, the plate crops it to 3:4
   around him, and the tablet lands at 58% across and 52% down the surviving
   window. Replace the photograph and these two numbers are the only thing that
   has to move.

   Here rather than on the element, because the apex serves this site under
   `style-src 'self'` with no 'unsafe-inline' -- the operator console is one
   path segment away on the same origin -- and an inline style attribute is
   refused outright. */
.hand {
  position: absolute;
  left: var(--hand-x, 58%);
  top: var(--hand-y, 52%);
  width: 0; height: 0;
  pointer-events: none;
}

/* The tablet waking: one ring, once.
   Not a halo of glowing icons -- that image is the most-used stock shot in
   maritime technology marketing and it says nothing. This is a pressure wave
   on water. It expands and it is gone. */
.handwave {
  position: absolute;
  z-index: 4;
  width: 12px; height: 12px;
  margin: -6px 0 0 -6px;
  border-radius: 50%;
  border: 1px solid rgba(146, 196, 232, 0.55);
  pointer-events: none;
  animation: handwave 1500ms var(--ease) forwards;
}
@keyframes handwave {
  0%   { transform: scale(1);  opacity: 0; }
  12%  { opacity: 0.75; }
  100% { transform: scale(34); opacity: 0; }
}

/* ---------------------------------------------------------------------------
   What he says
   ---------------------------------------------------------------------------
   Consecutive lines of one person's speech. They arrive in order and they
   stay: a line that replaces its predecessor cannot promise that a reader
   slower than the timer keeps up, and this one is asked to introduce a
   business in five sentences.

   Without JavaScript the `crew-speaking` class is never added and every line
   is simply visible, which turns the performance into a paragraph of prose.
   That is the correct fallback, not a degraded one.
   --------------------------------------------------------------------------- */

.guide { max-width: 56ch; align-self: end; }

.guide-who {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  align-items: baseline;
  margin-bottom: 14px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
/* Brighter than the product's teal token.
 *
 * --teal is #16697f, which is correct on the application's dark panels and
 * far too dark for a caption sitting on open water at dusk. A colour that
 * works on one background is not a colour; it is a coincidence. */
.guide-rank { color: #6fb7cc; }
.guide-where { color: var(--on-rail-muted); }

.says { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.says li {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(16px, 1.45vw, 20px);
  line-height: 1.34;
  letter-spacing: -0.008em;
  color: var(--on-rail);
  text-wrap: pretty;
}
.says li em { font-style: normal; color: #9fd0e2; }

/* The closing line is the offer, and it is the one the eye should land on. */
.says-close { color: var(--on-rail); }
.crew-speaking .says-close { font-size: clamp(17px, 1.6vw, 22px); }

.crew-speaking .says li {
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 620ms var(--ease), transform 620ms var(--ease), color 900ms var(--ease);
}
.crew-speaking .says li.said { opacity: 1; transform: none; }
/* Older lines settle back rather than leaving. Still readable, plainly not
   the sentence being spoken. */
.crew-speaking .says li.said--back { color: var(--on-rail-muted); }

.guide-hint {
  margin-top: 16px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--on-rail-muted);
  opacity: 0.6;
}
.crew-open .guide-hint { display: none; }

.skip {
  position: absolute;
  right: clamp(16px, 4vw, 40px);
  bottom: clamp(16px, 3vh, 30px);
  z-index: 4;
  font: inherit;
  font-size: 12px;
  color: var(--on-rail-muted);
  background: none;
  border: 0;
  cursor: pointer;
  padding: 8px 10px;
}
.skip:hover { color: var(--on-rail); }
[data-beat="aboard"] .skip { opacity: 0; pointer-events: none; }

/* ---------------------------------------------------------------------------
   Navigation
   ---------------------------------------------------------------------------
   Present from the first byte and reachable by keyboard immediately; only its
   appearance waits. A nav that is genuinely absent until an animation finishes
   is a nav somebody on a screen reader never finds.
   --------------------------------------------------------------------------- */

.nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: clamp(16px, 4vw, 44px);
  /* The page gutter, named once. The open menu on a phone has to cancel it to
     go full-bleed, and two independent copies of a clamp() drift the day one
     of them is tuned. */
  --nav-pad: clamp(20px, 6vw, 92px);
  padding: 16px var(--nav-pad);
  background: linear-gradient(180deg, rgba(5, 10, 18, 0.72), rgba(5, 10, 18, 0));
  opacity: 0;
  transform: translateY(-10px);
  transition: opacity 700ms var(--ease), transform 700ms var(--ease);
}
.nav-ready .nav { opacity: 1; transform: none; }
.nav:focus-within { opacity: 1; transform: none; }

.brand { display: inline-flex; align-items: center; gap: 10px; }
.brand-mark {
  display: grid; place-items: center;
  width: 30px; height: 30px; border-radius: 9px;
  background: linear-gradient(145deg, var(--accent), var(--teal));
  font-weight: 700; font-size: 15px;
}
.brand-name { font-family: var(--font-display); font-weight: 700; font-size: 16px; letter-spacing: 0.14em; }

.nav ul { display: flex; gap: clamp(12px, 2vw, 26px); list-style: none; margin: 0; padding: 0; }
.nav a { font-size: 14px; color: var(--on-rail-muted); transition: color 200ms var(--ease); }
.nav a:hover { color: var(--on-rail); }

/* ---------------------------------------------------------------------------
   The content
   ---------------------------------------------------------------------------
   Sits above the stage and scrolls over it. The sea stays behind rather than
   being swapped for a flat colour, so leaving the opening feels like moving
   through the same place rather than arriving at a different website.
   --------------------------------------------------------------------------- */

main { position: relative; z-index: 10; margin-top: 100vh; }

.act {
  padding: clamp(64px, 12vh, 132px) clamp(20px, 6vw, 92px);
  background: linear-gradient(180deg, rgba(5, 10, 18, 0.88), rgba(5, 10, 18, 0.96));
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  border-top: 1px solid var(--hairline-soft);
}
.act h2 {
  font-size: clamp(26px, 3.6vw, 44px);
  line-height: 1.12;
  letter-spacing: -0.018em;
  max-width: 22ch;
}
.lede { color: var(--on-rail-muted); margin-top: 16px; max-width: 62ch; font-size: 17px; }
.note { color: var(--on-rail-muted); margin-top: 28px; max-width: 62ch; font-size: 14px; }

.chain { list-style: none; margin: 40px 0 0; padding: 0; display: grid; gap: 1px; background: var(--hairline-soft); border: 1px solid var(--hairline-soft); border-radius: 14px; overflow: hidden; }
.chain li { background: rgba(16, 26, 38, 0.72); padding: 20px 22px; display: grid; grid-template-columns: 46px 1fr; gap: 4px 14px; align-items: start; }
.chain-no { grid-row: span 2; font-family: var(--font-mono); font-size: 12px; color: var(--teal); padding-top: 3px; }
.chain h3 { font-size: 16px; font-weight: 600; }
.chain p { color: var(--on-rail-muted); font-size: 14px; }

.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1px; background: var(--hairline-soft); border: 1px solid var(--hairline-soft); border-radius: 14px; overflow: hidden; margin-top: 40px; }
.grid article { background: rgba(16, 26, 38, 0.72); padding: 24px; }
.grid h3 { font-size: 17px; font-weight: 600; margin-bottom: 8px; }
.grid p { color: var(--on-rail-muted); font-size: 14px; }

.plain { margin: 26px 0 0; padding-left: 20px; color: var(--on-rail-muted); max-width: 60ch; }
.plain li { margin-bottom: 8px; }

.cta { margin-top: 28px; }
.btn {
  display: inline-block;
  padding: 13px 24px;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font-weight: 600;
  transition: background 200ms var(--ease), transform 200ms var(--ease);
}
.btn:hover { background: var(--accent-hover); transform: translateY(-1px); }

footer { position: relative; z-index: 10; padding: 40px clamp(20px, 6vw, 92px); background: #050a12; color: var(--on-rail-muted); font-size: 13px; border-top: 1px solid var(--hairline-soft); }

/* ---------------------------------------------------------------------------
   No WebGL
   ---------------------------------------------------------------------------
   The body gradient behind the canvas already paints sky over sea. All that is
   needed is to stop waiting for a camera that will never move: the opening is
   shown at rest and the content starts where it can be read.
   --------------------------------------------------------------------------- */

.no-gl .aboard { opacity: 1; transform: none; pointer-events: auto; position: static; margin: 0 auto; padding: 0 clamp(20px, 6vw, 92px) 60px; }
.no-gl .stage { position: relative; height: auto; min-height: 88vh; padding-bottom: 40px; }
.no-gl .opening { position: static; margin: 0 auto; padding: 120px clamp(20px, 6vw, 92px) 30px; max-width: none; }
.no-gl main { margin-top: 0; }
.no-gl .skip { display: none; }
.no-gl .nav { opacity: 1; transform: none; }

/* ---------------------------------------------------------------------------
   Smaller screens
   ---------------------------------------------------------------------------
   Redesigned rather than scaled. The crew plate goes above the words instead
   of beside them, and the opening headline gives up a size step -- a 40-pixel
   headline on a phone is two words a line.
   --------------------------------------------------------------------------- */

@media (max-width: 860px) {
  /* The photograph stops being a column and becomes a header for the speech.
     Below this width a 3:4 plate beside 56 characters of type leaves neither
     of them enough room to be worth having. */
  .aboard { grid-template-columns: minmax(0, 1fr); align-items: start; }
  .crew { max-width: 148px; }
  .crew-plate { aspect-ratio: 1 / 1; }
  .says li { font-size: 16px; }

}

@media (max-width: 720px) {
  .aboard { grid-template-columns: 1fr; gap: 16px; inset: auto 16px 20px 16px; }
  .crew { max-width: 116px; }
  .opening { top: 84px; max-width: none; }
  .nav ul { display: none; }
  .chain li { grid-template-columns: 34px 1fr; padding: 16px; }

}

/* ---------------------------------------------------------------------------
   Reduced motion
   ---------------------------------------------------------------------------
   The sea still renders -- a still frame of an ocean is a photograph, and
   removing it takes the subject away from somebody who asked for less
   movement, not none. What stops is being moved: the camera goes straight to
   the beat that carries the content, and the plates arrive without sliding.
   --------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; }
  .opening, .aboard, .nav { transition: opacity 200ms linear; transform: none !important; }

}


/* ===========================================================================
   ACT III — the reel
   ---------------------------------------------------------------------------
   A tall section with a sticky stage. The scroll distance is the transport
   control: the visitor sets the pace, which a timed sequence cannot do -- it
   is always too fast for somebody taking it in and too slow for somebody who
   already has.
   =========================================================================== */

/* The reel keeps the world.
 *
 * `.act` paints a near-opaque panel so body copy survives over rendered water.
 * Right for a text section, wrong for this one: it covered the sea for the
 * whole of Act III. Nobody could tell while every scene was a solid card
 * floating on top -- the dive was the first shot with nothing in front of it,
 * and it came out black.
 *
 * Legibility is carried per element instead. The tablet and the shore panels
 * are opaque in their own right; the caption gets a scrim the width of its
 * own text. */
.act--reel {
  padding-bottom: 0;
  background: none;
  border-top: 0;
}

/* The intro paragraph still needs a ground -- it is loose type over water. */
.reel-intro {
  background: linear-gradient(180deg, rgba(5, 10, 18, 0.92), rgba(5, 10, 18, 0.72));
  padding: clamp(28px, 6vh, 64px) clamp(18px, 4vw, 40px);
  border-radius: 16px;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

.reel-caption {
  background: rgba(4, 9, 16, 0.72);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  padding: 12px 20px;
  border-radius: 999px;
  justify-self: center;
}
.reel-intro { max-width: 62ch; }
.reel { height: 640vh; position: relative; margin-top: 48px; }
.reel-stick {
  position: sticky;
  top: 0;
  height: 100vh;
  display: grid;
  place-items: center;
  grid-template-rows: 1fr auto;
  gap: 24px;
  padding: 7vh 0 9vh;
}

/* The tablet.
   A device, not a browser window: the bezel is what tells a visitor this is
   something somebody is holding on a deck rather than a screenshot of a web
   app. Kept plain -- no reflections, no floating, no perspective tilt. A
   rotated slab with a gloss sweep is the single most common way an enterprise
   product is made to look like a toy. */
.tablet, .shore {
  grid-row: 1;
  grid-column: 1;
  width: min(92vw, 520px);
  opacity: 0;
  transform: translateY(14px) scale(0.985);
  transition: opacity 620ms var(--ease), transform 620ms var(--ease);
  pointer-events: none;
}
.shore { width: min(94vw, 720px); }
[data-stage-on="tablet"] .tablet,
[data-stage-on="shore"] .shore { opacity: 1; transform: none; }

.tablet-shell {
  background: #0a121c;
  border: 1px solid var(--hairline);
  border-radius: 22px;
  padding: 14px;
  box-shadow: 0 30px 80px rgba(2, 6, 12, 0.6);
}
.tablet-screen {
  border-radius: 12px;
  overflow: hidden;
  min-height: 46vh;
  max-height: 62vh;
  overflow-y: auto;
  scrollbar-width: none;
}
.tablet-screen::-webkit-scrollbar { display: none; }

.shore-screen {
  background: #0a121c;
  border: 1px solid var(--hairline);
  border-radius: 14px;
  padding: 20px;
  box-shadow: 0 30px 80px rgba(2, 6, 12, 0.6);
}

.reel-caption {
  grid-row: 2;
  max-width: 54ch;
  text-align: center;
  color: var(--on-rail-muted);
  font-size: 15px;
  min-height: 3em;
}

/* Crossfade between scenes. The screens stack, so the outgoing one does not
   collapse the layout as it leaves. */
.tablet-screen, .shore-screen { position: relative; }
.swap-in  { opacity: 0; transform: translateY(8px); }
.tablet-screen > *, .shore-screen > * { transition: opacity 400ms var(--ease), transform 400ms var(--ease); }
.swap-out { position: absolute; inset: 0; opacity: 0; pointer-events: none; }

/* --- the product's own pieces, placed --------------------------------------
   These position the application's components inside the website's stage.
   They set no colour, no type and no spacing that the product defines -- only
   where the block sits. Anything else here would be the site quietly
   redecorating the product. */

.report-rows { display: grid; gap: 1px; margin-top: var(--space-3); }
.report-row {
  display: flex; justify-content: space-between; align-items: baseline;
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border-subtle);
}
.report-label { color: var(--text-muted, var(--on-rail-muted)); font-size: var(--text-caption); }
.report-value { font-size: var(--text-body); font-weight: 600; }
.report-value--ready { color: var(--readiness-ready-mark); }
.report-value--gaps  { color: var(--readiness-gaps-mark, var(--gaps)); }
.report-value--bad   { color: var(--readiness-notready-mark); }

.sync-lines { display: grid; gap: var(--space-2); margin-top: var(--space-3); }
.sync-line { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-body); }
.sync-tick {
  width: 22px; height: 22px; flex: none;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--readiness-ready-wash);
  color: var(--readiness-ready-mark);
  font-size: 13px; font-weight: 700;
}

.fleet-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 18px; }
.fleet-head h3 { font-size: 17px; font-weight: 600; }
.fleet-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: 1px; background: var(--hairline-soft); border: 1px solid var(--hairline-soft); border-radius: 12px; overflow: hidden; }
.fleet-stat { background: rgba(16, 26, 38, 0.8); padding: 16px 14px; display: grid; gap: 4px; }
.fleet-stat-value { font-size: 26px; font-weight: 700; line-height: 1; }
.fleet-stat-label { font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--on-rail-muted); }
.fleet-stat--ready .fleet-stat-value { color: var(--readiness-ready-mark); }
.fleet-stat--gaps  .fleet-stat-value { color: var(--gaps); }
.fleet-stat--bad   .fleet-stat-value { color: var(--readiness-notready-mark); }

.arrive {
  margin-top: 16px; padding: 16px;
  border: 1px solid rgba(47, 192, 138, 0.34);
  border-radius: 12px;
  background: rgba(47, 192, 138, 0.08);
  display: grid; gap: 4px;
}
.arrive h4 { font-size: 16px; font-weight: 600; }

.instruction { margin-top: 16px; }

@media (max-width: 720px) {
  .reel { height: 560vh; }
  .tablet, .shore { width: 94vw; }
  .tablet-screen { min-height: 42vh; max-height: 56vh; }
  .reel-caption { font-size: 14px; }

}

@media (prefers-reduced-motion: reduce) {
  .tablet, .shore { transition: opacity 200ms linear; transform: none !important; }
  .tablet-screen > *, .shore-screen > * { transition: none; }

}

/* The handover.
   While the reel has the floor, the opening steps back. Not display:none --
   it fades, because the stage behind it is the same sea and a plate that
   vanishes reads as a bug rather than a cut. */
[data-reel] .aboard,
[data-reel] .opening,
[data-reel] .skip,
[data-past] .aboard,
[data-past] .opening,
[data-past] .skip { opacity: 0; pointer-events: none; }
[data-reel] .stage::after,
[data-past] .stage::after { opacity: 0; }


/* ===========================================================================
   ACT IV — shore
   =========================================================================== */

/* Eighteen scenes, and each needs about a viewport of scroll.
   
   Not only so they can be read. The camera is a spring, so it needs *time* to
   travel -- and the dive is the longest move in the sequence, twenty units
   down through the waterline. At 62vh per scene a fast scroller outran it and
   arrived at the shore having never gone under. Distance here is what buys the
   camera the seconds it needs on a slow device, which is exactly where it has
   least of them. */
.reel { height: 1700vh; }

.arrival {
  border: 1px solid rgba(47, 192, 138, 0.34);
  border-radius: 12px;
  background: rgba(47, 192, 138, 0.08);
  padding: 20px;
  display: grid;
  gap: 8px;
}
.arrival h4 { font-size: 18px; font-weight: 600; }
.arrival-list { list-style: none; margin: 8px 0 0; padding: 0; display: grid; gap: 8px; }
.arrival-list li { display: flex; align-items: center; gap: 10px; font-size: 14px; }

/* The fleet list. Rows, not cards: a manager scanning twenty-four hulls is
   reading a list, and cards would make each one a decision. */
.fleet-rows { display: grid; gap: 1px; background: var(--hairline-soft); border: 1px solid var(--hairline-soft); border-radius: 12px; overflow: hidden; }
.fleet-row {
  background: rgba(16, 26, 38, 0.8);
  padding: 14px 16px;
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 14px;
  align-items: center;
}
/* The selected hull. A left edge rather than a fill, so the row still reads as
   one of a list rather than becoming a different kind of object. */
.fleet-row--on { background: rgba(11, 95, 191, 0.14); box-shadow: inset 3px 0 0 var(--accent); }
.fleet-row-name { font-weight: 600; font-size: 15px; }
.fleet-row-meta { font-size: 12px; color: var(--on-rail-muted); }

.v360-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(108px, 1fr)); gap: 1px; background: var(--hairline-soft); border: 1px solid var(--hairline-soft); border-radius: 12px; overflow: hidden; }
.v360-cell { background: rgba(16, 26, 38, 0.8); padding: 16px 14px; display: grid; gap: 4px; }
.v360-value { font-size: 24px; font-weight: 700; line-height: 1; }
.v360-label { font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--on-rail-muted); }
.v360-cell--ready .v360-value { color: var(--readiness-ready-mark); }
.v360-cell--gaps  .v360-value { color: var(--gaps); }
.v360-cell--bad   .v360-value { color: var(--readiness-notready-mark); }

.finding-what { font-size: 19px; font-weight: 600; line-height: 1.3; margin: 4px 0 14px; }
/* The product ships nv-band as an inline chip. Dropped into a block context it
   stretches the full width and centres, which reads as a banner rather than a
   severity mark -- the site placing the product component badly, not the
   component being wrong. */
.finding-detail .nv-band { display: inline-block; width: auto; }
.finding-meta { display: grid; grid-template-columns: auto 1fr; gap: 8px 18px; margin: 18px 0 0; }
.finding-meta dt { font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--on-rail-muted); }
.finding-meta dd { margin: 0; font-size: 14px; }

/* The evidence slot. Same honest-placeholder treatment as the crew plate: it
   states what belongs there and its real recorded dimensions rather than
   showing an invented thumbnail. The claim is that the photograph travelled
   with the finding, and a decorative stand-in would undercut it. */
/* The evidence, sized as evidence rather than as a hero image.
   At full card width the plate was taller than the finding it belongs to and
   pushed the caption off the screen -- which inverts the point. The photograph
   is corroboration for the words above it, not the subject. */
.evidence { margin: 20px 0 0; max-width: 260px; }
.evidence-plate {
  aspect-ratio: 4 / 3;
  border: 1px dashed var(--hairline);
  border-radius: 12px;
  background: var(--glass);
  display: grid;
  place-items: center;
  padding: 16px;
}
.evidence figcaption { margin-top: 10px; }

.compose-body { font-size: 16px; line-height: 1.5; margin: 12px 0 18px; max-width: 52ch; }
.compose-actions { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }

.receipt {
  border: 1px solid rgba(47, 192, 138, 0.34);
  border-radius: 12px;
  background: rgba(47, 192, 138, 0.08);
  padding: 20px;
  display: grid;
  gap: 6px;
  margin-bottom: 16px;
}
.receipt h4 { font-size: 18px; font-weight: 600; }

/* The dive. Both stages step back and the caption carries alone -- the shot is
   the subject there, and a panel over it would be answering the question the
   descent is asking. */
[data-stage-on="none"] .tablet,
[data-stage-on="none"] .shore { opacity: 0; }

@media (max-width: 720px) {
  .reel { height: 1450vh; }
  .fleet-row { grid-template-columns: 1fr auto; }
  .fleet-row-meta { display: none; }
  .finding-meta { grid-template-columns: 1fr; gap: 2px 0; }
  .finding-meta dd { margin-bottom: 10px; }

}

/* Below the reel the section is ordinary prose again, so the ground returns. */
.act--reel .chain,
.act--reel .note {
  background: linear-gradient(180deg, rgba(5, 10, 18, 0.9), rgba(5, 10, 18, 0.96));
}
.act--reel .note { padding: 24px clamp(18px, 4vw, 40px) clamp(48px, 9vh, 96px); border-radius: 0 0 16px 16px; max-width: none; }
.act--reel .chain { padding: 24px; border-radius: 16px 16px 0 0; }


/* ===========================================================================
   ACT V — the product sections
   ---------------------------------------------------------------------------
   The character changes deliberately, and it changes in three steps. Product
   sections keep the sea behind them and carry their own grounds. Safety is
   sober. The closing prose lets the ocean go entirely.
   =========================================================================== */

.product-mount { display: grid; gap: 22px; margin-top: 36px; }

.act--product {
  background: linear-gradient(180deg, rgba(5, 10, 18, 0.9), rgba(5, 10, 18, 0.95));
}

/* Safety. No transitions declared anywhere in this block, on purpose: an
   incident register that animates is a register somebody stops reading as a
   record of things that happened to people. */
.act--sober {
  background: linear-gradient(180deg, rgba(5, 10, 18, 0.95), rgba(4, 8, 14, 0.985));
}
.act--sober .lede { max-width: 66ch; }
.sub { font-size: 20px; font-weight: 600; margin: 56px 0 0; }

/* Quiet. The sea is gone and the page is a page -- a visitor reading the
   mission statement does not need water moving behind it, and leaving it there
   would be animation for its own sake, which is the one thing ruled out. */
.act--quiet {
  background: #050a12;
}

/* --- the lifecycle ------------------------------------------------------- */
.life { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; background: var(--hairline-soft); border: 1px solid var(--hairline-soft); border-radius: 14px; overflow: hidden; }
.life-step { background: rgba(16, 26, 38, 0.82); padding: 16px 20px; display: grid; grid-template-columns: 40px 1fr auto; gap: 16px; align-items: center; }
.life-no { font-family: var(--font-mono); font-size: 12px; color: var(--teal); }
.life-body h4 { font-size: 15px; font-weight: 600; }
.life-n { font-size: 20px; font-weight: 700; }
.life-step--done .life-n { color: var(--ready); }
/* The one still open. Marked, because it is the point of the list. */
.life-step--open { background: rgba(194, 138, 0, 0.09); box-shadow: inset 3px 0 0 var(--gaps); }
.life-step--open .life-n { color: var(--gaps); }

/* --- the work order ------------------------------------------------------ */
.work { background: rgba(16, 26, 38, 0.78); border: 1px solid var(--hairline-soft); border-radius: 14px; padding: 22px; }
.work-task { font-size: 19px; font-weight: 600; margin: 6px 0 12px; }
/* The provenance line: where this job came from. Marked out because it is the
   connection the whole section exists to show. */
.work-from {
  font-size: 14px; color: var(--on-rail-muted);
  padding: 12px 14px; margin-bottom: 4px;
  border-left: 2px solid var(--teal);
  background: rgba(22, 105, 127, 0.12);
  border-radius: 0 8px 8px 0;
}
.work-from strong { color: var(--on-rail); }
.work-steps { list-style: none; margin: 22px 0 0; padding: 0; display: grid; gap: 10px; }
.work-step { display: grid; grid-template-columns: 22px 1fr auto; gap: 12px; align-items: center; font-size: 14px; }
.work-step--open .sync-tick { background: rgba(194, 138, 0, 0.16); }
.work-step--open .work-what { color: var(--gaps); }
.work-when { font-size: 12px; color: var(--on-rail-muted); }

/* --- safety -------------------------------------------------------------- */
.inc { background: rgba(16, 26, 38, 0.78); border: 1px solid var(--hairline-soft); border-radius: 12px; padding: 18px 20px; display: grid; gap: 8px; }
.inc-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.inc-ref { font-weight: 700; }
.inc-what { font-size: 16px; line-height: 1.4; }
.inc-facts { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 4px; }
.inc-fact { font-size: 12px; padding: 5px 10px; border-radius: 999px; border: 1px solid var(--hairline-soft); color: var(--on-rail-muted); }
/* Only the two that are true get weight. A register where every fact is
   highlighted is a register with no facts highlighted. */
.inc-fact--on { color: var(--on-rail); border-color: rgba(214, 95, 95, 0.4); background: rgba(214, 95, 95, 0.1); }

/* --- certificates -------------------------------------------------------- */
.cert { display: grid; grid-template-columns: minmax(0, 1.4fr) auto auto minmax(0, 1.2fr); gap: 16px; align-items: center; padding: 14px 18px; background: rgba(16, 26, 38, 0.78); border: 1px solid var(--hairline-soft); border-radius: 10px; }
.cert-kind { font-weight: 600; font-size: 14px; }
.cert-until { font-size: 13px; color: var(--on-rail-muted); }
.cert-note { font-size: 12px; color: var(--on-rail-muted); }

/* --- analytics ----------------------------------------------------------- */
.stats { background: rgba(16, 26, 38, 0.78); border: 1px solid var(--hairline-soft); border-radius: 14px; padding: 22px; }
.stats-rows { display: grid; gap: 1px; background: var(--hairline-soft); border-radius: 10px; overflow: hidden; margin-top: 16px; }
.stats-row { background: rgba(10, 18, 28, 0.9); padding: 16px 18px; display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 16px; align-items: baseline; }
.stats-label { font-size: 14px; }
.stats-value { font-size: 22px; font-weight: 700; }
.stats-delta { font-size: 13px; color: var(--on-rail-muted); }
.stats-delta--healthy { color: var(--ready); }
.stats-delta--attention { color: var(--gaps); }
/* The note that stops a rising findings count reading as bad news. Spans the
   row, because it is a caveat on the figure rather than another column. */
.stats-note { grid-column: 1 / -1; font-size: 13px; color: var(--on-rail-muted); margin-top: 6px; max-width: 62ch; }

@media (max-width: 720px) {
  .life-step { grid-template-columns: 32px 1fr auto; padding: 14px; gap: 10px; }
  .cert { grid-template-columns: 1fr auto; gap: 8px; }
  .cert-until, .cert-note { grid-column: 1 / -1; }
  .stats-row { grid-template-columns: 1fr auto; }
  .stats-delta { grid-column: 2; }
  .work-step { grid-template-columns: 22px 1fr; }
  .work-when { grid-column: 2; font-size: 11px; }

}

/* ===========================================================================
   THE SECTIONS
   ---------------------------------------------------------------------------
   Act V used to be six headings with six panels of the application under
   them. It reads as a website now: written content, and drawings made out of
   this page's own type and rules.

   The drawings are deliberately built from HTML and CSS rather than as
   images. They are therefore readable, selectable, translatable, correct in
   both a 320px column and a 2560px one, and -- the part that matters most on
   a site making this particular argument -- they cannot go stale against the
   product the way a screenshot does.
   =========================================================================== */

.act--page {
  background: linear-gradient(180deg, rgba(5, 10, 18, 0.9), rgba(5, 10, 18, 0.97));
  border-top: 1px solid var(--hairline-soft);
}

/* ---------------------------------------------------------------------------
   Running text
   --------------------------------------------------------------------------- */

.prose { max-width: 62ch; }
.prose--wide { max-width: 72ch; margin-top: 34px; }
.prose p { color: var(--on-rail-muted); margin-top: 16px; }
.prose p strong, .prose p em { color: var(--on-rail); font-style: normal; }
.prose h3 {
  font-size: 19px;
  font-weight: 600;
  margin-top: 38px;
  color: var(--on-rail);
}
.prose h3 + p { margin-top: 10px; }

/* A sentence that is the point of the section, set so a reader who is
   skimming still lands on it. Not a decorative pull-quote: it is always a
   claim the surrounding paragraphs have just earned. */
.pull {
  margin-top: 30px;
  padding-left: 20px;
  border-left: 2px solid var(--teal);
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 600;
  line-height: 1.42;
  color: var(--on-rail);
  max-width: 44ch;
}

/* ---------------------------------------------------------------------------
   Text beside a drawing
   ---------------------------------------------------------------------------
   Collapses to one column early -- at 1040px rather than at a phone
   breakpoint -- because a diagram squeezed into a third of a laptop window is
   worse than a diagram with the whole width and the text above it.
   --------------------------------------------------------------------------- */

.split {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  gap: clamp(32px, 5vw, 72px);
  align-items: start;
  margin-top: 44px;
}
.split--gap { grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); }
.split--contact { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }

@media (max-width: 1040px) {
  .split, .split--gap, .split--contact { grid-template-columns: minmax(0, 1fr); gap: 36px; }

}

/* ---------------------------------------------------------------------------
   Drawings
   --------------------------------------------------------------------------- */

.dia { margin: 0; position: sticky; top: 96px; }
@media (max-width: 1040px) { .dia { position: static; } 
}

.dia figcaption {
  margin-top: 16px;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--on-rail-muted);
  max-width: 46ch;
}

/* Every drawing sits on the same plate, so the section reads as one set of
   figures rather than five unrelated graphics. */
.gapfig, .ftable, .matrix, .weigh, .feed {
  background: rgba(16, 26, 38, 0.72);
  border: 1px solid var(--hairline-soft);
  border-radius: 14px;
}

/* --- the gap ------------------------------------------------------------- */

.gapfig {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: stretch;
  overflow: hidden;
}
.gapfig-side { padding: 22px 20px; }
.gapfig-title {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--teal);
}
.gapfig-side ul { list-style: none; margin: 14px 0 0; padding: 0; display: grid; gap: 9px; }
.gapfig-side li { font-size: 13.5px; color: var(--on-rail-muted); }

.gapfig-mid {
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 12px;
  padding: 22px 14px;
  border-left: 1px dashed var(--hairline);
  border-right: 1px dashed var(--hairline);
  background: rgba(5, 10, 18, 0.5);
}
/* The break itself: a rule that stops. It is the one graphic on the page
   whose meaning is its absence. */
.gapfig-break {
  width: 2px;
  height: 46px;
  background: linear-gradient(180deg, var(--hairline), transparent 40%, transparent 60%, var(--hairline));
}
.gapfig-label {
  font-size: 11.5px;
  line-height: 1.4;
  text-align: center;
  letter-spacing: 0.04em;
  color: var(--on-rail-muted);
}

/* --- the fleet table ------------------------------------------------------ */

.ftable { overflow: hidden; }
.ftable-head, .frow {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1.1fr) auto;
  gap: 14px;
  align-items: center;
  padding: 13px 18px;
}
.ftable-head {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--on-rail-muted);
  border-bottom: 1px solid var(--hairline-soft);
}
.frow { border-bottom: 1px solid var(--hairline-soft); }
.frow:last-child { border-bottom: 0; }
.fname { font-size: 14.5px; font-weight: 600; }
.fmark { font-size: 12.5px; }
.fmark--ready { color: var(--ready); }
.fmark--gaps  { color: var(--gaps); }
.fmark--dark  { color: var(--on-rail-muted); }
.fage { font-family: var(--font-mono); font-size: 12px; color: var(--on-rail-muted); }

.frow--gaps { box-shadow: inset 3px 0 0 var(--gaps); background: rgba(194, 138, 0, 0.07); }
.frow--ready { box-shadow: inset 3px 0 0 rgba(47, 192, 138, 0.45); }
/* Struck through on the age, not the name. The vessel is fine; what is stale
   is our knowledge of her, and the drawing should say which. */
.frow--dark { box-shadow: inset 3px 0 0 var(--on-rail-muted); }
.frow--dark .fage { color: var(--on-rail); }

/* --- the question matrix -------------------------------------------------- */

.matrix {
  display: grid;
  grid-template-columns: minmax(84px, 1.3fr) repeat(3, minmax(0, 1fr));
  gap: 1px;
  padding: 1px;
  background: var(--hairline-soft);
  overflow: hidden;
}
.matrix > * { background: rgba(16, 26, 38, 0.92); padding: 14px 12px; }
.matrix-corner { background: rgba(5, 10, 18, 0.6); }
.matrix-col, .matrix-row {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--on-rail-muted);
}
.matrix-col { text-align: center; }
.matrix-row { color: var(--teal); }

.cellx { display: grid; place-items: center; min-height: 46px; }
/* A filled cell is a mark, not a tick. Ticks imply "done"; these are
   "assessed", which is a different thing and the distinction matters to
   anybody who has sat through one of these. */
.cellx--on::after {
  content: "";
  width: 9px; height: 9px;
  border-radius: 2px;
  background: var(--teal);
  box-shadow: 0 0 0 4px rgba(22, 105, 127, 0.16);
}

/* --- the two questions ---------------------------------------------------- */

.weigh { display: grid; grid-template-columns: 1fr 1fr; overflow: hidden; }
@media (max-width: 560px) { .weigh { grid-template-columns: 1fr; } 
}
.weigh-side { padding: 22px 20px; display: grid; align-content: start; gap: 10px; }
.weigh-side--bad { background: rgba(5, 10, 18, 0.45); }
.weigh-side--good { border-left: 1px solid var(--hairline-soft); }
@media (max-width: 560px) {
  .weigh-side--good { border-left: 0; border-top: 1px solid var(--hairline-soft); }

}
.weigh-tag {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--on-rail-muted);
}
.weigh-side--good .weigh-tag { color: var(--teal); }
.weigh-q { font-size: 15px; font-weight: 600; line-height: 1.35; }
.weigh-side--bad .weigh-q { color: var(--on-rail-muted); }
.weigh-note { font-size: 12.5px; line-height: 1.5; color: var(--on-rail-muted); margin-top: 4px; }

.weigh-scale { display: flex; gap: 6px; margin-top: 2px; }
.weigh-scale span {
  width: 26px; height: 26px;
  display: grid; place-items: center;
  border: 1px solid var(--hairline-soft);
  border-radius: 6px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--on-rail-muted);
}
/* The 3 is selected because 3 is always what gets selected. */
.weigh-scale .on { border-color: rgba(194, 138, 0, 0.5); background: rgba(194, 138, 0, 0.12); color: var(--gaps); }

/* --- what feeds the report ------------------------------------------------ */

.feed { display: grid; gap: 18px; padding: 22px 20px; }
.feed-in { display: flex; flex-wrap: wrap; gap: 8px; }
.feed-in span {
  font-size: 12.5px;
  padding: 7px 12px;
  border: 1px solid var(--hairline-soft);
  border-radius: 999px;
  color: var(--on-rail-muted);
}
/* One arrow, drawn as a rule and a head. A funnel graphic would be six
   elements saying what one line says. */
.feed-arrow {
  height: 26px;
  position: relative;
  border-left: 1px solid var(--hairline);
  margin-left: 18px;
}
.feed-arrow::after {
  content: "";
  position: absolute;
  left: -4px; bottom: 0;
  width: 7px; height: 7px;
  border-right: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
  transform: rotate(45deg);
}
.feed-out {
  padding: 16px 18px;
  border: 1px solid rgba(22, 105, 127, 0.4);
  background: rgba(22, 105, 127, 0.1);
  border-radius: 10px;
}
.feed-title { font-family: var(--font-display); font-size: 17px; font-weight: 600; }
.feed-sub { font-size: 12.5px; color: var(--on-rail-muted); margin-top: 2px; }

/* ---------------------------------------------------------------------------
   Numbered flows
   --------------------------------------------------------------------------- */

.flow {
  list-style: none;
  margin: 34px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 1px;
  background: var(--hairline-soft);
  border: 1px solid var(--hairline-soft);
  border-radius: 14px;
  overflow: hidden;
}
.flow li { background: rgba(16, 26, 38, 0.72); padding: 22px 20px; display: grid; gap: 8px; align-content: start; }
.flow-no { font-family: var(--font-mono); font-size: 11.5px; color: var(--teal); letter-spacing: 0.06em; }
.flow h4 { font-family: var(--font-display); font-size: 16px; font-weight: 600; }
.flow p { font-size: 13.5px; color: var(--on-rail-muted); }
.flow--4 { grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); }

/* ---------------------------------------------------------------------------
   Certificate states
   ---------------------------------------------------------------------------
   Four, and they are laid out as four equal things on purpose. Three states
   and an "other" would be exactly the collapse the copy is arguing against.
   --------------------------------------------------------------------------- */

.states {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 1px;
  background: var(--hairline-soft);
  border: 1px solid var(--hairline-soft);
  border-radius: 14px;
  overflow: hidden;
  margin-top: 34px;
}
.state { background: rgba(16, 26, 38, 0.72); padding: 22px 20px; display: grid; gap: 8px; align-content: start; }
.state h4 { font-family: var(--font-display); font-size: 16px; font-weight: 600; }
.state p { font-size: 13.5px; color: var(--on-rail-muted); }
.state--ok      { box-shadow: inset 0 3px 0 var(--ready); }
.state--soon    { box-shadow: inset 0 3px 0 var(--gaps); }
.state--unknown { box-shadow: inset 0 3px 0 var(--on-rail-muted); background: rgba(22, 105, 127, 0.08); }
.state--bad     { box-shadow: inset 0 3px 0 #d65f5f; }

/* ---------------------------------------------------------------------------
   Odds and ends
   --------------------------------------------------------------------------- */

.grid--3 { grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }
.grid--4 { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
.grid article p { line-height: 1.6; }

.plain--tight { margin-top: 12px; }
.plain--tight li { font-size: 14.5px; }

/* The one product panel left outside the story. Framed and labelled, so it
   reads as a specimen rather than as the section's content. */
.product-mount--framed {
  margin-top: 28px;
  padding: 22px;
  border: 1px solid var(--hairline-soft);
  border-radius: 16px;
  background: rgba(5, 10, 18, 0.55);
}

/* ---------------------------------------------------------------------------
   Arriving
   ---------------------------------------------------------------------------
   Blocks lift into place as they come into view. Transform only: nothing
   fades, and nothing is ever hidden.

   The first version of this faded from opacity 0.001, directly under a comment
   explaining that content must never be parked invisible waiting for an
   observer -- and then it did exactly that, and every block below the fold
   rendered blank. The rules were present, correctly ordered, correctly
   specific and matching the element; it took a CSSOM dump to establish that,
   and the content still did not appear.

   So the fade is gone rather than fixed. With transform alone the failure mode
   does not exist: if the observer never runs, every block sits sixteen pixels
   low and completely readable, which is a thing nobody will ever notice. A
   one-property effect that cannot fail beats a two-property effect that has to
   be watched.

   `:not(.in)` rather than a second overriding rule, for the same reason -- one
   selector decides the state, so there is no cascade to lose.
   --------------------------------------------------------------------------- */

@media (prefers-reduced-motion: no-preference) {
  .js .reveal { transition: transform 720ms var(--ease); }
  .js .reveal:not(.in) { transform: translateY(16px); }

}

@media (max-width: 720px) {
  .pull { font-size: 17px; }
  .matrix { grid-template-columns: minmax(70px, 1fr) repeat(3, minmax(0, 1fr)); }
  .matrix > * { padding: 10px 6px; }
  .gapfig { grid-template-columns: 1fr; }
  .gapfig-mid {
    border-left: 0; border-right: 0;
    border-top: 1px dashed var(--hairline);
    border-bottom: 1px dashed var(--hairline);
  }
  .gapfig-break { height: 2px; width: 46px; background: linear-gradient(90deg, var(--hairline), transparent 40%, transparent 60%, var(--hairline)); }
  .product-mount--framed { padding: 14px; }

}

/* ===========================================================================
   PAGES
   ---------------------------------------------------------------------------
   Ten content pages and a landing page, rather than one document with a very
   long scroll in it. What follows is the chrome they share.

   Only two pages carry the ocean. The rest get the same palette as a
   gradient: a WebGL context running behind an article somebody is reading is
   a battery complaint rather than an atmosphere, and it competes with the
   thing it is supposed to be framing.

   The treatment is instrument-grade rather than decorative -- lit hairlines,
   bezel ticks at the corners of a plate, mono labels, numbers that line up.
   That is what a ship's own equipment looks like, and it is the only kind of
   "advanced" worth chasing here: the alternative, floating holograms and
   glowing icon halos, is what every competitor in this industry already has.
   =========================================================================== */

/* The same world, without the WebGL.
   ---------------------------------------------------------------------------
   The first attempt at this was a near-black gradient with a faint glow on it,
   which is technically a background and reads as nothing at all.

   This is the sky the shader actually draws, taken apart into fixed gradients:
   a low sun off the top left, the warm band it puts along the horizon, the
   horizon itself sitting above the reading area, and deep water below. Fixed
   attachment, so scrolling a long page moves the text through the light rather
   than dragging the whole sky with it.

   It is the landing page's world seen from inside -- which is the point. A
   visitor should not feel they have left the ship when they click Fleet. */
body.page {
  background-color: #050a12;
  background-image:
    /* The sun, well off the top of the frame. */
    radial-gradient(60% 34% at 22% -6%, rgba(255, 206, 150, 0.20) 0%, rgba(255, 190, 130, 0) 70%),
    /* Its warmth along the horizon band. */
    radial-gradient(120% 22% at 34% 15%, rgba(206, 168, 136, 0.16) 0%, rgba(206, 168, 136, 0) 74%),
    /* Sky into haze into water. The horizon sits at 17%, above everything
       anybody has to read. */
    linear-gradient(180deg,
      #16293c 0%,
      #22394c 9%,
      #3c5566 15%,
      #22384a 18%,
      #12212f 34%,
      #0a131e 62%,
      #050a12 100%);
  background-attachment: fixed;
  background-repeat: no-repeat;
  background-size: cover;
}

/* The navigation does not wait for a camera that is not on this page. */
body.page .nav { opacity: 1; transform: none; }

/* A lit hairline under the navigation. One pixel, and it is the whole of the
   "futuristic" budget spent in the one place it reads: the edge between the
   chrome and the page. */
.nav::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: linear-gradient(90deg,
    rgba(110, 182, 204, 0) 0%,
    rgba(110, 182, 204, 0.32) 22%,
    rgba(110, 182, 204, 0.32) 78%,
    rgba(110, 182, 204, 0) 100%);
  pointer-events: none;
}

.nav a.on { color: var(--on-rail); }
.nav a { position: relative; }
.nav a.on::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -7px;
  height: 2px;
  border-radius: 2px;
  background: #6fb7cc;
  box-shadow: 0 0 10px rgba(111, 183, 204, 0.6);
}

/* ---------------------------------------------------------------------------
   Navigation on a narrow screen
   --------------------------------------------------------------------------- */

.nav-toggle {
  display: none;
  align-items: center;
  gap: 10px;
  font: inherit;
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--on-rail-muted);
  background: none;
  border: 1px solid var(--hairline-soft);
  border-radius: 8px;
  padding: 8px 12px;
  cursor: pointer;
}
.nav-toggle-bars, .nav-toggle-bars::before, .nav-toggle-bars::after {
  display: block;
  width: 16px; height: 1.5px;
  background: currentColor;
}
.nav-toggle-bars { position: relative; }
.nav-toggle-bars::before, .nav-toggle-bars::after { content: ""; position: absolute; left: 0; }
.nav-toggle-bars::before { top: -5px; }
.nav-toggle-bars::after  { top: 5px; }

/* ---------------------------------------------------------------------------
   The page
   --------------------------------------------------------------------------- */

.page-main {
  position: relative;
  z-index: 1;
  padding-top: clamp(92px, 12vh, 140px);
}

.page-head {
  padding: clamp(28px, 6vh, 64px) clamp(20px, 6vw, 92px) clamp(40px, 7vh, 78px);
  max-width: 1180px;
  position: relative;
}
/* The bezel tick. Two short rules meeting at the corner of the block, the way
   a scale is marked on an instrument. It is doing one job: saying where the
   content starts, without a box. */
.page-head::before {
  content: "";
  position: absolute;
  left: clamp(20px, 6vw, 92px);
  top: clamp(18px, 4vh, 44px);
  width: 34px; height: 34px;
  border-left: 1px solid rgba(111, 183, 204, 0.45);
  border-top: 1px solid rgba(111, 183, 204, 0.45);
  pointer-events: none;
}
.page-head h1 {
  font-size: clamp(30px, 4.6vw, 58px);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.02em;
  margin-top: 14px;
  max-width: 19ch;
}
.page-head .lede { margin-top: 20px; font-size: clamp(16px, 1.4vw, 19px); max-width: 60ch; }

/* The eyebrow, as an instrument label: a lit mark, then the word. */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 9px;
}
.eyebrow::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 1px;
  background: #6fb7cc;
  box-shadow: 0 0 8px rgba(111, 183, 204, 0.7);
  flex: none;
}

.page-main .act { padding-top: clamp(30px, 5vh, 56px); }

/* The dead band under every page head.
   ---------------------------------------------------------------------------
   The head's bottom padding, the first section's top padding, the prose
   block's own top margin and the first paragraph's margin all stacked: about
   140px of nothing between the lede and the first sentence, on every page.
   Each of the four was reasonable on its own, which is how it survived.

   The first section after the head carries the whole gap; the parts inside it
   give theirs up. */
.page-head + .act { padding-top: 0; }
.page-head + .act > .prose:first-child,
.page-head + .act > .split:first-child { margin-top: 0; }
.prose > p:first-child { margin-top: 0; }
/* NOT `h2:first-child + p` here. In the wide two-column layout a heading and
   its first paragraph share a row and the paragraph's top margin has to go --
   that rule lives in the grid's own media query, where it is true. Applied
   globally it also hit the narrow .prose inside a split, where the two are
   stacked, and glued every heading to the sentence under it. */
.page-main .act--page { border-top: 0; background: none; }
.page-main .act h2 {
  font-size: clamp(22px, 2.5vw, 32px);
  font-weight: 700;
  letter-spacing: -0.015em;
}
.prose h2 { margin-top: 42px; }
.prose h2:first-child { margin-top: 0; }
.prose h2 + p { margin-top: 12px; }

/* ---------------------------------------------------------------------------
   Plates
   ---------------------------------------------------------------------------
   Every drawing, card grid and flow sits on the same plate and carries the
   same corner tick, so a page reads as one set of instruments rather than as
   five unrelated graphics.
   --------------------------------------------------------------------------- */

.dia, .grid, .flow, .states { position: relative; }
.dia::after, .grid::after, .flow::after, .states::after {
  content: "";
  position: absolute;
  right: -1px; top: -1px;
  width: 16px; height: 16px;
  border-right: 1px solid rgba(111, 183, 204, 0.4);
  border-top: 1px solid rgba(111, 183, 204, 0.4);
  border-radius: 0 14px 0 0;
  pointer-events: none;
}

.dia-title { font-size: 17px; font-weight: 600; margin-bottom: 8px; }
.dia-note { font-size: 13.5px; line-height: 1.55; color: var(--on-rail-muted); max-width: 46ch; }

/* ---------------------------------------------------------------------------
   Next
   --------------------------------------------------------------------------- */

.page-next {
  padding: clamp(40px, 8vh, 96px) clamp(20px, 6vw, 92px) clamp(30px, 5vh, 60px);
  max-width: 1180px;
}
.page-next a {
  display: inline-grid;
  gap: 4px;
  padding: 18px 26px 18px 22px;
  border: 1px solid var(--hairline-soft);
  border-left: 2px solid rgba(111, 183, 204, 0.5);
  border-radius: 4px 12px 12px 4px;
  background: rgba(16, 26, 38, 0.6);
  transition: background 220ms var(--ease), border-color 220ms var(--ease), transform 220ms var(--ease);
}
.page-next a:hover {
  background: rgba(22, 105, 127, 0.18);
  border-left-color: #6fb7cc;
  transform: translateX(3px);
}
.page-next-label {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--on-rail-muted);
}
.page-next-title { font-family: var(--font-display); font-size: 21px; font-weight: 600; }

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

footer {
  position: relative;
  z-index: 1;
  border-top: 1px solid var(--hairline-soft);
  background: rgba(5, 10, 18, 0.7);
  padding: clamp(34px, 6vh, 60px) clamp(20px, 6vw, 92px);
}
.foot-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, 0.8fr);
  gap: clamp(24px, 4vw, 64px);
  max-width: 1180px;
}
.foot-brand {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: 0.14em;
  font-size: 15px;
}
.foot-line { color: var(--on-rail-muted); font-size: 13.5px; margin-top: 10px; max-width: 42ch; }
.foot-line a { color: #6fb7cc; }
.foot-links { list-style: none; margin: 0; padding: 0; columns: 2; column-gap: 24px; }
.foot-links li { margin-bottom: 8px; break-inside: avoid; }
.foot-links a { font-size: 13.5px; color: var(--on-rail-muted); }
.foot-links a:hover { color: var(--on-rail); }

/* ---------------------------------------------------------------------------
   The reel page
   ---------------------------------------------------------------------------
   Keeps the ocean, because its whole argument is a camera moving through one.
   The stage is fixed behind the content exactly as it is on the landing page.
   --------------------------------------------------------------------------- */

.stage--reel { z-index: 0; }
.stage--reel::after { display: none; }

@media (max-width: 860px) {
  .nav { flex-wrap: wrap; }
  .nav-toggle { display: inline-flex; margin-left: auto; }

  .nav ul {
    display: none;
    flex-direction: column;
    gap: 0;
    /* Full-bleed. `flex-basis: 100%` resolves against the nav's *content* box,
       so with the gutter cancelled by negative margins the panel started at 0
       and still ended 49px short of the right edge -- measured at 363 in a 412
       viewport. Negative margins move a flex item; they do not widen it. The
       basis has to carry the gutter back. */
    flex-basis: calc(100% + 2 * var(--nav-pad));
    margin: 10px calc(-1 * var(--nav-pad)) 0;
    padding: 4px 0 8px;
    border-top: 1px solid var(--hairline-soft);
    /* Taller than the screen on a small phone once nine items are open. */
    max-height: calc(100dvh - 120px);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  .nav--open ul { display: flex; }

  /* An opened menu needs something behind it.
     ---------------------------------------------------------------------
     The bar's background is a gradient that fades to transparent, which is
     right for a 60px strip sitting over open sky and catastrophic for a
     367px panel sitting over a crew member and six lines of speech: the page
     read straight through the links.

     On every page, not `body.page` -- the landing page is `body.landing`, so
     the one page where this menu opens over a photograph and a moving ocean
     was the only one that did not get a solid backdrop. */
  .nav--open {
    background: rgba(6, 12, 20, 0.97);
    backdrop-filter: blur(18px) saturate(130%);
    -webkit-backdrop-filter: blur(18px) saturate(130%);
    box-shadow: 0 18px 50px rgba(2, 6, 12, 0.6);
  }

  /* 17px was the measured height of a link. A finger is about 44. */
  .nav ul a {
    display: block;
    padding: 13px var(--nav-pad);
    min-height: 44px;
    font-size: 16px;
    border-bottom: 1px solid var(--hairline-soft);
  }
  .nav ul li:last-child a { border-bottom: 0; }
  .nav a.on {
    background: rgba(22, 105, 127, 0.16);
    box-shadow: inset 3px 0 0 #6fb7cc;
  }
  .nav a.on::after { display: none; }

  .foot-grid { grid-template-columns: minmax(0, 1fr); }
  .page-head h1 { max-width: none; }

}

/* The 100vh offset on `main` exists so the landing page's fixed stage has a
   screen to itself before the content arrives. A content page has no stage,
   so the same rule pushed every word one viewport below the fold -- the page
   rendered as an empty gradient with a navigation on it. */
/* The 100vh offset on `main` belongs to the landing page alone: it is what
   gives that page's fixed stage a screen to itself before content arrives.
   Every other page, ocean or not, starts with its own heading. */
body.page main { margin-top: 0; }

/* A page with the ocean on it still needs its words to be readable over it. */
body.page--stage .page-main { background: linear-gradient(180deg, rgba(5, 10, 18, 0.55) 0%, rgba(5, 10, 18, 0.82) 22%, rgba(5, 10, 18, 0.9) 100%); }
body.page--stage .page-head { background: none; }

/* And the `.act` plate is for sections stacked over a moving ocean. On a
   content page there is nothing behind them to separate from, so the panel
   backgrounds, the blur and the rules all come off and the page is a page. */
body.page .act--page {
  background: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-top: 0;
  padding: clamp(24px, 4vh, 44px) clamp(20px, 6vw, 92px) clamp(36px, 7vh, 80px);
  max-width: 1180px;
}

/* A solid bar rather than a fade, because on a content page the nav sits over
   text the moment anybody scrolls. */
body.page .nav {
  background: rgba(6, 12, 20, 0.86);
  backdrop-filter: blur(14px) saturate(120%);
  -webkit-backdrop-filter: blur(14px) saturate(120%);
}

/* On the stage, the eyebrow and the headline are read against whatever the sea
   is doing behind them -- which at the opening beat includes the sun. The h1
   already carries a shadow; the label needs one too. */
.stage .eyebrow { color: #b9c9d8; text-shadow: 0 1px 14px rgba(5, 10, 18, 0.9); }
.stage .eyebrow::before { box-shadow: 0 0 10px rgba(111, 183, 204, 0.9); }

/* ---------------------------------------------------------------------------
   Alignment
   ---------------------------------------------------------------------------
   Every block on a content page carried a max-width and none of them carried a
   margin, so all of them sat hard against the left edge with half a wide
   screen empty beside them. A measure is supposed to be a column, and a column
   in the corner of the window reads as a page that has not been laid out.

   One rule centres the lot. The measures themselves are unchanged -- running
   text stays near 65 characters, because that is a reading decision rather
   than a layout one.
   --------------------------------------------------------------------------- */

.page-head,
body.page .act--page,
.act--reel > .reel-intro,
.act--reel > .chain,
.act--reel > .note,
.page-next,
.foot-grid {
  margin-inline: auto;
}

/* A touch wider, so the headline and the diagrams have room and the two
   columns of a .split are not squeezed. */
.page-head, body.page .act--page, .page-next, .foot-grid { max-width: 1240px; }
.page-head h1 { max-width: 21ch; }
.prose { max-width: 66ch; }
.prose--wide { max-width: 74ch; }

/* On a wide screen a single column of prose in a 1240px frame still leaves a
   lot of room on one side. The section heading moves out into it: text on the
   right, heading on the left, which is an editorial layout rather than a
   stack, and it uses the width the page actually has. */
@media (min-width: 1100px) {
  /* Section headings move out into the margin: heading on the left, text on
     the right. An editorial layout rather than a stack, and it uses width the
     page otherwise wastes.

     Auto-placement does the work -- the heading is pinned to column one and
     everything else to column two, so a heading and its first paragraph land
     on the same row by themselves. What it does NOT do is put a gap between
     one group and the next, because that gap would have to be a row gap and a
     row gap would also space every paragraph apart. So the gap is a margin on
     the two items that start a group, applied to both so the row grows evenly
     and the heading stays level with its own first line. */
  body.page .act--page > .prose--wide {
    display: grid;
    grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
    column-gap: clamp(32px, 4vw, 72px);
    max-width: none;
  }
  body.page .act--page > .prose--wide > * { grid-column: 2; max-width: 66ch; }
  body.page .act--page > .prose--wide > h2 {
    grid-column: 1;
    font-size: 20px;
    line-height: 1.26;
    color: #b9c9d8;
    align-self: start;
  }
  body.page .act--page > .prose--wide > h2,
  body.page .act--page > .prose--wide > h2 + p { margin-top: 52px; }
  body.page .act--page > .prose--wide > h2:first-child,
  body.page .act--page > .prose--wide > h2:first-child + p { margin-top: 0; }

  /* A pull sentence belongs with the text, not out in the heading rail. */
  body.page .act--page > .prose--wide > .pull { max-width: 46ch; }

}

/* ===========================================================================
   THE PILOT CARD
   ---------------------------------------------------------------------------
   A real pilot card is the document a master hands a pilot as he comes over
   the rail: a rigid printed grid of the particulars the pilot needs before he
   can do anything useful. Every master and superintendent in this trade has
   filled one in.

   The enquiry form is one. Paper-white on a dark site, because it is a
   document and a document on a ship is printed -- the contrast is the point,
   and it is the one place on this site that is deliberately not the site's own
   palette. It asks the same kind of question a pilot card asks, for the same
   reason, which is why it is this rather than a decorative idea.

   The palette here is hard-coded rather than tokenised, on purpose: it is a
   single committed object, not a themeable surface, and a "paper" that follows
   the interface palette is not paper.
   =========================================================================== */

.pilotcard {
  --ink: #16202b;
  --ink-soft: #55636f;
  --rule: #b9bfc4;
  --paper: #f2efe7;

  background: var(--paper);
  color: var(--ink);
  border-radius: 3px;
  padding: 0;
  box-shadow: 0 24px 60px rgba(2, 6, 12, 0.55), 0 2px 0 rgba(255, 255, 255, 0.06);
  font-size: 14px;
  overflow: hidden;
  /* Sat very slightly off square, the way a card handed across a rail sits on
     a chart table. One degree; enough to read as an object, not enough to make
     anybody feel the page is broken. */
  transform: rotate(-0.35deg);
}
@media (prefers-reduced-motion: reduce) { .pilotcard { transform: none; } 
}

.pc-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 20px 14px;
  border-bottom: 2px solid var(--ink);
}
.pc-doc {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
.pc-sub { font-size: 12px; color: var(--ink-soft); margin-top: 3px; }
.pc-rev {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-soft);
  border: 1px solid var(--rule);
  padding: 3px 7px;
  white-space: nowrap;
}

/* Fields, ruled like a form rather than boxed like an app. The line under the
   answer is the line you write on. */
.pc-grid { display: grid; grid-template-columns: 1fr 1fr; }
.pc-f {
  display: grid;
  gap: 3px;
  padding: 11px 20px 12px;
  border-bottom: 1px solid var(--rule);
  border-right: 1px solid var(--rule);
  min-width: 0;
}
.pc-grid > .pc-f:nth-child(2n) { border-right: 0; }
.pc-f--wide { border-right: 0; }
.pc-f > span {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.pc-f input, .pc-f textarea {
  font: inherit;
  font-size: 15px;
  color: var(--ink);
  background: none;
  border: 0;
  border-radius: 0;
  padding: 2px 0 1px;
  width: 100%;
  min-width: 0;
  resize: vertical;
}
.pc-f input::placeholder, .pc-f textarea::placeholder { color: #9aa3ab; }
.pc-f input:focus-visible, .pc-f textarea:focus-visible {
  outline: 0;
  box-shadow: 0 2px 0 var(--ink);
}
.pc-f:focus-within > span { color: var(--ink); }

.pc-set {
  border: 0;
  border-bottom: 1px solid var(--rule);
  margin: 0;
  padding: 11px 20px 13px;
  display: flex;
  flex-wrap: wrap;
  gap: 8px 18px;
  align-items: center;
}
.pc-set legend {
  float: left;
  width: 100%;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-soft);
  padding: 0 0 8px;
}
.pc-set label { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; }
.pc-set input { accent-color: #16202b; width: 15px; height: 15px; margin: 0; }
.pc-set span { font-size: 14px; }

.pc-foot { padding: 16px 20px 18px; display: grid; gap: 14px; }
.pc-note { font-size: 11.5px; line-height: 1.5; color: var(--ink-soft); max-width: 46ch; }
.btn--pc {
  justify-self: start;
  background: #16202b;
  color: #f2efe7;
  border-radius: 2px;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.btn--pc:hover { background: #24313f; transform: none; }

/* ---------------------------------------------------------------------------
   Who answers the phone
   --------------------------------------------------------------------------- */

.person {
  margin-top: 14px;
  padding: 20px 22px;
  border: 1px solid var(--hairline-soft);
  border-left: 2px solid #6fb7cc;
  border-radius: 3px 12px 12px 3px;
  background: rgba(16, 26, 38, 0.6);
}
.person-name { font-family: var(--font-display); font-size: 22px; font-weight: 700; }
.person-role {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #6fb7cc;
  margin-top: 4px;
}
.person-reach { list-style: none; margin: 16px 0 0; padding: 0; display: grid; gap: 8px; }
.person-reach li { display: grid; grid-template-columns: 62px 1fr; gap: 12px; align-items: baseline; }
.person-reach span {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--on-rail-muted);
}
.person-reach a { font-size: 17px; font-weight: 600; color: var(--on-rail); }
.person-reach a:hover { color: #6fb7cc; }
.person-demo { margin-top: 16px; font-size: 13.5px; color: var(--on-rail-muted); }

@media (max-width: 620px) {
  .pc-grid { grid-template-columns: 1fr; }
  .pc-grid > .pc-f { border-right: 0; }
  .pilotcard { transform: none; }

}

/* The telephone number on the landing page. Below the ten destinations,
   because somebody who already knows what this is should not have to read a
   menu to find a way of reaching a person. */
.callus {
  grid-column: 1 / -1;
  margin-top: 14px;
  font-size: 13.5px;
  color: var(--on-rail-muted);
}
.callus a {
  font-family: var(--font-mono);
  font-size: 15px;
  color: var(--on-rail);
  border-bottom: 1px solid rgba(111, 183, 204, 0.5);
  padding-bottom: 1px;
}
.callus a:hover { color: #6fb7cc; }
.callus span { display: block; margin-top: 4px; font-size: 12px; opacity: 0.75; }
.crew-speaking .callus { visibility: hidden; }
.crew-speaking.crew-open .callus { visibility: visible; }

/* ===========================================================================
   THE CREW PHOTOGRAPH
   ---------------------------------------------------------------------------
   Supplied as a landscape frame of somebody in hi-vis with a tablet, shot in
   flat overcast daylight. The scene it has to sit in is a low sun over open
   water at dusk. Dropped in untouched it reads as a cut-out, which is the same
   failure the hulls had before they got reflections.

   So it is graded rather than placed: cooled, darkened, desaturated a little,
   and finished with a wash of the sea's own colour over the top. Nothing is
   done to the file -- it stays exactly as it was given -- because framing and
   grading are decisions that should stay adjustable, and a destructively
   processed asset cannot be re-cropped when the photograph is replaced.
   =========================================================================== */

.crew--real .crew-plate img {
  /* 3:4 out of a 3:2 frame crops a third of the width away. 64% keeps him in
     the middle of what survives instead of slicing him down one edge. */
  object-position: 64% 42%;
  filter: brightness(0.62) contrast(1.12) saturate(0.62) hue-rotate(-8deg);
}

/* The sea, over the top. A photograph taken in one light and a scene rendered
   in another never quite agree; a thin wash of the scene's own colour is what
   makes them agree. Multiply, so it deepens the shadows rather than fogging
   the whole plate. */
.crew--real .crew-plate::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  border-radius: 13px;
  mix-blend-mode: multiply;
  background: linear-gradient(170deg, #7d93ad 0%, #4a6180 42%, #2b3d55 100%);
}
/* And the light coming back the other way -- the low sun is off to the left of
   the frame in this scene, so the plate catches a little of it on that edge. */
.crew--real .crew-plate::after {
  height: 100%;
  background:
    linear-gradient(100deg, rgba(255, 206, 150, 0.16) 0%, rgba(255, 206, 150, 0) 34%),
    linear-gradient(180deg, rgba(5, 10, 18, 0) 52%, rgba(5, 10, 18, 0.62) 100%);
  z-index: 2;
}

/* Alive, not animated.
   ---------------------------------------------------------------------------
   A still photograph beside six lines of someone speaking reads as a portrait
   hung next to a conversation. A very slow drift -- twenty-eight seconds for
   one pass, about a percent of scale -- is enough for the eye to stop reading
   it as a picture. Any faster and it becomes a slideshow effect, which is the
   genre this site is built to avoid. */
@media (prefers-reduced-motion: no-preference) {
  .crew--real .crew-plate img {
    animation: crewdrift 28s ease-in-out infinite alternate;
    will-change: transform;
  }

}
@keyframes crewdrift {
  from { transform: scale(1.04) translate3d(-0.6%, 0.4%, 0); }
  to   { transform: scale(1.09) translate3d(0.8%, -0.7%, 0); }
}

/* ===========================================================================
   THE PAGE HERO
   ---------------------------------------------------------------------------
   Every content page opens on a rendered still of the same ocean the landing
   page runs, at a camera and an hour chosen for that page. See hero.js for why
   it is rendered rather than photographed, and why it is one frame rather than
   a loop.

   It sits behind the page head rather than above it: the headline is read over
   the water, the way the landing page's is, and the band dissolves into the
   page so there is no seam where an image stops and a document starts.
   =========================================================================== */

.pagehero-band {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: clamp(420px, 62vh, 720px);
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}
.pagehero, .pagehero-hulls { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

/* The dissolve. Two of them: down into the page, and a little from the left so
   the headline always has something quiet behind its first word. */
.pagehero-band::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(5, 10, 18, 0.34) 0%, rgba(5, 10, 18, 0.12) 34%, rgba(5, 10, 18, 0.62) 78%, rgba(5, 10, 18, 0.72) 100%),
    linear-gradient(90deg, rgba(5, 10, 18, 0.66) 0%, rgba(5, 10, 18, 0.12) 46%, rgba(5, 10, 18, 0) 72%);
}

/* The band dissolves rather than ending.
   ---------------------------------------------------------------------------
   Fading the scrim to a flat colour left a visible horizontal seam wherever
   the band stopped -- on a phone, straight through the middle of the lede,
   because the page's own background is a different gradient and the two do not
   meet at the same value.

   Masking the whole band instead means there is no second surface: the canvas,
   the hulls and the scrim all go transparent together and the page background
   is simply what is left. Nothing has to match anything. */
.pagehero-band {
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 52%, rgba(0,0,0,.55) 82%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 0%, #000 52%, rgba(0,0,0,.55) 82%, transparent 100%);
  background: none;
}

/* The page sits over it. `position: relative` on the main is what puts the
   text above the band; the band is absolutely positioned in the body. */
body.page .page-main { position: relative; z-index: 1; }
body.page .page-head { min-height: clamp(360px, 52vh, 600px); display: flex; flex-direction: column; justify-content: flex-end; }
body.page .page-head h1 { text-shadow: 0 2px 30px rgba(5, 10, 18, 0.7); }
body.page .page-head .lede { text-shadow: 0 1px 18px rgba(5, 10, 18, 0.8); }

/* The first section clears the band, so a paragraph is never read over water. */
body.page .page-head + .act { padding-top: clamp(36px, 6vh, 72px); }

@media (max-width: 860px) {
  /* Taller than the head, so the lede is never the thing the band ends on. */
  .pagehero-band { height: clamp(430px, 62vh, 620px); }
  body.page .page-head { min-height: clamp(280px, 38vh, 420px); }
  .pagehero-band::after {
    background:
      linear-gradient(180deg, rgba(5, 10, 18, 0.42) 0%, rgba(5, 10, 18, 0.2) 30%, rgba(5, 10, 18, 0.8) 74%, #050a12 100%);
  }
}

@media (prefers-reduced-motion: reduce) {
  /* Nothing to do: it is a still. Named so nobody adds motion to it later
     without noticing there is a reason it has none. */
  .pagehero-band { }
}
