/* ============================================================
   LITPROFIT — style.css
   Made by ALDY

   The brand navy is #15196D, taken from the guideline artwork itself
   (the full-bleed background of the supplied page SVG) rather than
   sampled off a compressed render. The rest of the --navy-* ramp is
   that hue carried down to usable ground tones. Nothing outside the
   palette block below names a colour.
   ============================================================ */

:root {
  /* ---------- brand palette (Brand Guidelines › Colour) ---------- */
  --navy-900: #070824;   /* deepest ground */
  --navy-800: #0b0d3a;
  --navy-700: #0f1250;
  --navy-600: #15196D;   /* THE brand navy — containers, sign face, CTA */
  --navy-500: #1c2191;
  --navy-400: #2d35be;
  --white:    #ffffff;
  --grey-200: #d6d8e2;
  --grey-300: #b0b3c4;
  --grey-400: #8d90a6;
  --grey-500: #6b6e85;
  --grey-600: #454863;
  --black:    #04040d;

  /* ---------- semantic roles ---------- */
  --bg:        var(--navy-900);
  --bg-2:      var(--navy-800);
  --panel:     rgba(255, 255, 255, 0.035);
  --panel-2:   rgba(255, 255, 255, 0.07);
  --line:      rgba(255, 255, 255, 0.10);
  --line-2:    rgba(255, 255, 255, 0.20);
  --text:      var(--white);
  --muted:     var(--grey-300);
  /* was --grey-500, which measured 3.44:1 on the glass panels and 3.92:1 on
     solid navy -- under AA either way, and it carries 18 usages of small
     uppercase label text, none of it large enough to qualify for the 3:1
     exemption. --grey-400 is the next step on the existing scale: 5.2:1, and
     still three steps down from white, so the labels stay quiet. */
  --muted-2:   var(--grey-400);
  --accent:    var(--grey-200);
  --brand:     var(--navy-600);

  /* ---------- type ----------
     Montserrat is a STAND-IN. The wordmark on the guidelines is a
     heavier geometric sans; swap --font once the typography page
     names it (and once a web licence is confirmed). */
  /* "Montserrat Fallback" is a metric-matched stand-in defined in fonts.css —
     it keeps the layout from reflowing when the webfont is blocked, which
     Safari's Lockdown Mode and content blockers both do. Ordinary system
     names follow it for anything that has neither. */
  --font: "Montserrat", "Montserrat Fallback", system-ui, -apple-system,
          "Segoe UI", Helvetica, Arial, sans-serif;

  /* min(vw, vh): the headline is the tallest thing in the hero, and sizing
     it on width alone made a wide, short laptop the worst case -- the screen
     inflated the type exactly where there was no room for it. */
  --h1: clamp(40px, min(6.6vw, 9vh), 104px);
  --h2: clamp(29px, 4vw, 62px);
  --h3: clamp(22px, 2.3vw, 34px);
  --h4: clamp(19px, 1.6vw, 24px);
  --text-xl: clamp(17px, 1.5vw, 21px);
  --text-l: 16px;
  --text-m: 14px;
  --text-s: 13px;
  --label: 11px;

  /* ---------- layout ---------- */
  --container: 1440px;
  --pad: clamp(20px, 4vw, 64px);
  --nav-h: 76px;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* ---------- rivets (Guidelines › Graphic › Pattern) ----------
     The guidelines call for "small squares arranged in a strict grid". Drawn
     as a flat all-over field, that reads as a blueprint grid — which is the
     ALPROJECTS motif, and this is a different company. So: the same square at
     the same strict spacing, but built as a RIVET — a bright square face with
     a dark square dropped a fraction down-right, so it sits proud of the
     surface the way a fastener does on steel plate.

     That also decides where it belongs. Rivets run along seams and plate
     edges, not across open faces, so the primary use is a seam at a section
     boundary rather than wallpaper behind the text.

     A data URI rather than a file: smaller than the request that would fetch
     it, and it cannot 404. Opacity is deliberately NOT baked in — the tile is
     drawn at full strength and each use dials it down, so one tile serves
     every context. */
  --rivet: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30'%3E%3Crect x='11.7' y='11.7' width='9' height='9' rx='1.8' fill='%23000000' fill-opacity='0.7'/%3E%3Crect x='10.5' y='10.5' width='9' height='9' rx='1.8' fill='%23ffffff'/%3E%3C/svg%3E");
  --rivet-pitch: 30px;
  /* The seam has its own tile. --rivet is a 30px square and the seam
     repeated it every 30px, which on a wide screen is 114 rivets in a
     row -- a dotted rule rather than a line of fasteners. This is the
     same 9px rivet in a 56px tile, so the spacing opens up and the rivet
     itself does not change size. It needed a separate tile because
     background-size scales the artwork: widening --rivet-pitch would
     have enlarged every rivet with it, including the two corner marks
     .cornered draws from the same token on the media banners. */
  --rivet-seam: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='56' height='30'%3E%3Crect x='24.7' y='11.7' width='9' height='9' rx='1.8' fill='%23000000' fill-opacity='0.7'/%3E%3Crect x='23.5' y='10.5' width='9' height='9' rx='1.8' fill='%23ffffff'/%3E%3C/svg%3E");
  --rivet-seam-pitch: 56px;

  /* ---------- the brand angle ----------
     The monogram is built on a single diagonal. Measured off the artwork,
     its long slash runs 24.41deg from vertical and the short one 25.03deg —
     they agree, so this is a real constant of the mark rather than a guess.
     Every slanted element on the site uses it, which is what makes the
     geometry read as LITPROFIT's rather than as generic diagonal styling. */
  --slant: 24.4deg;

  /* The monogram as a data URI, so the watermark does not depend on BASE and
     cannot 404. White-filled; dialled down by whatever uses it. */
  /* Liquid glass, colourless on purpose. The tint is white -> 70% grey at 6%
     in both themes: hue in the tint fights the hue arriving through the panel
     and the surface goes muddy. Every colour in these surfaces comes from
     what is behind them, which on this site means the hero's indigo glow or
     the frost gradient -- never from a pastel mesh laid down to feed them. */
  --glass-tint-top: rgba(255, 255, 255, 0.06);
  --glass-tint-bottom: rgba(179, 179, 179, 0.06);
  --glass-rim-top: rgba(255, 255, 255, 0.5);
  --glass-rim-hairline: rgba(255, 255, 255, 0.1);
  --glass-rim-bottom: rgba(255, 255, 255, 0.06);
  --glass-border: rgba(255, 255, 255, 0.08);

  --mark-img: url("data:image/svg+xml,%3Csvg width='272' height='200' viewBox='0 0 272 200' fill='none' xmlns='http://www.w3.org/2000/svg' role='img' aria-label='LITPROFIT'%3E %3Cpath d='M0.577201 199.507C0.258422 199.507 0 199.249 0 198.93V24.7151C0 24.3963 0.258416 24.1379 0.577195 24.1379H130.854L141.971 0.332973C142.066 0.12983 142.269 0 142.494 0H196.616C197.034 0 197.314 0.430806 197.143 0.812608L186.724 24.1379H222.381C249.863 24.1379 267.882 53.0312 255.904 77.8935L232.232 127.028C225.681 140.625 211.974 149.261 196.945 149.261H156.752C156.33 149.261 156.051 148.824 156.228 148.441L178.728 99.8421C178.822 99.638 179.027 99.5074 179.252 99.5074H186.22C190.975 99.5074 195.263 96.6287 197.085 92.213L203.994 75.4642C207.205 67.6793 201.513 59.1133 193.129 59.1133H86.6278C86.4013 59.1133 86.1957 59.2457 86.1022 59.452L22.6978 199.169C22.6042 199.375 22.3987 199.507 22.1722 199.507H0.577201Z' fill='%23ffffff'/%3E %3Cpath d='M159.339 79.4005C159.51 79.0185 159.231 78.5866 158.812 78.5866H104.698C104.471 78.5866 104.265 78.7203 104.172 78.928L50.353 199.187C50.1821 199.569 50.4615 200 50.8799 200H271.423C271.742 200 272 199.742 272 199.423V125.208C272 124.889 271.742 124.631 271.423 124.631H258.186C257.946 124.631 257.729 124.785 257.644 125.009C242.906 163.617 215.566 174.384 193.095 174.384H117.534C117.115 174.384 116.836 173.952 117.007 173.57L159.339 79.4005Z' fill='%23ffffff'/%3E %3C/svg%3E");
}

/* ============================================================
   RIVET SEAM
   A single row of rivets along a section edge, like the joint between
   two plates. `.seam-top` / `.seam-bottom` are opt-in, so a section only
   gets a seam where a seam makes sense.
   ============================================================ */
/* The seam fades in and out rather than switching on.

   Measured across a 3024px capture, the row of rivets ran from x=47 to
   x=2976: edge to edge at full strength, cut off by the viewport at both
   ends. A line of fasteners does not begin at the left edge of a screen, and
   the eye reads a strip that starts and stops at nothing as a stamp laid over
   the page rather than as a join in it.

   Two masks. Along the row, so the fasteners emerge from the margins and
   return to them; and down through it, so the band has a falloff instead of a
   top and a bottom edge. The rivets in the middle are untouched -- this
   changes only how the row meets everything around it, which is the whole of
   what a seam is for. */
/* One joint, one row.

   Where a section that ends in a seam is followed by one that begins with
   one, the page draws two rows of rivets a few pixels apart -- which is not
   a stronger joint, it is two joints where the sheet has one edge. It
   happened in six places: the partners band into the machine on the home
   page, and the same band and the completed-works strip into the call to
   action on three other pages.

   Fixed here rather than by taking .seam-top off those particular sections,
   because that is a fact about what happens to sit above them today. Sections
   get reordered; a rule that reads the arrangement stays right when they do.
   The BOTTOM seam wins: it belongs to the plate that ends, and it is the one
   already carrying the fade into the section below it. */
.seam-bottom + .seam-top::before { content: none; }

.seam-top, .seam-bottom { position: relative; }
.seam-top::before, .seam-bottom::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  height: var(--rivet-pitch);
  background-image: var(--rivet-seam);
  background-size: var(--rivet-seam-pitch) var(--rivet-pitch);
  background-repeat: repeat-x;
  opacity: 0.45;
  pointer-events: none;
  z-index: 2;
  /* Two mask layers, intersected: the horizontal fade and the vertical one.
     -webkit- as well, because Safari only dropped the prefix in 15.4 and a
     mask that is ignored is a seam back to full strength, not a broken one.

     The fade reaches a third of the way in from each side. At 14% it was a
     fade you had to be told about; the point is that the row has no visible
     beginning, and a short ramp still reads as an edge with a gradient on it
     rather than as fasteners coming out of the margin. */
  -webkit-mask-image:
    linear-gradient(90deg, transparent, #000 32%, #000 68%, transparent),
    linear-gradient(180deg, transparent, #000 42%, #000 58%, transparent);
  mask-image:
    linear-gradient(90deg, transparent, #000 32%, #000 68%, transparent),
    linear-gradient(180deg, transparent, #000 42%, #000 58%, transparent);
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}

/* NO rule through the rivets. One was added here and taken out again: a line
   joining them turns a row of fasteners into a border, and a border is the
   hard edge this whole change exists to remove. The fasteners mark the join
   on their own; that is what fasteners do. */
.seam-top::before { top: 0; }
.seam-bottom::after { bottom: 0; }

/* ============================================================
   RESET
   ============================================================ */
* { margin: 0; padding: 0; box-sizing: border-box; }

/* overflow-x belongs on <html>, NOT <body>: setting it on body makes
   body a scroll container, which changes how position:sticky resolves.
   `clip` clips without creating a scroll container; `hidden` is the
   fallback for older Safari. This is the safety net — the real
   guarantee is min-width:0 on flex/grid children, further down. */
html {
  overflow-x: hidden;
  overflow-x: clip;
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ---------- ambient field ----------
   Glass shows what is behind it, and behind flat navy there is nothing to
   show -- a glass panel on this ground was a faintly outlined grey box. The
   recipe's answer is an ambient mesh, and the one it ships is violet, pink
   and cyan, which on an industrial site reads as a SaaS landing page.

   This is the same device in the palette the site already owns: the hero has
   carried radial-gradient(90% 70% at 78% 8%, rgba(38,38,140,0.5)) since the
   start. Three cool radials -- indigo, steel, a cold slate -- all low alpha,
   all well under the saturation of the hero's own glow. Fixed to the
   viewport, so it does not travel with the scroll and never reads as a
   pattern.

   Verify this before styling a single panel: with no glass anywhere the page
   must already look subtly uneven. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(58rem 44rem at 76% 6%, rgba(38, 38, 140, 0.26), transparent 62%),
    radial-gradient(50rem 40rem at 12% 34%, rgba(32, 58, 112, 0.18), transparent 64%),
    radial-gradient(54rem 46rem at 88% 78%, rgba(28, 44, 92, 0.20), transparent 62%);
}

/* ---------- and a moving light over it ----------
   The field above is static. The glass on this page -- the stat strip, the two
   partner cards, the certificate list, the menu -- all show whatever is behind
   them, and behind them nothing ever moved, so the panels were as fixed as the
   ground. This is the light that moves, and the glass picks it up without any
   panel animating anything itself.

   A separate FIXED layer rather than animating the field: the field is behind a
   document 8,000px tall, and handing the compositor a layer that size to
   transform is not worth it on a phone. Fixed means it is only ever one screen.

   inset:-30% so no edge of the layer can scroll into view as it drifts, and
   40s alternate so it never snaps back to a start position.

   Cool only. alprojects carries a warm gradient in its equivalent and it works
   there; on this site warm reads as cheap, so all three of these are indigo
   through steel. */
body::after {
  content: "";
  position: fixed;
  inset: -30%;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(44% 46% at 24% 26%, rgba(48, 58, 150, 0.55) 0%, transparent 68%),
    radial-gradient(38% 42% at 78% 70%, rgba(38, 74, 128, 0.34) 0%, transparent 64%),
    radial-gradient(36% 38% at 66% 12%, rgba(58, 66, 168, 0.42) 0%, transparent 70%);
  animation: ground-drift 40s ease-in-out infinite alternate;
  will-change: transform;
}
@keyframes ground-drift {
  from { transform: translate3d(-13%, -8%, 0) scale(1); }
  to   { transform: translate3d(16%, 12%, 0) scale(1.42); }
}
@media (prefers-reduced-motion: reduce) { body::after { animation: none; } }

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--text-l);
  font-weight: 400;
  line-height: 1.65;
  /* a long email or URL must not be able to widen the layout */
  overflow-wrap: break-word;
  -webkit-font-smoothing: antialiased;
}

img, svg, video { display: block; max-width: 100%; height: auto; }
a { color: inherit; text-decoration: none; }
button, input, select, textarea { font: inherit; color: inherit; }
ul, ol { list-style: none; }

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

/* anchor targets must not land underneath the fixed header */
:target, [id] { scroll-margin-top: calc(var(--nav-h) + 24px); }

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--pad);
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  background: var(--white);
  color: var(--navy-900);
  padding: 12px 20px;
  font-size: var(--text-m);
  font-weight: 600;
}
.skip-link:focus { left: 0; }

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

/* ============================================================
   TYPE PRIMITIVES
   ============================================================ */
h1, h2, h3, h4, h5, h6 {
  font-weight: 700;
  line-height: 1.04;
  /* tracking tightens as the type grows — at 104px the default spacing
     reads loose, at 19px the same value would read cramped */
  letter-spacing: -0.032em;
  text-wrap: balance;
}
h3, h4, h5, h6 { letter-spacing: -0.015em; }
h1 { font-size: var(--h1); }
h2 { font-size: var(--h2); }
h3 { font-size: var(--h3); }
h4 { font-size: var(--h4); letter-spacing: -0.01em; }

/* The guidelines set supporting text in light caps with wide tracking
   (`LITPROFIT`, `BRAND GUIDELINES`). This is that style. */
.eyebrow {
  display: flex;
  align-items: center;
  gap: 14px;
  font-size: var(--label);
  font-weight: 500;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--muted-2);
  margin-bottom: 22px;
}
/* the hairline that trails an eyebrow — a technical-drawing tic */
.eyebrow::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, var(--line), transparent);
  max-width: 180px;
}
/* a slanted tick leading it, cut at the monogram's own angle */
.eyebrow::before {
  content: "";
  flex: none;
  width: 3px;
  height: 13px;
  background: var(--white);
  transform: skewX(calc(var(--slant) * -1));
}
.eyebrow-plain::before { display: none; }
.eyebrow-plain { display: block; }
.eyebrow-plain::after { display: none; }

/* Section index, after the guidelines' own page numbering (`21 // 36`).
   Gives the page a spine — you always know where you are in the sequence. */
.eyebrow-num { color: var(--white); font-weight: 600; }
.eyebrow-of { color: var(--grey-600); }

/* `//` is a brand device, taken from the guidelines' page numbering
   (`33 // 36`). Used as a separator in eyebrows and breadcrumbs. */
.sep { color: var(--grey-600); margin-inline: 0.55em; font-weight: 400; }

.lead {
  font-size: var(--text-xl);
  color: var(--muted);
  max-width: 62ch;
  line-height: 1.6;
}

/* 9vw came to 130px top and bottom on a 1440px screen -- 260px of a 900px
   screen spent before any content, which is what put most of this site's
   sections over one screenful. Tightened so a section is a screen; the rhythm
   is still generous, it is just no longer the largest thing in it. */
.section { padding-block: clamp(50px, 6.2vw, 104px); }
/* No ground of its own at all.
   Alternating .section and .section-alt banded the page. Sampled down the left
   gutter it stepped (7,8,36) -> (10,11,50) -> back, in hard jumps of 14 and 22
   with delta ZERO for hundreds of pixels between them: not a gradient, stripes
   of two different navies. And they drowned the drifting field completely.

   One continuous ground now. The sections are still separated -- by the rivet
   seams and by their own heads, which is separation you read rather than
   separation you notice. With nothing painted over it, the field and its
   moving light are finally the only variation on the page, which is what they
   were built to be. */
.section-alt { background: transparent; }
.section-tight { padding-block: clamp(38px, 4.4vw, 72px); }

/* 30ch here was a mistake worth naming: the cap applies to the whole
   section-head, so it squeezed 62px headings into four-line wraps AND
   narrowed the lead inside it. Constrain the heading and the lead
   separately, and let text-wrap:balance even out the last line. */
.section-head { margin-bottom: clamp(28px, 3.4vw, 50px); max-width: 68ch; }
.section-head h2 { max-width: 20ch; }
.section-head .lead { max-width: 54ch; }
.section-head h2 { margin-bottom: 22px; }

/* Stacked, every section head measured 720px inside a 1440px container -- the
   right half of the page was empty under the heading four times over on the
   home page alone, which reads as unfinished rather than as air. Set the
   heading and its lead side by side and the head spans the measure it is
   sitting in. Bottom-aligned so the lead's last line sits on the heading's
   baseline; the eyebrow keeps its own full-width row above both. */
@media (min-width: 900px) {
  .section-head:has(.lead) {
    max-width: none;
    display: grid;
    grid-template-columns: minmax(0, 1.02fr) minmax(0, 0.98fr);
    column-gap: clamp(40px, 6vw, 96px);
    align-items: end;
  }
  .section-head:has(.lead) > .eyebrow { grid-column: 1 / -1; }
  .section-head:has(.lead) > h2 { grid-column: 1; margin-bottom: 0; }
  .section-head:has(.lead) > .lead {
    grid-column: 2;
    max-width: 46ch;
    /* the heading sits on a much larger body, so its descender line is well
       below the lead's -- nudge the lead down to meet it */
    margin-bottom: 0.28em;
  }
}

/* ============================================================
   BUTTONS
   Minimum 44px tall: WCAG 2.2 target size, and the thing a thumb
   actually has to hit.
   ============================================================ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 50px;
  padding: 14px 30px;
  font-size: var(--text-m);
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  border: 1px solid transparent;
  border-radius: 2px;
  cursor: pointer;
  transition: background 0.3s var(--ease), color 0.3s var(--ease),
              border-color 0.3s var(--ease), transform 0.3s var(--ease);
}
.btn-solid { background: var(--white); color: var(--navy-900); }
.btn-solid:hover { background: var(--grey-200); }
@media (hover: hover) and (pointer: fine) {
  .btn-solid:hover { transform: translateY(-2px); }
}

.btn-outline { border-color: var(--line-2); color: var(--text); }
.btn-outline:hover { border-color: var(--white); background: rgba(255,255,255,0.07); }

.btn-row { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 38px; }

/* ============================================================
   HEADER
   ============================================================ */
.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  /* Glass, not a dark plate. The tint is colourless on purpose: hue in the
     tint fights the hue coming through from the page and the bar goes muddy,
     so the colour arrives from whatever is passing underneath. The rim is
     what makes it read as a polished edge rather than a translucent
     rectangle -- a highlight along the top, a hairline inside, a shadow along
     the bottom. */
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0.035));
  /* brightness() in the backdrop, not more opacity in the tint. A glass bar
     has to hold its contrast over whatever happens to be passing under it, and
     what passes under this one includes workshop photographs: measured over
     the rendered page, the language links fell to 3.14:1 against a print on
     the about page. Darkening the backdrop keeps the bar see-through -- shapes
     still move through it -- while guaranteeing the ground beneath the type. */
  -webkit-backdrop-filter: blur(18px) saturate(150%) brightness(0.52);
  backdrop-filter: blur(18px) saturate(150%) brightness(0.52);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.14),
    inset 0 -1px 0 rgba(255, 255, 255, 0.05);
  border-bottom: 1px solid transparent;
  transition: background 0.4s var(--ease), border-color 0.4s var(--ease);
}
.site-header.is-stuck {
  /* Once content is running under it the bar has to carry its own contrast,
     but 0.96 was not glass at all -- it was a solid plate with a blur behind
     it doing nothing. This keeps the page visible through it. */
  background: linear-gradient(180deg,
    rgba(10, 11, 34, 0.06), rgba(10, 11, 34, 0.03));
  border-bottom-color: var(--line);
}
/* Safari's Lockdown Mode and older engines drop backdrop-filter entirely, and
   a 7%-white bar over live content is unreadable. Fall back to the plate. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-header { background: rgba(8, 8, 31, 0.88); }
  .site-header.is-stuck { background: rgba(8, 8, 31, 0.97); }
}

.nav {
  display: flex;
  align-items: center;
  gap: 28px;
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--pad);
}

/* min-height, not padding: the wordmark placeholder is text, so without it
   the brand link is only as tall as its line box (19px) and misses the
   24px target minimum that the old <img> met by accident. */
.brand { display: flex; align-items: center; gap: 11px; flex: none; min-height: 44px; }
.brand-mark { height: 25px; width: auto; }
.brand-lockup { height: 27px; width: auto; }
.footer-brand .brand-lockup { height: 30px; }

/* The name alongside the monogram. Montserrat 700 stands in for the
   guidelines' heavier geometric wordmark until that is supplied as vector
   artwork; the tracking is tightened to sit closer to it. */
.brand-word {
  font-size: 20px;
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 1;
  color: var(--white);
  white-space: nowrap;
}
.footer-brand .brand { margin-bottom: 18px; }
.footer-brand .brand-mark { height: 28px; }
.footer-brand .brand-word { font-size: 22px; }

.nav-links {
  display: flex;
  align-items: center;
  gap: clamp(12px, 1.6vw, 28px);
  margin-left: auto;
  min-width: 0;
}
.nav-links a {
  position: relative;
  font-size: var(--text-m);
  /* Brighter than --muted, and the reason is the glass. The bar is now
     see-through enough that a workshop photograph passes under the nav, and
     the contrast the type has to hold is against that, not against a plate.
     Buying it back in the ink rather than in the tint is what lets the bar
     stay transparent -- muted grey needed nearly twice the tint to clear AA. */
  color: rgba(255, 255, 255, 0.88);
  padding-block: 12px;
  transition: color 0.25s var(--ease);
  white-space: nowrap;
}
.nav-links a:hover, .nav-links a[aria-current="page"] { color: var(--white); }
/* underline marker on the current page — desktop only, where the nav is a row */
@media (min-width: 981px) {
  .nav-links a[aria-current="page"]::after {
    content: "";
    position: absolute;
    left: 0; right: 0; bottom: 6px;
    height: 1px;
    background: var(--white);
  }
}

.nav-actions { display: flex; align-items: center; gap: 14px; flex: none; }

/* the header call-to-action — Calendly when configured, contacts until then */
.btn-book {
  min-height: 42px;
  padding: 10px 20px;
  font-size: var(--text-s);
  letter-spacing: 0.08em;
  background: var(--white);
  color: var(--navy-900);
  white-space: nowrap;
}
.btn-book:hover { background: var(--grey-200); }

.burger {
  display: none;
  flex: none;               /* a flex item will otherwise shrink below its width */
  width: 44px; height: 44px;
  background: none;
  border: 1px solid var(--line-2);
  border-radius: 2px;
  cursor: pointer;
  position: relative;
}
.burger span {
  position: absolute;
  left: 12px;
  width: 18px; height: 1.5px;
  background: var(--white);
  transition: transform 0.3s var(--ease), opacity 0.2s var(--ease);
}
.burger span:nth-child(1) { top: 16px; }
.burger span:nth-child(2) { top: 21px; }
.burger span:nth-child(3) { top: 26px; }
.burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(5px) rotate(45deg); }
.burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-5px) rotate(-45deg); }

/* ============================================================
   HERO
   ============================================================ */
.hero {
  position: relative;
  display: flex;
  /* Centred, not bottom-aligned. flex-end was right while the trust strip was
     the block's last line and wanted to sit on the hero's bottom edge. With
     the strip gone the same rule left the headline low: 440px of empty screen
     above it and 40px below the buttons. */
  align-items: center;
  /* Exactly one screen, not 92% of one. At 92vh the hero stopped 35px short
     of the fold and the next section's rivet seam showed underneath it as a
     thin strip -- not a glimpse of what follows, just a line that looks like a
     mistake. Capped at 1080px so a very tall monitor gets a genuine reveal of
     the band below rather than a void under the headline. */
  min-height: min(100svh, 1080px);
  /* Every vertical measure in here used to scale with viewport WIDTH only, so
     the hero grew as the screen got wider while the space to put it in did
     not. On a MacBook Pro 14 the trust strip finished 96px below the fold and
     at 1280x720 it was 176px below: the partner logos, the certificates and
     the frost control were all simply invisible on load.

     min(Xvw, Yvh) takes whichever is the tighter constraint, so the rhythm
     opens up on a tall screen and closes on a short one, continuously and
     with no breakpoint to fall off. */
  padding-top: calc(var(--nav-h) + clamp(26px, min(6vw, 6.5vh), 70px));
  padding-bottom: clamp(26px, min(5vw, 4.4vh), 72px);
  overflow: hidden;
  background: var(--navy-900);
  isolation: isolate;
}
/* The photograph is 800px wide at source, which is soft for a full-bleed
   hero. Held well back it reads as texture rather than as a picture, which
   is both truer to the brand's dark mockups and hides the resolution. */
.hero-media {
  position: absolute;
  inset: 0;
  z-index: -2;
}
.hero-media img {
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0.3;
  filter: saturate(0.55) contrast(1.05);
}
.hero-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(90% 70% at 78% 8%, rgba(38, 38, 140, 0.5), transparent 60%),
    linear-gradient(180deg, rgba(8,8,31,0.86) 0%, rgba(8,8,31,0.5) 42%, var(--navy-900) 96%);
}
/* No all-over field here — the photograph is the texture, and a grid over it
   was the thing that read as the ALPROJECTS blueprint. The hero closes on a
   rivet seam instead (`.seam-bottom` in the markup), so the section boundary
   reads as a plate joint. */

.hero-inner {
  width: 100%;
  /* Nudged below centre. Centring alone put the block slightly high -- 258px
     of screen above it against 163 below at 1440x900 -- because the hero's own
     padding-top already carries the header and the optical centre of a block
     whose largest element is at the top sits lower than its geometric one.
     Scaled to viewport height and capped, so a short laptop gets a smaller
     push and the buttons keep their air underneath. */
  margin-top: clamp(0px, 4vh, 52px);
}
.hero h1 { max-width: 15ch; margin-bottom: clamp(14px, min(2.4vw, 3vh), 30px); }
.hero .lead { max-width: 52ch; }

/* We consult -> We organise -> We ensure.
   Set as three numbered steps rather than a run-on line: it is a sequence,
   and numbering it says so without needing the arrows. */
.promise {
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  margin-top: clamp(18px, min(4vw, 3.8vh), 52px);
  max-width: 620px;
  border-top: 1px solid var(--line);
}
.promise li { min-width: 0; flex: 1 1 170px; }
/* All three carry a rule, and each one leads somewhere. Only the first was lit
   before, with the other two on a transparent border -- which read as one live
   step and two dead labels, when in fact all three are the company describing
   what it does. Each now links to its own paragraph further down the page, so
   the strip is a contents list for the section that explains it rather than
   decoration that happens to be numbered. */
.promise a {
  display: block;
  padding: clamp(10px, 1.9vh, 18px) 22px 0 0;
  border-top: 2px solid var(--line-2);
  margin-top: -1px;
  transition: border-color 0.25s var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .promise a:hover { border-top-color: var(--white); }
  .promise a:hover .step-num { color: var(--white); }
}
.promise a:focus-visible { border-top-color: var(--white); }
.promise a:focus-visible .step-num { color: var(--white); }
/* A finger has no hover to find these with, and the rule is the only thing
   saying they are more than labels -- so on touch they are simply all lit. */
@media (hover: none) {
  .promise a { border-top-color: var(--line-2); min-height: 44px; }
}
.promise .step-num { transition: color 0.25s var(--ease); }
.promise .step-num {
  display: block;
  font-size: var(--label);
  font-weight: 600;
  letter-spacing: 0.2em;
  color: var(--grey-400);
  margin-bottom: 7px;
}
.promise .step-label {
  font-size: var(--text-m);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--white);
}

/* The last 12px, on screens short enough that the tuning above still does not
   quite fit -- a 1280x720 laptop. Aimed at height alone: a phone is short too,
   but there the hero is a different shape and the rule below would be solving
   a problem it does not have, hence the width floor. */
@media (max-height: 780px) and (min-width: 861px) {
  .hero { padding-bottom: clamp(14px, 2.6vh, 40px); }
  .hero h1 { margin-bottom: clamp(8px, 1.8vh, 22px); }
  .hero .lead { font-size: clamp(15px, 2.2vh, 19px); }
  .promise { margin-top: clamp(12px, 2.2vh, 40px); }
  .promise li { padding-top: clamp(6px, 1.2vh, 14px); }
}

/* scroll cue — a rule that draws itself down, so the hero has a bottom edge
   that invites rather than just stops */
.scroll-cue {
  position: absolute;
  right: var(--pad);
  bottom: 0;
  width: 1px;
  height: clamp(48px, 7vw, 92px);
  background: linear-gradient(180deg, transparent, var(--line-2));
  overflow: hidden;
}
.scroll-cue::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--white);
  animation: cue 2.6s var(--ease) infinite;
}
@keyframes cue {
  0%   { transform: translateY(-100%); }
  60%  { transform: translateY(100%); }
  100% { transform: translateY(100%); }
}
@media (max-width: 980px) { .scroll-cue { display: none; } }

/* ============================================================
   PARTNERSHIP BAND — BITZER / DANFOSS
   The two representations are the company's strongest credential, so
   they get a section of their own rather than a line in a list.
   ============================================================ */
/* The glow is a background LAYER, not a pseudo-element: .seam-top and
   .seam-bottom need ::before and ::after, and a section can only have one
   of each.

   Centred at 88% 50% it sat squarely across the card row. That did not matter
   while the cards were opaque tiles -- they covered it. Once they became one
   glass panel it came through, and measured 22 levels of blue between the left
   and right halves: BITZER visibly dimmer than DANFOSS, two partners of equal
   standing rendered unequal. Moved up behind the section head, where it still
   lights the band and the two cards measure identical. */
.partners-band {
  position: relative;
  overflow: hidden;
  /* The glow only -- var(--bg-2) under it made this a hard band, a step of 22
     going in and 22 coming out. Removing the fill was not enough on its own:
     the glow was centred at 10% down with a 70% radius, so it still had most
     of its strength where the section is cut off, and overflow: hidden turned
     that into a rule across the full width. Both seams here already own
     ::before and ::after, so there is nowhere to hang a masking layer -- the
     falloff has to finish inside the box instead. Centred, radius 46% of the
     height, transparent by 66% of that: the glow is out at 30% from the
     middle and both edges are bare. Alpha is up to pay for the smaller field. */
  background:
    radial-gradient(62% 46% at 88% 48%, rgba(45, 53, 190, 0.38), transparent 66%);
}
.partners-band > * { position: relative; z-index: 1; }

.partner-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  /* Three named bands the cards share, so the role label, the logo and the
     body text each start on the same line across both cards. The official
     roles are long enough to wrap at two lines in Lithuanian and Russian --
     and only ever in ONE of the two cards -- which left the boxes uneven and
     the logos below them out of step. Reserving two lines everywhere would
     have bought that alignment with dead space at full width. */
  grid-template-rows: auto auto 1fr;
  gap: 0;
  background: linear-gradient(180deg, var(--glass-tint-top), var(--glass-tint-bottom));
  border: 1px solid var(--glass-border);
  box-shadow:
    inset 0 1px 0 var(--glass-rim-top),
    inset 0 0 0 1px var(--glass-rim-hairline),
    inset 0 -1px 0 var(--glass-rim-bottom);
  -webkit-backdrop-filter: blur(14px) saturate(130%);
  backdrop-filter: blur(14px) saturate(130%);
}
.partner {
  min-width: 0;
  border-left: 1px solid var(--line);
  padding: clamp(30px, 3.6vw, 52px);
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid;
  gap: 16px;
  transition: background 0.35s var(--ease);
}
/* Subgrid is the enhancement, not the layout: without it the cards fall back
   to a flex column exactly as before, merely unaligned. */
@supports not (grid-template-rows: subgrid) {
  .partner { display: flex; flex-direction: column; }
}
/* The hover used to repaint an opaque fill, which is exactly what rule three
   forbids -- it covers the very thing the panel is showing. Lift the tint
   instead: same brightening, nothing painted over the backdrop. */
.partner:first-child { border-left: 0; }
.partner:hover { background: rgba(255, 255, 255, 0.05); }
/* NB: these two are scoped `.partner .x` on purpose. Written as bare
   `.partner-name`, the generic `.partner p` rule below out-specifies them
   (class + element beats class), and both collapse to 14px body text. */
.partner .partner-name {
  font-size: clamp(30px, 3.6vw, 52px);
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 1;
  color: var(--white);
}

/* ---------- partner logo plate ----------
   A heading whose content IS the logo, so the alt text is the heading text
   and the name is not announced twice. White plate because both marks are
   full-colour brand assets — BITZER's green diamond, Danfoss's red badge —
   and recolouring another company's trademark is not ours to do. */
/* No white plate. Both marks now sit straight on the navy: BITZER's green
   diamond had its surrounding white flooded out (the white script inside the
   diamond is enclosed, so it survives), and the Danfoss badge is red with
   white lettering, which needs no ground of its own. A white slab behind
   each one was reading as a sticker stuck onto the page. */
.partner-logo {
  display: flex;
  align-items: center;
  margin: 0 0 4px;
  align-self: flex-start;
}
.partner-logo img {
  height: 46px;
  width: auto;
  max-width: 100%;
}
.partner .partner-role {
  display: inline-flex;
  align-self: flex-start;
  /* the box hugs its text; the ROW is what both cards share */
  justify-self: start;
  align-items: center;
  gap: 8px;
  padding: 7px 14px;
  border: 1px solid var(--line-2);
  border-radius: 2px;
  font-size: var(--label);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--white);
}
.partner p { color: var(--muted); font-size: var(--text-m); }

/* ============================================================
   FACT STRIP
   ============================================================ */
/* One glass panel, not four tiles. The strip used the hairline trick -- a 1px
   grid gap over a --line container background, with each cell painting an
   opaque fill on top. That cannot become glass: translucent cells would let
   the divider colour wash through every one of them, and a child of a glass
   panel is supposed to paint no surface at all. So the container carries the
   glass and the cells carry nothing but a divider rule. */
/* THE FIGURES ALONG THE FOOT OF THE HERO
   These were a glass panel of their own, a screen further down. In the hero a
   second glass box under the buttons is a second thing competing with the
   drawing behind it, so they are a measurement rule instead: one hairline
   across the top, a tick above each figure, nothing else. That is what the
   eyebrow rules on this site already do.

   Written as a panel plus a variant that switched every panel property back
   off, and the switching-off lost to a mobile rule for the panel further down
   the file -- so the phone kept getting two columns whatever this said. There
   is only one form of this element on the site now, so there is only one rule
   for it. */
.facts {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0;
  border-top: 1px solid var(--line);
  margin-top: clamp(30px, 4vw, 52px);
  /* 780 gave 173px of usable column and "years in refrigeration" wrapped in it
     while the other three did not, so the row read as ragged rather than
     ruled. 880 clears the longest label on one line in both languages. */
  max-width: 880px;
}
.fact { position: relative; min-width: 0; padding: 20px 22px 0 0; }
/* the tick, sitting on the rule above each figure */
.fact::before {
  content: "";
  position: absolute;
  top: -1px; left: 0;
  width: 1px; height: 9px;
  background: var(--muted-2);
}
.fact-value {
  font-size: clamp(23px, 2.3vw, 34px);
  font-weight: 700;
  letter-spacing: -0.04em;
  line-height: 1;
  /* tabular figures so the four values sit on a common rhythm rather than
     each one setting its own width */
  font-variant-numeric: tabular-nums lining;
}
.fact-label {
  font-size: var(--label);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted-2);
  margin-top: 4px;
}

@media (max-width: 720px) {
  /* Two by two, tightened. Four across saves about 50px of height and was
     tried for it, but 85px of column puts "years in refrigeration" on three
     lines and "civilinė atsakomybė" on two, which is not a rule, it is a
     paragraph in four columns. Two columns keep every label on one line in
     both languages. On a small phone the second row sits just under the fold,
     and that is the right trade: these are the last thing in the hero, so a
     reader reaches them by scrolling once, which they were going to do. */
  .facts { max-width: none; margin-top: 26px; grid-template-columns: 1fr 1fr; }
  .fact { padding: 14px 10px 0 0; }
  .fact-value { font-size: 21px; }
  /* "Years in refrigeration" is 22 characters and the longest label in either
     language; at 10px on 0.07em it came to 158px in a 157px column and wrapped
     alone, which is the ragged row again one size down. */
  .fact-label { font-size: 9.5px; letter-spacing: 0.04em; margin-top: 3px; }
  /* the second row needs its own rule to sit its ticks on */
  .fact:nth-child(n + 3) { border-top: 1px solid var(--line); margin-top: 6px; padding-top: 14px; }
}

/* ============================================================
   SERVICE CARDS — with media
   ============================================================ */
/* Four equal cards, one row.
   This was a feature card beside a stack of compact ones, and what that made
   on the page was three thumbnails beside three paragraphs -- a directory
   listing under a heading that says "four disciplines, one contractor". The
   depth argument for singling out refrigeration is answered elsewhere: there
   is a live compressor two sections further down. */
.card-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
}
@media (max-width: 1080px) { .card-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px)  { .card-grid { grid-template-columns: 1fr; } }
/* No frame of its own and no radius: the grid's 1px gap over a ruled ground
   is the frame, so four cards read as one instrument divided, not as four
   boxes lying next to each other. A rounded panel with a fill was the single
   most catalogue-looking thing on the page. */
.card {
  display: flex;
  flex-direction: column;
  min-width: 0;                 /* grid children default to min-width:auto */
  background: linear-gradient(180deg, var(--glass-tint-top), var(--glass-tint-bottom));
  overflow: hidden;
  transition: background 0.35s var(--ease), border-color 0.35s var(--ease),
              transform 0.35s var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  a.card:hover { background: rgba(255, 255, 255, 0.055); }
}
/* There was a white glare sweeping 460% across the card over 850ms here,
   skewed to the monogram's diagonal. It is gone. A shine wiping across a card
   is the single most template-looking effect on the web, it ran at nearly
   three times the hover budget, and it was one of FIVE things this card did at
   once on hover -- background lift, border, a 5px jump, the sweep and a photo
   zoom. The pile-up was the problem, not any one of them.

   What is left is the card reacting without the card moving: the border
   brightens, the photograph wakes up, and the arrow advances. Three signals,
   all inside 260ms, none of which announce themselves. */
a.card { position: relative; }

.card-media {
  position: relative;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background: var(--navy-700);
}
.card-media img {
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0.62;
  filter: saturate(0.6);
  transition: transform 0.26s var(--ease), opacity 0.26s var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  /* No scale. A slow zoom on a thumbnail is the other half of the template
     look, and it moved the one thing on the card worth looking at. The photo
     brightens and loses a little of its desaturation instead -- it wakes up
     without going anywhere. */
  a.card:hover .card-media img { opacity: 0.82; filter: saturate(0.78); }
}
.card-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(8,8,31,0.15), rgba(8,8,31,0.85));
}
/* The number used to be a solid brand chip with a diagonal cut, pinned to the
   corner of the photograph. That is a sticker on a stock image, and it is the
   detail that made the block read as cheap. It is a mono eyebrow above the
   title now -- the same device as every other numbered thing on this site. */
.card-num {
  display: block;
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.18em;
  color: var(--muted-2);
  margin-bottom: 10px;
}

.card-body { display: flex; flex-direction: column; flex: 1; padding: 26px 26px 24px; }
.card h2, .card h3 { font-size: var(--h4); margin-bottom: 12px; letter-spacing: -0.01em; }
.card p { color: var(--muted); font-size: var(--text-m); }
.card-more {
  margin-top: auto;
  padding-top: 24px;
  font-size: var(--text-s);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
}
.card-more::after {
  content: " \2192";
  display: inline-block;
  transition: transform 0.2s var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  a.card:hover .card-more::after { transform: translateX(4px); }
}

/* ============================================================
   SPLIT (text + panel)
   ============================================================ */
.split {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: clamp(32px, 5vw, 80px);
  align-items: start;
}
.split > * { min-width: 0; }

.pillars { display: grid; gap: 4px; }
.pillar {
  padding: 24px 26px;
  border: 1px solid var(--line);
  background: var(--panel);
  transition: background 0.3s var(--ease), border-color 0.3s var(--ease);
}
.pillar:hover { background: var(--panel-2); border-color: var(--line-2); }
.pillar h3 { font-size: var(--h4); margin-bottom: 10px; }
.pillar p { color: var(--muted); font-size: var(--text-m); }

/* a framed image panel */
.media-panel {
  position: relative;
  border: 1px solid var(--line);
  overflow: hidden;
  background: var(--navy-700);
}
/* Matched to the service cards. At 0.8 this sat noticeably brighter than every
   other photograph on the page and pulled the eye off the heading beside it;
   the cards rest at 0.62 and wake to 0.82. Same numbers here, same curve, and
   the panel is a link so the wake means something. */
.media-panel { display: block; }
.media-panel img {
  width: 100%;
  object-fit: cover;
  opacity: 0.62;
  filter: saturate(0.6);
  transition: opacity 0.26s var(--ease), filter 0.26s var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  a.media-panel:hover img { opacity: 0.82; filter: saturate(0.78); }
}
/* no :focus-visible rule -- it is tabindex=-1 and cannot take focus */

/* ---------- corner rivets ----------
   The guidelines' pattern block sets a solid square at each of its four
   corners. Here that becomes four rivets, as though the panel were bolted
   down at the corners. Two elements carrying two rivets each, rather than
   four pseudo-elements. */
.cornered { position: relative; }
.cornered::before,
.cornered::after {
  content: "";
  position: absolute;
  left: 14px; right: 14px;
  height: var(--rivet-pitch);
  background-image: var(--rivet), var(--rivet);
  background-size: var(--rivet-pitch) var(--rivet-pitch);
  background-repeat: no-repeat;
  background-position: left center, right center;
  opacity: 0.85;
  pointer-events: none;
  z-index: 2;
}
.cornered::before { top: 6px; }
.cornered::after { bottom: 6px; }

/* ============================================================
   CLIENT RAIL
   A row of cards that scrolls sideways instead of a fixed grid. The old wall
   hard-coded five columns because ten logos divide evenly by five — remove one
   client and it left a ragged half-empty row, which is exactly what happened.
   A rail does not care how many there are.

   BITZER and DANFOSS keep their own section above: they are manufacturers we
   represent, not customers, and merging the two would misstate both.
   ============================================================ */
.client-rail-wrap { position: relative; }
.client-rail {
  display: flex;
  gap: 14px;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scroll-behavior: smooth;
  padding-bottom: 6px;
  scrollbar-width: thin;
  scrollbar-color: var(--line-2) transparent;
}
.client-rail::-webkit-scrollbar { height: 6px; }
.client-rail::-webkit-scrollbar-thumb { background: var(--line-2); }
.cc { flex: 0 0 300px; scroll-snap-align: start; min-width: 0; }
.cc > a, .cc-inner {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: 22px 22px 24px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 2px;
  position: relative;
  transition: border-color 0.3s var(--ease), background 0.3s var(--ease),
              transform 0.3s var(--ease);
}
.cc > a:hover { border-color: var(--line-2); background: var(--panel-2); }
@media (hover: hover) and (pointer: fine) {
  .cc > a:hover { transform: translateY(-3px); }
}
.cc > a:focus-visible { outline-offset: 2px; }

/* Client logos sit directly on the card. */
.cc-plate {
  display: grid;
  place-items: center;
  padding: 8px 4px;
  margin-bottom: 18px;
  aspect-ratio: 16 / 9;
}
/* Knocked out to white, straight on the card — no plate.
   The plate was there because five of the nine logos are dark navy or black
   and measured 1.0-1.7:1 against this ground: invisible. Knocking them out
   solves that by making every mark the same white, which is also the usual
   convention for a client wall on a dark page.

   The cost is any mark carrying knockout DETAIL: shape and detail both go to
   white together. Ocean Whale's whale collapses into a plain disc, and
   Baltreids is only 66px wide at source so its wordmark goes mushy. Both need
   a proper reverse logo from the client — see the README. The other seven
   come through intact, globe lines and anchor and all. */
.cc-plate img {
  max-height: 76px;
  max-width: 100%;
  width: auto;
  filter: brightness(0) invert(1);
  opacity: 0.82;
  transition: opacity 0.3s var(--ease);
}
.cc > a:hover .cc-plate img { opacity: 1; }
.cc-name { font-size: var(--h4); font-weight: 700; letter-spacing: -0.015em; line-height: 1.2; }
.cc-desc {
  margin-top: 7px; font-size: var(--text-s); color: var(--muted-2);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.04em;
}
.cc-go {
  position: absolute; top: 14px; right: 15px;
  font-size: 13px; color: var(--muted-2); transition: color 0.3s var(--ease);
}
.cc > a:hover .cc-go { color: var(--white); }

/* Arrows disable at each end rather than sitting there doing nothing, and are
   absent on touch, where swiping is the obvious gesture. */
.rail-btn {
  position: absolute; top: 96px;
  width: 44px; height: 44px;
  display: grid; place-items: center;
  background: rgba(7, 8, 36, 0.92);
  border: 1px solid var(--line-2);
  border-radius: 2px; color: var(--text); font-size: 16px; cursor: pointer; z-index: 2;
  transition: background 0.25s var(--ease), opacity 0.25s var(--ease);
}
.rail-btn:hover { background: var(--brand); }
.rail-prev { left: -16px; }
.rail-next { right: -16px; }
.rail-btn[disabled] { opacity: 0; pointer-events: none; }
@media (hover: none) { .rail-btn { display: none; } }
@media (max-width: 620px) { .cc { flex: 0 0 250px; } }

/* ============================================================
   DOCUMENT LIST (certificates)
   ============================================================ */
.docs {
  display: grid;
  gap: 0;
  background: linear-gradient(180deg, var(--glass-tint-top), var(--glass-tint-bottom));
  border: 1px solid var(--glass-border);
  box-shadow:
    inset 0 1px 0 var(--glass-rim-top),
    inset 0 0 0 1px var(--glass-rim-hairline),
    inset 0 -1px 0 var(--glass-rim-bottom);
  -webkit-backdrop-filter: blur(14px) saturate(130%);
  backdrop-filter: blur(14px) saturate(130%);
}
/* The certificate itself, beside what it says. A page that lists
   accreditations and shows none of them is a page of claims; the thumbnail is
   what lets a reader recognise the society's mark and the company's name
   before deciding whether to open the PDF. */
.doc {
  display: grid;
  grid-template-columns: clamp(150px, 15vw, 186px) 1fr;
  align-items: start;
  gap: clamp(18px, 2.4vw, 32px);
  border-top: 1px solid var(--line);
  padding: clamp(20px, 2.4vw, 30px);
  min-width: 0;
  transition: background 0.3s var(--ease);
}
.doc:first-child { border-top: 0; }
.doc:hover { background: rgba(255, 255, 255, 0.05); }
.doc-shot {
  display: block;
  border: 1px solid var(--line-2);
  background: var(--white);
  overflow: hidden;
  /* Square, cropped to the head of the page. The whole A4 shrunk to thumbnail
     width is a grey rectangle nobody can read; the top third carries the
     society's mark, the title and the certificate number, which is what makes
     it recognisable as the document it claims to be. */
  aspect-ratio: 1 / 1;
}
.doc-shot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
}
.doc-body { display: block; min-width: 0; }
.doc-name { display: block; font-size: var(--h4); font-weight: 700; min-width: 0; }
.doc-note {
  display: block;
  margin-top: 2px;
  font-size: var(--text-s);
  color: var(--muted-2);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
/* number, issued, valid until -- read off the document, not off the file */
.doc-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 26px;
  margin: 16px 0 18px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.1em;
  color: var(--white);
}
.doc-facts span { display: block; }
.doc-facts i {
  display: block;
  font-style: normal;
  color: var(--grey-400);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  margin-bottom: 4px;
}
.doc-get {
  display: block;
  font-size: var(--text-s);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
}
.doc-size { color: var(--muted-2); font-weight: 400; margin-left: 10px; }
@media (max-width: 560px) {
  .doc { grid-template-columns: 112px 1fr; }
}

/* ============================================================
   PAGE HEAD / PROSE
   ============================================================ */
.page-head {
  padding-top: calc(var(--nav-h) + clamp(48px, 7vw, 104px));
  padding-bottom: clamp(28px, 4vw, 52px);
}
.page-head h1 { margin-bottom: 24px; }

.crumb { font-size: var(--text-s); color: var(--muted-2); margin-bottom: 18px; }
.crumb a { display: inline-block; padding-block: 4px; }
.crumb a:hover { color: var(--white); }

/* banner image at the top of a service page */
.page-media {
  position: relative;
  margin-bottom: clamp(36px, 5vw, 64px);
  border: 1px solid var(--line);
  overflow: hidden;
  aspect-ratio: 21 / 8;
  background: var(--navy-700);
}
.page-media img {
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0.72;
  filter: saturate(0.6);
}
/* The workshop frames are roughly 4:3 and the banner is 21:8, so the crop is
   severe and centring it is wrong: in these photographs the machine sits above
   the middle and the bottom third is bare floor. Bias the window upward. */
.page-media--low img { object-position: center 38%; }

.page-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(8,8,31,0.2), rgba(8,8,31,0.75));
}

.prose { max-width: 74ch; padding-bottom: clamp(50px, 7vw, 100px); }

/* .prose sits on the same element as .container on 27 pages. .container
   centres itself with margin-inline:auto, so the narrower of the two
   max-widths -- 74ch -- was what got centred: the text column floated in the
   middle of the viewport, starting at x=385 while the banner above it, the
   breadcrumb and every section head began at x=58. Nothing lined up with
   anything. Keep the container at full width and hold the reading measure on
   its children instead, so the prose stands on the page's left edge. */
.container.prose { max-width: var(--container); }
/* ch resolves against each element's own font-size, so a bare 74ch let the
   headings run to the full container while the paragraphs under them stopped
   at 784px. Headings get their own measure, the way .section-head already
   splits h2 from .lead. */
.container.prose > * { max-width: 74ch; }
.container.prose > h2 { max-width: 24ch; }
.container.prose > h3 { max-width: 32ch; }
.prose h2 { font-size: var(--h3); margin-top: 58px; margin-bottom: 18px; }
.prose h3 { font-size: var(--h4); margin-top: 38px; margin-bottom: 12px; }
.prose > *:first-child { margin-top: 0; }
.prose p { color: var(--muted); margin-bottom: 18px; }
.prose strong { color: var(--text); font-weight: 600; }
/* padding-block on an INLINE element grows the hit area without pushing the
   lines apart, so this buys the 24px target size for free */
.prose a {
  color: var(--white);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--line-2);
  padding-block: 5px;
}
.prose a:hover { text-decoration-color: var(--white); }

.prose ul { margin: 0 0 22px; display: grid; gap: 10px; }
.prose ul li { position: relative; padding-left: 26px; color: var(--muted); }
/* One marker, and it is a rule rather than a glyph.
   Two things were wrong with "//". First it collided: the PRS scope list sets
   its own dash on the same ::before, and since .prose ul li::before is the
   more specific selector its content won while the dash's width and colour
   still applied -- so every item in that list carried a dash AND two slashes,
   which is not a bullet, it is a bug that looked like a decision. Second, //
   is the eyebrow's separator. Reusing it as a list marker spends a mark that
   means "section" on meaning "item", and it read as punctuation dropped in
   the margin. A short rule is what a specification list uses. */
.prose ul li::before {
  content: "";
  position: absolute;
  left: 0;
  /* Half the line box, less the marker's own height and the half pixel the
     line box sits above the content box. Measured against the first line's
     own client rect, not eyeballed. */
  top: calc(0.825em - 1px);
  top: calc(0.5lh - 1px);
  width: 10px;
  height: 1px;
  background: var(--grey-500);
}

/* Equipment / manufacturer lists — these carry over from the old site
   verbatim, so they get a denser treatment than prose bullets.

   Scoped `.prose .tags`, not bare `.tags`: every one of these lives inside
   .prose, and `.prose ul` (class + element) out-specifies a lone class, so
   the bare version loses `display:flex` to `display:grid` and each chip
   stretches to a full-width row wearing a `//` bullet. */
.prose .tags,
.tags { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 24px; }
.prose .tags li,
.tags li {
  padding: 8px 14px;
  border: 1px solid var(--line);
  background: var(--panel);
  font-size: var(--text-s);
  letter-spacing: 0.04em;
  color: var(--muted);
  transition: border-color 0.25s var(--ease), color 0.25s var(--ease);
}
.prose .tags li::before { content: none; }
.prose .tags li:hover,
.tags li:hover { border-color: var(--line-2); color: var(--white); }

/* ============================================================
   CTA BAND
   ============================================================ */
/* The brand block used to start and stop at full strength: a step of 20 going
   in and 25 coming out, both of them a razor line across the page. It fades up
   out of whatever ground it happens to be sitting on instead -- alpha, not a
   fade to a guessed background colour, so it blends against the real ambient
   field rather than an approximation of it. 100px, which is inside the block's
   own top padding, so no text ever sits on the ramp. */
.cta {
  background: linear-gradient(to bottom,
    transparent 0,
    var(--brand) 100px,
    var(--brand) calc(100% - 100px),
    transparent 100%);
  padding-block: clamp(58px, 8vw, 118px);
  position: relative;
  overflow: hidden;
}
/* The mark itself, oversized and barely there, bleeding off the right edge —
   the kind of thing you only notice on the second look.

   On ::after, NOT ::before: .cta carries .seam-top, which owns ::before. Put
   this on ::before and it silently replaces the rivet seam, same specificity,
   later in the file. .cta has no .seam-bottom, so ::after is free. */
.cta::after {
  content: "";
  position: absolute;
  right: -6%;
  top: 50%;
  width: min(46vw, 620px);
  aspect-ratio: 272 / 200;
  transform: translateY(-50%);
  background: var(--mark-img) right center / contain no-repeat;
  /* 0.17 was chosen while the declaration was invalid and the mark was not
     painting at all -- once it rendered it read as a watermark rather than as
     the second-look detail it is meant to be. */
  opacity: 0.1;
  pointer-events: none;
}
.cta > * { position: relative; z-index: 1; }
@media (max-width: 780px) { .cta::after { display: none; } }
.cta h2 { margin-bottom: 18px; }
.cta p { color: rgba(255, 255, 255, 0.8); font-size: var(--text-xl); max-width: 54ch; }
.cta .btn-solid { background: var(--white); color: var(--navy-900); }
.cta .btn-outline { border-color: rgba(255, 255, 255, 0.45); }

/* ============================================================
   CONTACT
   ============================================================ */
.contact-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: clamp(30px, 4vw, 64px);
}
.contact-grid > * { min-width: 0; }
/* Targeted by class, not by tag: the heading LEVEL is chosen per page to keep
   the document outline continuous, so the styling must not depend on it. */
.contact-block .block-title {
  font-size: var(--label);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted-2);
  font-weight: 500;
  margin-bottom: 12px;
}
.contact-block p, .contact-block a { font-size: var(--text-l); }
.contact-block a {
  display: inline-block;
  padding-block: 4px;   /* WCAG 2.2 target size — 19px of text is not tappable */
}
.contact-block a:hover { text-decoration: underline; text-underline-offset: 3px; }
.contact-block + .contact-block { margin-top: 32px; }

.form { display: grid; gap: 18px; }
.field { display: grid; gap: 8px; min-width: 0; }
.field label { font-size: var(--text-s); letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted-2); }
.field .opt { text-transform: none; letter-spacing: 0; }
.field input, .field textarea {
  width: 100%;
  padding: 14px 16px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 2px;
  color: var(--text);
  transition: border-color 0.25s var(--ease), background 0.25s var(--ease);
}
.field input:focus, .field textarea:focus {
  outline: none;
  border-color: var(--line-2);
  background: var(--panel-2);
}
.field textarea { resize: vertical; min-height: 130px; }

.field-check { grid-template-columns: auto 1fr; align-items: start; gap: 12px; }
.field-check input { width: 24px; height: 24px; flex: none; margin-top: 2px; accent-color: var(--navy-500); }
.field-check label { text-transform: none; letter-spacing: 0; font-size: var(--text-m); color: var(--muted); }
/* the consent label is outside .prose, so it needs its own target-size rule */
.field-check label a { color: var(--white); text-decoration: underline; text-underline-offset: 3px; padding-block: 5px; }

.form-note { font-size: var(--text-m); color: var(--muted-2); min-height: 22px; }
.form-note.is-error { color: #ff9a9a; }
.form-note.is-ok { color: var(--white); }

/* ============================================================
   FOOTER
   ============================================================ */
.site-footer {
  /* same idea, one direction: the footer rises out of the page instead of
     being butted against it. The hairline stays -- it is the sheet edge, and
     a rule over a soft ground reads as drawn rather than as a seam. */
  background: linear-gradient(to bottom, transparent 0, var(--black) 130px, var(--black));
  padding-block: clamp(52px, 6vw, 92px) 34px;
  border-top: 1px solid var(--line);
}
.footer-top {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 40px;
  padding-bottom: 46px;
  border-bottom: 1px solid var(--line);
}
.footer-top > * { min-width: 0; }
.footer-col .col-title {
  font-size: var(--label);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted-2);
  font-weight: 500;
  margin-bottom: 16px;
}
.footer-col li { margin-bottom: 5px; }
.footer-col a, .footer-col p { font-size: var(--text-m); color: var(--muted); }
.footer-col a {
  display: inline-block;
  padding-block: 4px;   /* 18px of text is under the 24px minimum on its own */
}
.footer-col a:hover { color: var(--white); }

.footer-brand p { font-size: var(--text-m); color: var(--muted-2); max-width: 30ch; }

/* ---------- the two hero controls ----------
   The work lamp and FROST, icon only, top right of the hero and directly over
   the drawing they act on. Both were labelled chips at the end of the trust
   rule, which turned a line of proof -- BITZER, DANFOSS, RINA, PRS -- into a
   toolbar.

   Below the header rather than under it, and on the hero's own padding edge so
   they line up with what the drawing already respects. Each carries the state
   it put the page into: dim at rest, brighter under the cursor, lit while its
   effect is running, so neither is a button pressed into the dark. */
.hero-tools {
  position: absolute;
  top: calc(var(--nav-h) + clamp(14px, 2.4vh, 30px));
  right: var(--pad);
  z-index: 3;
  display: flex;
  gap: 8px;
}
.hero-tool {
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  padding: 0;
  background: none;
  border: 1px solid var(--line);
  border-radius: 2px;
  color: var(--muted-2);
  cursor: inherit;
  transition: color 0.25s var(--ease), border-color 0.25s var(--ease),
              background 0.25s var(--ease);
}
.hero-tool svg { width: 20px; height: 20px; display: block; }
@media (hover: hover) and (pointer: fine) {
  .hero-tool:hover { color: var(--white); border-color: var(--line-2); }
}
.hero-tool:focus-visible { color: var(--white); border-color: var(--line-2); }
/* lit: the control shows the state it put the page into */
.hero.is-lamp #lampToggle,
html.is-frost #frostToggle {
  color: var(--white);
  border-color: var(--line-2);
  background: rgba(255, 255, 255, 0.06);
}


.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 26px;
  padding-top: 26px;
  font-size: var(--text-s);
  color: var(--muted-2);
}
.footer-bottom a { padding-block: 6px; }
.footer-bottom a:hover { color: var(--white); }
/* Two links live here now, so the span needs its own gap -- the row's gap is
   between the spans, not inside one. */
.footer-bottom .spacer {
  margin-left: auto;
  display: flex;
  flex-wrap: wrap;
  gap: 10px 26px;
}

/* the approved-scope list: a specification, so it is set like one */
.scope-list {
  list-style: none;
  padding: 0;
  margin: 18px 0 26px;
  border-top: 1px solid var(--line);
}
.scope-list li {
  /* The row padding is a variable because the marker has to know it. Written
     as a literal in both places they drift, and the drift is invisible in the
     source and obvious on the page. */
  --row-pad: 11px;
  padding: var(--row-pad) 0 var(--row-pad) 26px;
  border-bottom: 1px solid var(--line);
  position: relative;
}
/* Same rule, drawn from the same place, so a scope list inside .prose cannot
   end up with one marker from each.

   The vertical position is DERIVED, not chosen. It was 1.05em, which ignored
   the row's own 11px of padding and put the dash 6.2px above the middle of
   the line it marks -- measured, because at this size it reads as "slightly
   wrong" long before anyone can say by how much. The marker belongs half a
   line box below the top of the text, and the top of the text is the padding.

   Two declarations on purpose: the em fallback is half of the 1.65 line
   height written out, and the lh version overrides it wherever the unit is
   supported and then follows the line height if it ever changes. */
.prose .scope-list li::before,
.scope-list li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: calc(var(--row-pad) + 0.825em - 1px);
  top: calc(var(--row-pad) + 0.5lh - 1px);
  width: 10px;
  height: 1px;
  background: var(--grey-500);
}

/* ============================================================
   WORKSHOP SHOTS
   The opposite of .page-media. That one holds a photograph back
   to 72% and desaturates it so a headline can sit on top; these
   are the evidence a ship repair company is actually asked for,
   so they are shown at full strength and given room.
   ============================================================ */
.shots {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(14px, 1.6vw, 22px);
}
.shot {
  margin: 0;
  min-width: 0;
  border: 1px solid var(--line);
  background: var(--navy-800);
}
/* kept for a panorama: a photograph wide enough that halving it would throw
   away what makes it worth showing takes the whole row */
.shot--wide { grid-column: 1 / -1; }
.shot img {
  display: block;
  width: 100%;
  height: auto;
  /* Every other photograph on the site is desaturated to 0.6. Left at full
     strength these were the most colourful thing on a near-monochrome site --
     a teal casing stand and a red tool cabinet pulling the eye off the
     machine. The colour here is workshop clutter, not information. */
  filter: saturate(0.6);
}
/* The two that share a row are 1.60 and 1.36, so one image finished 69px
   above the other and their captions started at different heights -- the grid
   stretched the boxes to match and left a gap under the shorter picture. A
   common 3:2 crop costs each of them very little and lines the row up. */
.shots > .shot:not(.shot--wide) img {
  aspect-ratio: 3 / 2;
  object-fit: cover;
}
/* No margin-top:auto here. With the images the same height the captions
   already start on the same line; pushing them to the bottom of the box only
   dropped the shorter one 21px below its neighbour. */
.shot figcaption {
  display: flex;
  gap: 12px;
  padding: 16px clamp(14px, 1.6vw, 20px) 18px;
  border-top: 1px solid var(--line);
  font-size: var(--text-s);
  line-height: 1.6;
  color: var(--muted);
}
.shot-n {
  flex: none;
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.16em;
  color: var(--grey-400);
  padding-top: 2px;
}
@media (max-width: 760px) {
  .shots { grid-template-columns: 1fr; }
  .shot--wide { grid-column: auto; }
}

/* ============================================================
   REVEAL
   Scoped to .js — set by an inline script in <head>. Without that guard,
   opacity:0 is the DEFAULT state, so if the script fails, is blocked, or
   simply has not run yet, every section below the hero renders blank.
   Hiding content by default is not something a stylesheet should do.
   ============================================================ */
/* Matched to alprojects, which is the one that reads right: 700ms, 18px, and
   the site's own --ease. I had this at 420/14 and then at 900/8 on an
   exponential curve with the head's three parts staggered against each other,
   and both were further from it than the 750/24 it started at.

   will-change is set while the element is waiting and released the moment it
   lands -- held permanently it costs a compositor layer per revealed block for
   the rest of the session. */
.js .reveal {
  opacity: 0;
  transform: translateY(14px);
  /* 0.7s on the shared --ease landed the whole section at once and read as a
     switch being thrown. Longer, and on a curve that leaves quickly and
     arrives slowly, so the last third of the travel is almost still -- that
     tail is what makes one section hand over to the next instead of snapping
     into place. The stagger between siblings is set from the observer. */
  transition: opacity 0.9s cubic-bezier(0.16, 0.84, 0.3, 1),
              transform 0.9s cubic-bezier(0.16, 0.84, 0.3, 1);
  will-change: opacity, transform;
}
.js .reveal.is-in { opacity: 1; transform: none; will-change: auto; }

@media (prefers-reduced-motion: reduce) {
  .js .reveal { opacity: 1; transform: none; transition: none; }
}

/* Scroll-linked, where the engine has it. A transition fired from an observer
   is smooth in itself but it is not smooth WITH the scroll: it starts when a
   threshold is crossed and then runs on its own clock, so a block lands while
   the reader is still moving, or finishes long after they have stopped. Tied
   to the view timeline the entrance is a function of where the block is on
   the screen -- scroll half as far and it is half in, stop and it stops.
   Measured first: frame times during a full-page scroll were 8.3ms median
   with nothing over 32ms even at 4x CPU throttling, so this was never
   dropped frames, it was the entrances being on the wrong clock.

   The observer still runs and still adds .is-in: the hull drawing and the
   compressor read that class, and this must not take it away from them. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .js .reveal {
      opacity: 1;
      transform: none;
      transition: none;
      animation: reveal-in linear both;
      animation-timeline: view();
      /* A longer range and a shorter move. Over 18% of cover the block was
         still arriving faster than the page scrolls past it, which reads as a
         snap even when it is tied to the scroll. Spread over 38% the travel
         per pixel of scroll is a third of what it was. */
      animation-range: entry 0% cover 38%;
    }
    @keyframes reveal-in {
      from { opacity: 0; transform: translateY(11px); }
      to   { opacity: 1; transform: none; }
    }
    /* Not in the hero. A view timeline is a function of how far the block has
       travelled up the screen, and the hero's own content has not travelled at
       all when the page first paints -- the figures along its foot sat at 0.76
       opacity until the reader scrolled, which is a half-drawn first screen.
       Anything above the fold is simply there. */
    .js .hero .reveal { animation: none; opacity: 1; transform: none; }
  }
}

/* ============================================================
   404
   ============================================================ */
.notfound {
  min-height: 74vh;
  display: grid;
  place-items: center;
  text-align: center;
  padding-top: var(--nav-h);
}
.notfound .code {
  font-size: clamp(70px, 14vw, 190px);
  font-weight: 700;
  line-height: 1;
  color: var(--grey-600);
  letter-spacing: -0.05em;
}

/* ============================================================
   RESPONSIVE
   ============================================================ */
@media (max-width: 980px) {
  /* The panel below is a CHILD of .site-header, and .site-header carries a
     backdrop-filter, which makes it a backdrop root: a descendant's own
     backdrop-filter then samples the root's output instead of the page. The
     panel extends well below the header, where that output is empty, so the
     blur silently did nothing and the hero headline came through the menu at
     full contrast, straight across the labels. Nothing errors -- it just
     looks broken.

     So the header stops being a backdrop root, but only while the menu is
     actually open: :has() reaches the parent from the state class on the
     child. Dropping the filter site-wide left the header translucent WITHOUT
     blur, which is worse than either -- page text scrolled past the logo
     perfectly sharp. With the filter gone it has to be opaque, which is also
     the honest arrangement: a solid bar with a frosted sheet under it. */
  .site-header:has(.nav-links.is-open) {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    background: var(--navy-900);
  }

  .nav-links {
    position: fixed;
    inset: var(--nav-h) 0 auto 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    margin: 0;
    padding: 12px var(--pad) 28px;
    /* Menu tier: this panel floats over whatever the reader was looking at --
       usually the hero and its indigo glow, or a workshop photograph. Opaque
       navy threw all of that away. 55% surface behind blur(22px) is the
       recipe's answer to "a dropdown covers arbitrary content and the text on
       it still has to be readable" -- do not thin it back toward clear. */
    background: color-mix(in srgb, var(--navy-900) 72%, transparent);
    -webkit-backdrop-filter: blur(22px) saturate(140%);
    backdrop-filter: blur(22px) saturate(140%);
    border-bottom: 1px solid var(--glass-border);
    box-shadow: 0 16px 40px rgba(0, 0, 0, 0.55), inset 0 1px 0 var(--glass-rim-top);
    transform: translateY(-12px);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
    max-height: calc(100dvh - var(--nav-h));
    overflow-y: auto;
  }
  .nav-links.is-open { opacity: 1; transform: none; pointer-events: auto; }
  .nav-links a {
    padding: 15px 0;
    border-bottom: 1px solid var(--line);
    font-size: var(--text-l);
  }
  .burger { display: block; }
  .nav-actions { margin-left: auto; }
}

@media (max-width: 620px) {
  /* the booking button crowds the logo on a narrow screen — it moves into
     the menu, where it becomes a full-width action */
  .nav-actions .btn-book { display: none; }
  .nav-links .nav-cta-mobile {
    display: block;
    margin-top: 18px;
    padding: 15px 0;
    text-align: center;
    border: 1px solid var(--line-2);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    font-size: var(--text-m);
  }
  .btn { width: 100%; }
  .btn-row { gap: 10px; }
  .footer-bottom .spacer { margin-left: 0; flex-basis: 100%; }
}
@media (min-width: 621px) { .nav-links .nav-cta-mobile { display: none; } }

@media (max-width: 620px) {
  /* iOS Safari zooms the page when a focused field is under 16px.
     The desktop rules use `.field input`, so this has to match that
     specificity or it loses. */
  .field input, .field textarea { font-size: 16px; }
}

/* ============================================================
   PRINT
   ============================================================ */
@media print {
  .site-header, .burger, .cta, .form, .hero-media { display: none; }
  body { background: #fff; color: #000; }
  a { text-decoration: underline; }
}

/* ============================================================
   BLEED
   Every section on the home page sat in the same 1440px container -- nine
   blocks, nine identical widths, which is what made the page feel like one
   shape repeated rather than a document with a rhythm.

   The rule is not "vary it for variety": text keeps its measure, because a
   reading column has a right width and 1440px of prose is not it. Drawings
   do not. A general arrangement runs to the edge of the sheet, and the two
   here were being cropped to a text measure they have no reason to obey.

   width:100vw with a negative margin rather than a wider container, so the
   section head above it stays exactly where every other head on the page is
   and only the drawing escapes. calc against 100% covers the scrollbar, which
   100vw includes and the element does not get.
   ============================================================ */
.bleed {
  /* Capped, not edge-to-edge. On a 3440px display true 100vw stretched a
     vessel profile to three and a half thousand pixels of thin line, which is
     not width, it is distortion. 2160 is wide enough to read as a sheet and
     narrow enough that the drawing keeps its proportions. Below that it is
     simply the viewport. */
  /* --pad is the TEXT gutter, and using it here made the drawing 1325px
     inside a 1440px viewport -- exactly the width of the text column it
     was supposed to escape, so nothing changed at all. A drawing wants a
     narrower margin than a paragraph; 24px is the sheet edge. */
  width: min(100vw - 48px, 2160px);
  max-width: none;
  position: relative;
  left: 50%;
  translate: -50%;
}

/* ---------- where on the vessel ----------
   Draw-on rather than fade-in: a print appears line by line under a pen, and
   the site already spends its motion budget on that idea elsewhere. Every
   stroke carries pathLength="1" from the generator, so one dasharray value
   covers a hull, a waterline and a 12-unit propeller blade alike. */
.jp { margin: 34px 0 0; }
.jp svg { width: 100%; height: auto; display: block; overflow: visible; }
.jp figcaption {
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted-2);
  margin-top: 10px;
}
.jp-hull, .jp-deck, .jp-body { fill: none; stroke: var(--grey-600); stroke-width: 1.6; }
.jp-hull { stroke: var(--muted-2); }
.jp-wl { stroke: var(--grey-600); stroke-width: 1; stroke-dasharray: 14 5 2 5; }
.jp-datum {
  font-family: var(--mono); font-size: 13px; letter-spacing: 0.12em;
  fill: var(--muted-2); text-anchor: end;
}
.jp-space rect { fill: none; stroke: var(--line-2, var(--line)); stroke-width: 1; stroke-dasharray: 6 5; }
.jp-space text { font-family: var(--mono); font-size: 13px; letter-spacing: 0.1em; fill: var(--grey-600); }
/* the worked compartment: filled, solid-edged and in the accent, so it is the
   one thing on the drawing that is not a thin grey line */
.jp-space.is-work rect {
  fill: color-mix(in srgb, var(--accent) 22%, transparent);
  stroke: var(--accent); stroke-dasharray: none; stroke-width: 1.4;
}
.jp-space.is-work text { fill: var(--white); }
.jp-dim line { stroke: var(--accent); stroke-width: 1.2; }

/* The drawing carries its own labels wherever they are legible; under 700px
   they are about four pixels a character, so they go and the figcaption says
   the same thing in running type instead. Never both. */
.jp-legend { display: none; }
@media (max-width: 700px) {
  .jp-space text, .jp-datum { display: none; }
  .jp-legend {
    display: block;
    margin-top: 4px;
    letter-spacing: 0.08em;
    color: var(--muted);
    text-transform: none;
  }
}

/* The pen.
   Two things here were bugs first. (1) A keyframe, not a transition: the
   class arrives from script and a transition needs a resolved before-state to
   interpolate from, so the strokes sat at offset 1 and the hull never
   appeared -- only the compartments drew, and the drawing read as four boxes
   floating in the dark. (2) The strokes are selected by .jp-line, not by
   [pathLength]: Chrome lowercases the attribute name when it parses the
   selector, and attribute matching on SVG elements is case-sensitive, so the
   selector is not reliably the same thing you wrote. The screenshot caught
   both; the diff looked correct throughout. */
.js .jp .jp-line { stroke-dasharray: 1; stroke-dashoffset: 1; }
.js .jp.is-in .jp-line { animation: jp-draw 1.1s var(--ease) forwards; }
.js .jp.is-in .jp-deck { animation-delay: 0.15s; }
.js .jp.is-in .jp-body { animation-delay: 0.3s; }
/* transform-box: fill-box so the origin is the line's own left end and not
   the SVG's user-space origin, which is off the left of the drawing */
.js .jp .jp-wl { transform: scaleX(0); transform-box: fill-box; transform-origin: left; }
.js .jp.is-in .jp-wl { animation: jp-wipe 0.9s var(--ease) 0.45s forwards; }
@keyframes jp-wipe { to { transform: scaleX(1); } }
@keyframes jp-draw { to { stroke-dashoffset: 0; } }

/* the boxes and their rules arrive after the hull has finished drawing */
.js .jp .jp-space, .js .jp .jp-dim, .js .jp .jp-datum { opacity: 0; }
.js .jp.is-in .jp-space,
.js .jp.is-in .jp-dim,
.js .jp.is-in .jp-datum { animation: jp-fade 0.5s var(--ease) 0.9s forwards; }
@keyframes jp-fade { to { opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
  .js .jp .jp-line { stroke-dashoffset: 0; }
  .js .jp .jp-wl { transform: none; }
  .js .jp .jp-space, .js .jp .jp-dim, .js .jp .jp-datum { opacity: 1; }
  .js .jp.is-in .jp-line,
  .js .jp.is-in .jp-wl,
  .js .jp.is-in .jp-space,
  .js .jp.is-in .jp-dim,
  .js .jp.is-in .jp-datum { animation: none; }
}

/* ============================================================
   THE COMPRESSOR, LIVE
   The canvas is the only pixel-shaded thing on this site. Everything around
   it stays line work -- corner ticks, a rule, mono callouts -- so the object
   reads as a part sitting on a sheet rather than a render dropped on a page.
   ============================================================ */
.cmp { margin: 0; }
/* The section is meant to sit in one screenful, so the stage is sized off the
   viewport and not off an aspect ratio: at 16/9 it was 783px inside a 900px
   screen and the section came to 147% of it. vh alone would be wrong the other
   way -- on a short wide laptop the stage would go letterbox-thin -- so the
   width term caps it. */
.cmp-section { padding-top: clamp(34px, 4vw, 56px); padding-bottom: clamp(28px, 3vw, 44px); }
.cmp-stage {
  position: relative;
  height: min(53vh, 38vw);
  min-height: 260px;
  /* A pale pool under the machine, so it is standing on something. Radial and
     off-centre, matched to where the key light comes from in the renderer --
     a centred glow behind an off-centre light reads as two light sources. */
  background:
    radial-gradient(58% 62% at 38% 78%, rgba(45, 53, 190, 0.22), transparent 70%),
    radial-gradient(80% 90% at 50% 40%, rgba(255, 255, 255, 0.035), transparent 72%);
}
@media (max-width: 700px) { .cmp-stage { height: min(46vh, 84vw); } }

/* ---------- the machine beside its own words ----------
   Copied in intent from the About hero, and for the reason that hero exists:
   at roughly 740px of stage the renderer's own arithmetic flips the key --
   isKeyed() decides two columns of names no longer fit beside the drawing,
   so the numbers move onto the machine and the names line up underneath it.
   Seven leader lines stop crossing an empty half-page to reach the sheet
   edge, and the machine gets that half-page instead.

   Which is the counter-intuitive part worth writing down: the machine comes
   out LARGER in half a screen than it was across a whole one, because a full
   width stage is bound by its height and spends the rest on white space and
   leaders.

   Only the figure that asks for it. The packaged unit on the refrigeration
   page is a skid four times longer than it is tall -- half the width there
   would shrink it, not grow it. */
@media (min-width: 980px) {
  .cmp-section--split > .container {
    display: grid;
    grid-template-columns: minmax(300px, 0.85fr) minmax(0, 1.3fr);
    column-gap: clamp(36px, 4.5vw, 84px);
    align-items: center;
  }
  /* One column again inside the left half. The site-wide rule sets every
     section head as heading-beside-lead; in a half-width column that leaves
     two 20ch measures side by side, which is a paragraph shape nobody reads.
     Specificity, not source order, does the overriding here -- .cmp-section--
     split .section-head:has(.lead) is 0,3,0 against the 0,2,0 it replaces. */
  .cmp-section--split .section-head,
  .cmp-section--split .section-head:has(.lead) {
    display: block;
    max-width: none;
    margin-bottom: 0;
  }
  .cmp-section--split .section-head h2 { max-width: 15ch; margin-bottom: 20px; }
  .cmp-section--split .section-head .lead { max-width: 42ch; margin-bottom: 0; }

  /* Undo .bleed. Inside a grid cell, escaping to the viewport width is
     exactly what must not happen -- it would put the figure back across the
     whole page and lay it over the column beside it. */
  .cmp-section--split .cmp {
    width: auto;
    max-width: none;
    left: auto;
    translate: none;
    margin-bottom: 0;
  }
  .cmp-section--split .cmp-stage { height: min(74vh, 42vw); min-height: 440px; }
}

/* ============================================================
   ABOUT: the machine IS the first screen
   ============================================================
   The page head measured 610px and the stage began at 945, so on desktop,
   laptop and phone alike not one pixel of the compressor was above the fold:
   the page opened on a paragraph about a machine, and you had to take its
   word for it.

   Stacking them and trimming paddings does not work, and the numbers say why
   rather than the eye: an 81px h1 and a 58px h2 are 388px of type before
   anything is drawn, and putting the head in two columns made it WORSE -- the
   h1 went from two lines to four in the narrower column and the head grew
   from 485px to 538px. Height is the axis with nothing spare on it.

   Width is the axis with plenty. Side by side, the head takes a column it can
   wrap comfortably in and the machine takes the full height of the screen,
   which also suits it: a stage around 740 by 660 is a better frame for a
   compressor than the 1392 by 340 letterbox stacking would have left. */
.about-hero .page-head { padding-top: calc(var(--nav-h) + clamp(22px, 4vw, 56px)); }
.lead--sub { color: var(--muted-2); }

/* The machine's lead is printed twice and shown once. Above the breakpoint it
   belongs in the head's second column, beside the title; below it, where there
   is no second column, it would read as a second page lead about nothing in
   particular, so the copy in the section takes over. Hiding it in the head and
   NOT printing it in the section left it nowhere at all on a phone. */
.cmp-lead-alt { display: block; }
@media (max-width: 1079px), (max-height: 619px) {
  .about-hero .page-head-aside .lead--sub { display: none; }
}
@media (min-width: 1080px) and (min-height: 620px) {
  .about-hero .cmp-lead-alt { display: none; }
}

@media (min-width: 1080px) and (min-height: 620px) {
  .about-hero {
    display: grid;
    /* The machine gets the larger share: below 620px of stage the callout
       leaders collapse to a list under the drawing, and the leaders are the
       whole point of this view. */
    grid-template-columns: minmax(0, 0.66fr) minmax(0, 1.34fr);
    column-gap: clamp(24px, 3vw, 56px);
    align-items: center;
    /* svh, not vh: on mobile Safari vh is the tallest the viewport ever gets,
       so a 100vh hero stands taller than the screen while the toolbar is up. */
    min-height: 100svh;
    max-width: var(--container);
    margin-inline: auto;
    padding-inline: var(--pad);
  }
  /* The gutter belongs to the wrapper now, so the two columns line up with
     the prose further down the page instead of each keeping its own. */
  .about-hero > .container { max-width: none; padding-inline: 0; margin-inline: 0; }

  .about-hero .page-head {
    padding-block: calc(var(--nav-h) + clamp(16px, 3vh, 40px)) clamp(16px, 3vh, 40px);
  }
  .about-hero .page-head--split { display: block; }   /* one column again */
  .about-hero .page-head h1 {
    font-size: clamp(38px, 3.4vw, 54px);
    margin-bottom: clamp(14px, 2vh, 24px);
  }
  .about-hero .page-head .lead { font-size: clamp(16px, 1.15vw, 18px); }
  .about-hero .page-head-aside .lead { margin-bottom: clamp(10px, 1.4vh, 18px); }
  .about-hero .page-head-aside .lead:last-child { margin-bottom: 0; }

  /* Every box between the wrapper and the canvas has to pass the free height
     down, and each one needs min-height:0 or it refuses to shrink below its
     content -- that min-content floor is why a nested flex column silently
     overflows instead of fitting. */
  .about-hero .cmp-section {
    align-self: stretch;
    display: flex;
    min-height: 0;
    padding-block: calc(var(--nav-h) + clamp(16px, 3vh, 40px)) clamp(16px, 3vh, 40px);
  }
  .about-hero .cmp-section > .container {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    min-height: 0;
  }
  .about-hero .cmp {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    min-height: 0;
    margin-bottom: 0;
    /* undo .bleed: in a column, escaping to the viewport width is exactly
       what must not happen */
    width: auto;
    left: auto;
    translate: none;
  }
  .about-hero .cmp-stage {
    flex: 1 1 auto;
    height: auto;      /* beats the min(53vh, 38vw) above, which is the job of
                          the flex row here rather than of a guess */
    min-height: 260px;
  }
}

.cmp-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  /* the drag has to win over the page scroll, or a turn of the machine
     scrolls the section away underneath it */
  touch-action: none;
  cursor: grab;
}
.cmp.is-held .cmp-canvas { cursor: grabbing; }

/* ---------- callouts ----------
   Positioned by the renderer with transform only, so the browser never
   reflows: left/top stay at 0 and every frame writes a translate. */
/* The visible callouts are painted on the canvas, where they can be led out
   to the margin instead of sitting on the casting. These stay in the markup so
   the parts are still named for a screen reader, and so the renderer can read
   its labels out of them rather than carrying a copy of both languages. */
.cmp-legend { margin: 0; }
.cmp-tag {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
/* Wide enough for the leaders, and the drawing names its own parts. Narrow,
   and two columns of labels either side of the machine land on top of it, so
   the canvas keeps only the numbered balloons and the names come down here.

   The renderer decides which it is -- it is the only party that knows how
   wide the words come out in the font it is about to draw them in -- and
   stamps .is-keyed on the figure, so the canvas and the stylesheet cannot
   disagree. The media query stays as the no-script floor, where nothing is
   drawn at all and the names are the whole of it. */
.cmp.is-keyed .cmp-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 20px;
  margin-top: 14px;
}
.cmp.is-keyed .cmp-tag {
  position: static;
  width: auto; height: auto;
  margin: 0;
  clip-path: none;
  overflow: visible;
  display: flex;
  gap: 8px;
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
.cmp.is-keyed .cmp-tag b { color: var(--accent); font-weight: 600; }
@media (max-width: 620px) {
  .cmp-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 20px;
    margin-top: 14px;
  }
  .cmp-tag {
    position: static;
    width: auto; height: auto;
    margin: 0;
    clip-path: none;
    overflow: visible;
    display: flex;
    gap: 8px;
    font-family: var(--mono);
    font-size: var(--label);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--muted);
  }
  .cmp-tag b { color: var(--accent); font-weight: 600; }
}


/* ---------- the sheet it sits on ---------- */
.cmp-corner {
  position: absolute;
  width: 14px; height: 14px;
  border: 0 solid var(--line-2);
  pointer-events: none;
}
.cmp-corner--tl { top: 10px; left: 10px; border-top-width: 1px; border-left-width: 1px; }
.cmp-corner--tr { top: 10px; right: 10px; border-top-width: 1px; border-right-width: 1px; }
.cmp-corner--bl { bottom: 10px; left: 10px; border-bottom-width: 1px; border-left-width: 1px; }
.cmp-corner--br { bottom: 10px; right: 10px; border-bottom-width: 1px; border-right-width: 1px; }

.cmp-hint {
  position: absolute;
  left: 32px; bottom: 22px;
  margin: 0;
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted-2);
  opacity: 0;
  transition: opacity 0.6s var(--ease);
  pointer-events: none;
}
.cmp.is-live .cmp-hint { opacity: 1; }
.cmp.is-held .cmp-hint { opacity: 0; }
/* Nothing to drag with a finger-free pointer, and nothing to say about it. */
@media (hover: none) { .cmp-hint { letter-spacing: 0.14em; } }

.cmp-tb {
  display: flex;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  border-top: 1px solid var(--line);
  margin-top: 14px;
  padding-top: 12px;
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted-2);
}
.cmp-tb b { color: var(--muted); font-weight: 600; }

@media (prefers-reduced-motion: reduce) {
  .cmp-canvas { cursor: default; }
}

/* ---------- a document to take away ----------
   One ruled row, not a card: it is a file, and a file on this site should look
   like a line on a schedule. Renders only when the file is actually in
   assets/docs/ -- see PRESENTATION in build.py. */
.doc-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: clamp(16px, 2.4vw, 34px);
  padding: clamp(20px, 2.4vw, 30px) clamp(18px, 2.2vw, 28px);
  border: 1px solid var(--glass-border);
  background: linear-gradient(180deg, var(--glass-tint-top), var(--glass-tint-bottom));
  transition: background 0.3s var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .doc-row:hover { background: rgba(255, 255, 255, 0.055); }
}
.doc-row-num, .doc-row-size {
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.16em;
  color: var(--muted-2);
  white-space: nowrap;
}
.doc-row-title { display: block; font-size: var(--h4); font-weight: 600; color: var(--white); }
.doc-row-note { display: block; color: var(--muted); font-size: var(--text-m); margin-top: 6px; }
@media (max-width: 620px) {
  .doc-row { grid-template-columns: auto 1fr; }
  .doc-row-size { grid-column: 2; justify-self: start; }
}

/* ============================================================
   COMPLETED PROJECTS
   A job is a record, so it is set as one: the facts in a ruled block, the
   scope as a list, the photographs underneath. Same glass and the same rules
   as the certificate list, which is the other place on this site where a
   document is being shown rather than described.
   ============================================================ */
.prj-grid { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--glass-border); }
.prj-card {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: start;
  gap: clamp(16px, 2.2vw, 30px);
  padding: clamp(20px, 2.4vw, 30px);
  background: linear-gradient(180deg, var(--glass-tint-top), var(--glass-tint-bottom));
  transition: background 0.3s var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .prj-card:hover { background: rgba(255, 255, 255, 0.05); }
}
.prj-num {
  font-family: var(--mono);
  font-size: var(--text-s);
  letter-spacing: 0.16em;
  color: var(--muted-2);
  padding-top: 4px;
}
.prj-title { display: block; font-size: var(--h4); font-weight: 600; margin-bottom: 8px; color: var(--white); }
.prj-lead { display: block; color: var(--muted); font-size: var(--text-m); margin-bottom: 14px; }
.prj-facts { display: flex; flex-wrap: wrap; gap: 6px 26px; }
.prj-facts span { display: flex; gap: 8px; font-size: var(--text-s); color: var(--muted); }
.prj-scope {
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted-2);
  text-align: right;
  max-width: 20ch;
  padding-top: 6px;
}
.prj-facts i {
  font-style: normal;
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted-2);
}

/* the job page itself */
.prj-page { max-width: var(--container); padding-bottom: clamp(50px, 7vw, 100px); }
/* The measure goes on the elements that want one, not on every child. As
   `.prj-page > *` it also capped the record, the drawing and the photographs
   at 74ch -- and because it is declared below them in this file it beat their
   own max-width: none, so the bled drawing came out half the width of the
   block above it and looked deliberate. */
.prj-page > p, .prj-page > ul { max-width: 74ch; }
.prj-page h2 { font-size: var(--h3); margin: 46px 0 16px; max-width: 24ch; }
.prj-record {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--glass-border);
}
.prj-record > div {
  padding: 20px 22px;
  background: linear-gradient(180deg, var(--glass-tint-top), var(--glass-tint-bottom));
}
.prj-record span {
  display: block;
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted-2);
  margin-bottom: 8px;
}
.prj-record b { font-size: var(--text-l); font-weight: 600; color: var(--white); }
.prj-list { margin: 0 0 22px; display: grid; gap: 10px; }
.prj-list li { position: relative; padding-left: 26px; color: var(--muted); }
.prj-list li::before { content: "//"; position: absolute; left: 0; color: var(--grey-600); font-size: var(--text-s); }
.prj-shots { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 14px; margin-top: 34px; }
.prj-shot { margin: 0; border: 1px solid var(--line); overflow: hidden; }
.prj-shot img { width: 100%; height: auto; display: block; filter: saturate(0.6); }
.prj-back { margin-top: 40px; }
/* inline-flex, not block: the link sits inside a <p> and a block link would
   stretch the full measure, giving a 1325px-wide tap target that swallows the
   whitespace beside it. The audit caught this one at 19px tall. */
.prj-back a {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding: 0 14px 0 0;
}
.prj-back a::before { content: "//"; color: var(--grey-600); font-size: var(--text-s); }

@media (max-width: 900px) {
  /* the discipline is repeated on the job page's record block, so on a narrow
     card it goes rather than wrapping into a third stacked line */
  .prj-scope { display: none; }
}
@media (max-width: 620px) {
  .prj-card { grid-template-columns: 1fr; gap: 10px; }
  .prj-record { grid-template-columns: 1fr 1fr; }
}

/* ---------- capacity chart ----------
   Published figures, plotted in the site's own drawing hand. Three curves, one
   per condensing temperature. The 50 C curve genuinely stops short of -5: the
   machine is outside its envelope there, and the line ends where the data ends
   rather than being carried to the frame edge to look tidy. */
.capchart { margin-top: clamp(26px, 3.4vw, 52px); }
.cc { width: 100%; height: auto; overflow: visible; }
.cc-frame { fill: none; stroke: rgba(255,255,255,0.24); stroke-width: 1; }
.cc-grid { stroke: rgba(255,255,255,0.10); stroke-width: 1; }
.cc-ax { font-family: var(--mono); font-size: 12px; letter-spacing: 0.1em;
         fill: rgba(255,255,255,0.5); }
.cc-axis-x { letter-spacing: 0.14em; }
.cc-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.cc-dot { stroke: none; }
.cc-lbl { font-family: var(--mono); font-size: 12px; letter-spacing: 0.1em; }
/* Scoped to the element, not to the shared colour class. Written as
   .cc-tc30 { stroke: X; fill: X } the fill applied to the POLYLINE too and it
   came after .cc-line { fill: none }, so all three curves rendered as filled
   wedges rather than lines. */
.cc-line.cc-tc30 { stroke: rgba(186,219,255,0.95); }
.cc-line.cc-tc40 { stroke: rgba(158,201,255,0.72); }
.cc-line.cc-tc50 { stroke: rgba(140,166,214,0.60); }
.cc-dot.cc-tc30, .cc-lbl.cc-tc30 { fill: rgba(186,219,255,0.95); }
.cc-dot.cc-tc40, .cc-lbl.cc-tc40 { fill: rgba(158,201,255,0.72); }
.cc-dot.cc-tc50, .cc-lbl.cc-tc50 { fill: rgba(140,166,214,0.60); }
.cc-tb rect, .cc-tb line { fill: none; stroke: rgba(255,255,255,0.28); stroke-width: 1; }
.cc-tb text { font-family: var(--mono); font-size: 12px; letter-spacing: 0.12em;
              fill: rgba(255,255,255,0.5); }
.cc-tb-b { fill: rgba(255,255,255,0.85); font-weight: 700; }

/* at phone width the axis numbers collide; the title block goes with them */
@media (max-width: 700px) {
  .cc-tb { display: none; }
  .cc-ax { font-size: 15px; }
}

/* ============================================================
   RECIPROCATING COMPRESSOR PLATE
   Same conventions as the other two drawings: hairline geometry, a dash-dot
   axis, ticked dimensions, numbered balloons on leaders, a title block.
   ============================================================ */
/* The elevation is a wide drawing bled to 2160px, so on a 1440px screen it
   was 1392 wide and over 900 tall on its own -- the section came to 241% of a
   screenful. Capped by height and centred: the drawing keeps its proportions,
   it just stops being taller than the screen it is drawn on. */
.recip { margin-top: clamp(24px, 3vw, 44px); }
.recip svg { max-height: min(52vh, 460px); width: 100%; height: auto; display: block; margin-inline: auto; }
/* same for the capacity curve: 851px of chart is not a slide, it is a poster */
.capchart svg { max-height: min(54vh, 480px); width: 100%; height: auto; display: block; margin-inline: auto; }
.rc { width: 100%; height: auto; overflow: visible; }
.rc-body { fill: none; stroke: rgba(255,255,255,0.62); stroke-width: 1.6; stroke-linejoin: round; }
.rc-thin { fill: none; stroke: rgba(255,255,255,0.26); stroke-width: 1; }
.rc-cl { stroke: rgba(158,201,255,0.5); stroke-width: 1; stroke-dasharray: 18 4 3 4; }
.rc-tag, .rc-tb text { font-family: var(--mono); font-size: 12px; letter-spacing: 0.12em;
                       fill: rgba(255,255,255,0.55); }
.rc-dim line { stroke: rgba(158,201,255,0.55); stroke-width: 1; }
.rc-dim text { fill: #9ec9ff; font-family: var(--mono); font-size: 12px; letter-spacing: 0.1em; }
.rc-dim-bg { fill: var(--bg-2); }
.rc-tb rect, .rc-tb line { fill: none; stroke: rgba(255,255,255,0.28); stroke-width: 1; }
.rc-tb-b { fill: rgba(255,255,255,0.85); font-weight: 700; }
.rc-ball line { stroke: rgba(255,255,255,0.45); stroke-width: 1; }
.rc-ball circle { fill: var(--bg-2); stroke: rgba(255,255,255,0.6); stroke-width: 1.4; }
.rc-ball text { fill: var(--white); font-family: var(--mono); font-size: 13px;
                font-weight: 700; text-anchor: middle; }

.rc-legend {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 10px 26px;
  margin: 20px 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--text-s);
  color: var(--muted);
}
.rc-legend li { display: flex; gap: 9px; align-items: baseline; min-width: 0; }
.rc-legend span { font-family: var(--mono); font-size: var(--label); letter-spacing: 0.12em;
                  color: var(--muted-2); flex: none; }

/* the dimensions and the title block are unreadable at phone width */
@media (max-width: 700px) {
  .rc-dim, .rc-tb, .rc-tag { display: none; }
}

/* ============================================================
   VESSEL GENERAL ARRANGEMENT
   Same drawing-office conventions as the compressor package: hairline
   geometry, a dash-dot datum, dashed compartment boundaries, numbered
   balloons on leaders and a title block. The balloons are links -- each
   space leads to the discipline that works in it.
   ============================================================ */
.vessel { margin-top: clamp(26px, 3vw, 48px); }
.vs { width: 100%; height: auto; overflow: visible; }

.vs-hull, .vs-body {
  fill: none;
  stroke: rgba(255, 255, 255, 0.62);
  stroke-width: 1.6;
  stroke-linejoin: round;
}
/* fill:none is not optional on a <path>: SVG defaults to fill:black, so the
   sheer curve painted a solid wedge across the hull. */
.vs-deck { fill: none; stroke: rgba(255, 255, 255, 0.5); stroke-width: 1.2; }
.vs-thin { stroke: rgba(255, 255, 255, 0.3); stroke-width: 1; fill: none; }

/* the datum, dash-dot, as on a real print */
/* The waterline drifts, and nothing else does.
   A general arrangement is a drawing of a vessel at rest, so animating the
   ship would be animating the wrong thing -- and a hull that bobs is a toy.
   The datum is the one line on the sheet that represents something moving, so
   the dash-dot pattern runs slowly aft and the ship holds still. Same
   technique as the pipe flow on the hero, so the two drawings behave alike.

   35 units is the dash-dot pattern's own length (22+5+3+5), so it loops with
   no seam. Twelve seconds: this sits under a drawing somebody is reading
   labels off, and anything faster would pull the eye down to it. */
.vs-wl {
  stroke: rgba(158, 201, 255, 0.5);
  stroke-width: 1;
  stroke-dasharray: 22 5 3 5;
  animation: vs-drift 12s linear infinite;
}
@keyframes vs-drift { to { stroke-dashoffset: -35; } }
.vessel-section.is-idle .vs-wl { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) { .vs-wl { animation: none; } }
.vs-datum, .vs-tb text, .vs-space text {
  font-family: var(--mono);
  letter-spacing: 0.12em;
  fill: rgba(158, 201, 255, 0.8);
  font-size: 11px;
}
.vs-space rect {
  fill: none;
  stroke: rgba(255, 255, 255, 0.22);
  stroke-width: 1;
  stroke-dasharray: 6 5;
}
.vs-space text { fill: rgba(255, 255, 255, 0.6); }
.vs-datum--end { text-anchor: end; }

.vs-tb rect, .vs-tb line { fill: none; stroke: rgba(255, 255, 255, 0.28); stroke-width: 1; }
.vs-tb text { fill: rgba(255, 255, 255, 0.5); }
.vs-tb-b { fill: rgba(255, 255, 255, 0.85); font-weight: 700; }

/* .vs-ball circle is (0,1,1) and .vs-hit alone is (0,1,0), so the tap-target
   circle inherited the balloon's stroke and drew a 44-unit ring around every
   balloon. Invisible in the audit, which only measured whether the target was
   big enough. Needs the parent in the selector to win. */
.vs-ball .vs-hit { fill: transparent; stroke: none; }
.vs-ball line { stroke: rgba(255, 255, 255, 0.45); stroke-width: 1; }
.vs-ball circle {
  fill: var(--bg-2);
  stroke: rgba(255, 255, 255, 0.6);
  stroke-width: 1.4;
  transition: fill 0.25s var(--ease), stroke 0.25s var(--ease);
}
.vs-ball text {
  fill: var(--white);
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 700;
  text-anchor: middle;
}
@media (hover: hover) and (pointer: fine) {
  .vs-ball:hover circle { fill: var(--accent); stroke: var(--white); }
}
.vs-ball:focus-visible circle { fill: var(--accent); stroke: var(--white); }
.vs-ball:focus { outline: none; }

/* Below about 700px the compartment labels collide with their own boxes and
   the balloons crowd the mast. The drawing keeps every line -- it is the
   labels that go, and the four services are listed as cards directly above
   it, so nothing is lost that is not said twice. */
@media (max-width: 700px) {
  .vs-space text, .vs-datum, .vs-tb { display: none; }
}

/* The legend only exists where the labels cannot: above 700px the drawing
   carries its own and a second copy would be noise. */
.vs-legend { display: none; }
@media (max-width: 700px) {
  .vs-legend {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px 18px;
    margin: 18px 0 0;
    padding: 0;
    list-style: none;
    font-size: var(--text-s);
    color: var(--muted);
  }
  .vs-legend li { display: flex; gap: 8px; align-items: baseline; min-width: 0; }
  .vs-legend span {
    font-family: var(--mono);
    font-size: var(--label);
    letter-spacing: 0.12em;
    color: var(--muted-2);
    flex: none;
  }
}

/* ============================================================
   GENERAL ARRANGEMENT DRAWING
   Workshop-drawing conventions: hairline geometry, dash-dot centre
   lines, ticked dimension lines, hatched skid, numbered balloons on
   leaders, and a title block. The SVG is aria-hidden and the buttons
   beside it carry the content, so it is readable and operable without
   seeing the drawing at all.
   ============================================================ */
.drawing {
  display: grid;
  /* The parts list is five short numbered lines; the drawing is the point of
     the section. 1.5fr left it at 765px of a 1325px row -- 58% of the width,
     for the thing everything else is labelling. */
  grid-template-columns: 2.1fr 1fr;
  gap: clamp(22px, 2.8vw, 46px);
  align-items: center;
}
.drawing > * { min-width: 0; }

.ga { width: 100%; height: auto; overflow: visible; }

/* everything is stroked, nothing is filled — this is a drawing, not an
   illustration, and the ground shows through */
.ga .body,
.ga .pipe {
  fill: none;
  stroke: rgba(255, 255, 255, 0.62);
  stroke-width: 1.6;
  stroke-linejoin: round;
  transition: stroke 0.3s var(--ease), fill 0.3s var(--ease);
}
.ga .pipe { stroke-width: 3; stroke-linecap: round; }
.ga .thin line,
.ga .thin circle,
.ga .thin rect {
  fill: none;
  stroke: rgba(255, 255, 255, 0.3);
  stroke-width: 1;
  transition: stroke 0.3s var(--ease);
}

/* dash-dot: the convention for an axis */
.ga .cl {
  stroke: rgba(158, 201, 255, 0.5);
  stroke-width: 1;
  stroke-dasharray: 22 5 3 5;
}

/* dimensions sit in the cool accent, as they do on a real print */
.ga .dim line { stroke: rgba(158, 201, 255, 0.55); stroke-width: 1; }
.ga .dim path { fill: rgba(158, 201, 255, 0.75); }
.ga .dim text {
  fill: #9ec9ff;
  font-size: 13px;
  letter-spacing: 0.1em;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
/* the label interrupts its own dimension line, rather than sitting on top */
.ga .dim-bg { fill: var(--bg-2); }

/* balloons */
.ga .ball line { stroke: rgba(255, 255, 255, 0.45); stroke-width: 1; }
.ga .ball circle {
  fill: var(--bg-2);
  stroke: rgba(255, 255, 255, 0.6);
  stroke-width: 1.4;
  transition: fill 0.3s var(--ease), stroke 0.3s var(--ease);
}
.ga .ball text {
  fill: var(--white);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-anchor: middle;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* title block */
.ga .tb rect { fill: none; stroke: rgba(255, 255, 255, 0.4); stroke-width: 1.2; }
.ga .tb line { stroke: rgba(255, 255, 255, 0.4); stroke-width: 1; }
.ga .tb text {
  fill: rgba(255, 255, 255, 0.72);
  font-size: 10px;
  letter-spacing: 0.16em;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.ga .tb .tb-b { fill: var(--white); font-weight: 700; letter-spacing: 0.2em; }

/* ---------- selection ---------- */
.drawing[data-active] .prt .body,
.drawing[data-active] .prt .pipe { stroke: rgba(255, 255, 255, 0.22); }
.drawing[data-active] .prt .thin line,
.drawing[data-active] .prt .thin circle,
.drawing[data-active] .prt .thin rect { stroke: rgba(255, 255, 255, 0.12); }
.drawing[data-active] .ball circle { stroke: rgba(255, 255, 255, 0.25); }
.drawing[data-active] .ball text { fill: rgba(255, 255, 255, 0.3); }

.drawing[data-active="motor"]     .prt[data-prt="motor"]     .body,
.drawing[data-active="coupling"]  .prt[data-prt="coupling"]  .body,
.drawing[data-active="screw"]     .prt[data-prt="screw"]     .body,
.drawing[data-active="separator"] .prt[data-prt="separator"] .body,
.drawing[data-active="lines"]     .prt[data-prt="lines"]     .body,
.drawing[data-active="lines"]     .prt[data-prt="lines"]     .pipe {
  stroke: var(--white);
  stroke-width: 2.2;
  fill: rgba(45, 53, 190, 0.35);
}
.drawing[data-active="lines"] .prt[data-prt="lines"] .pipe { fill: none; }
.drawing[data-active="motor"]     .prt[data-prt="motor"]     .thin line,
.drawing[data-active="coupling"]  .prt[data-prt="coupling"]  .thin line,
.drawing[data-active="screw"]     .prt[data-prt="screw"]     .thin circle,
.drawing[data-active="separator"] .prt[data-prt="separator"] .thin line,
.drawing[data-active="separator"] .prt[data-prt="separator"] .thin rect,
.drawing[data-active="lines"]     .prt[data-prt="lines"]     .thin circle {
  stroke: rgba(255, 255, 255, 0.75);
}
.drawing[data-active="motor"]     .ball[data-prt="motor"]     circle,
.drawing[data-active="coupling"]  .ball[data-prt="coupling"]  circle,
.drawing[data-active="screw"]     .ball[data-prt="screw"]     circle,
.drawing[data-active="separator"] .ball[data-prt="separator"] circle,
.drawing[data-active="lines"]     .ball[data-prt="lines"]     circle {
  fill: var(--brand);
  stroke: var(--white);
}
.drawing[data-active="motor"]     .ball[data-prt="motor"]     text,
.drawing[data-active="coupling"]  .ball[data-prt="coupling"]  text,
.drawing[data-active="screw"]     .ball[data-prt="screw"]     text,
.drawing[data-active="separator"] .ball[data-prt="separator"] text,
.drawing[data-active="lines"]     .ball[data-prt="lines"]     text { fill: var(--white); }

/* ---------- the parts list, as real controls ---------- */
.part-list { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); }
.part {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 16px;
  width: 100%;
  text-align: left;
  background: var(--bg-2);
  border: 0;
  border-left: 2px solid transparent;
  padding: 16px 20px;
  cursor: pointer;
  transition: background 0.3s var(--ease), border-color 0.3s var(--ease);
}
.part:hover, .part:focus-visible { background: var(--panel-2); border-left-color: var(--white); }
.part-n {
  font-size: var(--label);
  font-weight: 600;
  letter-spacing: 0.18em;
  color: var(--grey-500);
  padding-top: 4px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.part-body { display: grid; gap: 2px; min-width: 0; }
.part-title { font-size: var(--h4); font-weight: 700; letter-spacing: -0.01em; }
/* 0fr -> 1fr only collapses if the row's item can shrink past its content,
   hence the inner span with min-height:0 */
.part-text {
  font-size: var(--text-m);
  color: var(--muted);
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  transition: grid-template-rows 0.4s var(--ease), opacity 0.3s var(--ease),
              margin-top 0.4s var(--ease);
}
.part-text > span { min-height: 0; overflow: hidden; }
.part[aria-expanded="true"] .part-text { grid-template-rows: 1fr; opacity: 1; margin-top: 6px; }

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

/* ============================================================
   THE ALDY CREDIT
   ============================================================ */
.made { display: flex; }
.aldy {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding-block: 6px;
  color: var(--muted-2);
  transition: color 0.25s var(--ease);
}
.aldy img { height: 13px; width: auto; opacity: 0.55; transition: opacity 0.25s var(--ease); }
.aldy b { font-weight: 700; letter-spacing: 0.08em; }
a.aldy:hover { color: var(--white); }
a.aldy:hover img { opacity: 1; }

/* ============================================================
   THE DRAWING, SIZED TO THE SCREEN
   Only where there is room: on a short window or a phone this would
   crush the drawing rather than frame it.
   ============================================================ */
@media (min-width: 901px) and (min-height: 720px) {
  .drawing-section {
    min-height: 100svh;
    display: grid;
    align-content: center;
    padding-block: clamp(32px, 4.5vh, 70px);
  }
  /* the heading block is tighter here than elsewhere, because the whole
     section has to clear one screen including it */
  .drawing-section .section-head { margin-bottom: clamp(20px, 2.6vh, 38px); }
  .drawing-section .section-head .lead { font-size: var(--text-l); }
  /* 48svh capped the drawing well below what the section had room for: the
     SVG box came out 870x456 while the drawing inside it was 668 wide, so
     202px was letterboxed away by preserveAspectRatio. The cap is what the
     heading block actually leaves, not a round number. */
  .ga { max-height: 58svh; }
}

/* ============================================================
   PAGER — previous / next across the sheet set
   A page that dead-ends at the footer gives the reader nowhere to go.
   ============================================================ */
/* Transparent, not var(--bg-2). A flat fill made this a lighter strip
   sandwiched between the block above and the footer below, with a hard edge at
   each end -- the last two on the page. The hairline is enough to say where it
   starts, and the page's own field now runs straight through it. */
.pager { border-top: 1px solid var(--line); background: transparent; }
.pg-grid {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 16px;
  padding-block: 8px;
}
.pg-cell {
  display: grid;
  gap: 4px;
  padding: 22px 4px;
  min-width: 0;
  transition: color 0.25s var(--ease);
}
.pg-next { text-align: right; }
.pg-dir {
  font-size: var(--label);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted-2);
}
.pg-name {
  font-size: var(--h4);
  font-weight: 700;
  letter-spacing: -0.01em;
  min-width: 0;
  overflow-wrap: break-word;
}
.pg-prev .pg-dir::before { content: "\2190  "; }
.pg-next .pg-dir::after  { content: "  \2192"; }
a.pg-cell:hover .pg-name { color: var(--accent); }
.pg-of {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--text-s);
  letter-spacing: 0.16em;
  /* --grey-600 measured 2.08:1 here. "01 // 08" is not decoration -- it is
     the one thing on the pager telling you where you are in the sequence --
     and at 13px it needs 4.5. This was the only contrast failure left on the
     whole site once --muted-2 was corrected. */
  color: var(--muted-2);
  padding-inline: 8px;
}

@media (max-width: 620px) {
  .pg-grid { grid-template-columns: 1fr 1fr; }
  .pg-of { grid-column: 1 / -1; grid-row: 1; text-align: center; padding-top: 14px; }
  .pg-cell { padding-block: 18px; }
  .pg-name { font-size: var(--text-l); }
}

/* The footer's bottom row -- privacy, the calculator, the ALDY credit -- sat
   at 33px tall on a phone. That clears the 24px WCAG minimum and is still an
   awkward thing to hit with a thumb, and the rest of this project is built to
   44px. Padding rather than height, so the row's rhythm is unchanged on a
   mouse; gated on pointer:coarse for the same reason as everywhere else --
   the question is whether a finger is doing the pointing. */
@media (pointer: coarse) {
  .footer-bottom a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
}

/* ============================================================
   THE CROSS
   The pointer is a registration cross everywhere on the site, not only over
   the imagery. Two strokes, no corner ticks, no centre dot -- the mark you
   line a drawing up with.

   An SVG data URI in `cursor`, so it IS the system cursor: it cannot lag
   behind the pointer, costs no JavaScript and no frame, and needs no mousemove
   handler. A cursor drawn in a div and chased with transforms is what makes
   custom cursors feel cheap; it is always a frame or two behind the thing the
   hand is holding.

   A 3px black stroke at half opacity sits under a 1px white one, so the cross
   holds on a white certificate scan and on a near-black engine room without
   changing colour. `crosshair` is the keyword fallback if the URI is refused.

   Two exceptions, both about not breaking the thing underneath. Fields where
   text is typed keep their caret, because a cross gives no indication of where
   a character will land. And coarse pointers have no cursor at all, so none of
   this applies on a phone.
   ============================================================ */
@media (hover: hover) and (pointer: fine) {
  html, body { cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='26' height='26' viewBox='0 0 26 26'%3E%3Cg fill='none' stroke-linecap='square'%3E%3Cpath d='M13 1v24M1 13h24' stroke='%23000' stroke-opacity='.5' stroke-width='3'/%3E%3Cpath d='M13 1v24M1 13h24' stroke='%23fff' stroke-width='1'/%3E%3C/g%3E%3C/svg%3E") 13 13, crosshair; }
  a, button, summary, [role="button"], label { cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='26' height='26' viewBox='0 0 26 26'%3E%3Cg fill='none' stroke-linecap='square'%3E%3Cpath d='M13 1v24M1 13h24' stroke='%23000' stroke-opacity='.5' stroke-width='3'/%3E%3Cpath d='M13 1v24M1 13h24' stroke='%23fff' stroke-width='1'/%3E%3C/g%3E%3C/svg%3E") 13 13, crosshair; }
  input, textarea, select { cursor: auto; }
  input[type="checkbox"], input[type="radio"], input[type="file"] { cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='26' height='26' viewBox='0 0 26 26'%3E%3Cg fill='none' stroke-linecap='square'%3E%3Cpath d='M13 1v24M1 13h24' stroke='%23000' stroke-opacity='.5' stroke-width='3'/%3E%3Cpath d='M13 1v24M1 13h24' stroke='%23fff' stroke-width='1'/%3E%3C/g%3E%3C/svg%3E") 13 13, crosshair; }
  /* Five components set cursor:pointer on a class, and a class beats a bare
     element selector -- so .btn, .burger, .rail-btn, .hero-tool and .part all
     kept the hand while everything around them had the cross. Named here
     rather than chased with !important. */
  .btn, .burger, .rail-btn, .hero-tool, .part { cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='26' height='26' viewBox='0 0 26 26'%3E%3Cg fill='none' stroke-linecap='square'%3E%3Cpath d='M13 1v24M1 13h24' stroke='%23000' stroke-opacity='.5' stroke-width='3'/%3E%3Cpath d='M13 1v24M1 13h24' stroke='%23fff' stroke-width='1'/%3E%3C/g%3E%3C/svg%3E") 13 13, crosshair; }
}

/* ============================================================
   WORDS LIGHT AS YOU SCROLL
   One statement on the site, not a device applied everywhere: the sentence
   that claims factory backing from BITZER and DANFOSS is the strongest thing
   the page says, so it is the one worth reading a word at a time.

   Colour only -- nothing moves, nothing is masked, and the reader drives it
   with the scrollbar rather than watching it play. `linear` on purpose: an
   eased colour change would make each word arrive with a character of its own,
   and the point is that the line fills evenly, like a level rising.

   The dim state is transient by construction -- progress is clamped to 1, so a
   reader who lands below the section, or arrives with the section already
   passed, gets every word lit. Reduced motion lights all of them outright.
   ============================================================ */
/* On display type, and only on display type. Put on a 21px body lead first,
   where 20% of the muted grey floated ABOVE the navy as pale ghost text and
   read as a drop shadow rather than as unlit type. alprojects works because
   the unlit word sits barely above a near-black ground -- etched into it, not
   hovering over it -- at 46px where that reads as deliberate. So: white at
   0.12 over this section's navy lands a few levels above the ground, which is
   the same relationship, and it lights to full white rather than to muted. */
.lightwords .w { color: rgba(255, 255, 255, 0.12); transition: color 0.42s linear; }
.lightwords .w.lit { color: var(--white); }
@media (prefers-reduced-motion: reduce) {
  .lightwords .w { color: var(--white); transition: none; }
}

/* ============================================================
   SCROLL PROGRESS
   A hairline on the header's own bottom edge. It reads as part of the
   rule that is already there, rather than as a bar bolted on top.
   ============================================================ */
.progress {
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 2px;
  transform: scaleX(var(--p, 0));
  transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--navy-400), var(--grey-200));
  pointer-events: none;
}

/* the hero photograph drifts; the transform is written by JS */
.hero-drawing { will-change: transform; }

/* ============================================================
   STAGGER
   Grid children arrive in sequence rather than all at once. Small
   delays only — enough to read as deliberate, not as waiting.
   ============================================================ */
.js .card-grid > .reveal:nth-child(2) { transition-delay: 0.07s; }
.js .card-grid > .reveal:nth-child(3) { transition-delay: 0.14s; }
.js .card-grid > .reveal:nth-child(4) { transition-delay: 0.21s; }
.js .pillars > .pillar { transition: none; }
@media (prefers-reduced-motion: reduce) {
  .js .card-grid > .reveal { transition-delay: 0s; }
}

/* ============================================================
   FROST  —  type FROST
   Ice creeps in from the edges, the page cools, and a probe readout
   falls to the temperature an RSW tank or blast freezer actually runs
   at. Thaws by itself after eleven seconds.
   ============================================================ */
:root {
  /* fractal noise, generated in the SVG itself — a frost texture without
     shipping a texture */
  --frost-tex: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='320' height='320'%3E%3Cfilter id='f'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='4' seed='7'/%3E%3C/filter%3E%3Crect width='320' height='320' filter='url(%23f)' opacity='0.7'/%3E%3C/svg%3E");
}

/* The cooling is done with an overlay, NOT `filter` on <body>.
   A filter on an element makes it the containing block for every
   position:fixed descendant — with it on body, the probe rendered at
   y=7388 instead of pinned to the corner, and the site header would have
   come unstuck from the top the moment frost switched on. */

html.is-frost::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 190;
  pointer-events: none;
  background-image: var(--frost-tex);
  background-size: 320px 320px;
  /* held to the edges, so the middle of the page stays readable */
  mask-image: radial-gradient(120% 105% at 50% 50%, transparent 12%, #000 78%);
  -webkit-mask-image: radial-gradient(120% 105% at 50% 50%, transparent 12%, #000 78%);
  opacity: 0;
  animation: frost-in 1.6s var(--ease) forwards;
  mix-blend-mode: screen;
}
html.is-frost::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 189;
  pointer-events: none;
  background:
    radial-gradient(120% 105% at 50% 50%,
      rgba(150, 195, 240, 0.10) 0%, rgba(158, 201, 255, 0.20) 34%,
      rgba(196, 226, 255, 0.62) 100%);
  opacity: 0;
  animation: frost-in 2.2s var(--ease) forwards;
}
@keyframes frost-in { to { opacity: 1; } }

/* ---------- the cold-test overlay ---------- */
#frostIce {
  position: fixed;
  inset: 0;
  z-index: 191;
  pointer-events: none;
  overflow: hidden;
}
#frostIce.is-out { animation: ice-out 1s var(--ease) forwards; }
@keyframes ice-out { to { opacity: 0; } }

/* the test banner — the mode has to announce itself */
.cold-bar {
  position: absolute;
  top: var(--nav-h);
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 9px 16px;
  background: rgba(7, 8, 36, 0.9);
  border-block: 1px solid rgba(178, 214, 255, 0.5);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 10.5px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: #cfe6ff;
  transform: translateY(-100%);
  animation: bar-in 0.55s var(--ease) 0.1s forwards;
}
@keyframes bar-in { to { transform: none; } }
.cb-sep { color: rgba(207, 230, 255, 0.35); }
.cb-live { color: #9ec9ff; }
.cb-live::before {
  content: "";
  display: inline-block;
  width: 6px; height: 6px;
  margin-right: 7px;
  background: #9ec9ff;
  vertical-align: middle;
  animation: cb-blink 1.2s steps(2, end) infinite;
}
@keyframes cb-blink { 50% { opacity: 0.2; } }

/* registration marks — the corner crosses on a technical print */
.reg {
  position: absolute;
  width: 46px;
  height: 46px;
  opacity: 0;
  animation: reg-in 0.7s var(--ease) forwards;
}
.reg::before, .reg::after {
  content: "";
  position: absolute;
  background: rgba(200, 230, 255, 0.75);
}
.reg::before { left: 0; top: 22px; width: 46px; height: 1px; }
.reg::after  { top: 0; left: 22px; width: 1px; height: 46px; }
.reg-tl { top: 118px; left: 26px; }
.reg-tr { top: 118px; right: 26px; animation-delay: 0.1s; }
.reg-bl { bottom: 26px; left: 26px; animation-delay: 0.2s; }
.reg-br { bottom: 26px; right: 26px; animation-delay: 0.3s; }
@keyframes reg-in { to { opacity: 1; } }

/* ice grains, not snowflakes: small, square, and they fall straight */
.flake {
  position: absolute;
  top: -10px;
  width: 3px;
  height: 3px;
  background: rgba(214, 236, 255, 0.7);
  animation-name: flake-fall;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
@keyframes flake-fall {
  0%   { transform: translate3d(0, -20px, 0); opacity: 0; }
  10%  { opacity: 0.85; }
  90%  { opacity: 0.85; }
  100% { transform: translate3d(8px, 100vh, 0); opacity: 0; }
}

/* ---------- the probe ---------- */
#frostProbe {
  position: fixed;
  right: clamp(16px, 3vw, 40px);
  bottom: clamp(16px, 3vw, 40px);
  z-index: 195;
  display: grid;
  gap: 2px;
  padding: 14px 20px 12px;
  /* The one place on this site where glass is the subject rather than a
     finish: this panel only exists while the page is frozen over, and the
     frost overlay behind it is a real blue gradient -- which is exactly the
     backdrop the effect needs and cannot fake. It read as a blurred
     rectangle, which is where most glassmorphism stops. The rim is what makes
     it glass: a colourless tint over the tint it already has, plus three
     inset highlights that catch the edge the way a polished bevel does. */
  background:
    linear-gradient(180deg, var(--glass-tint-top), var(--glass-tint-bottom)),
    rgba(7, 8, 36, 0.78);
  border: 1px solid rgba(178, 214, 255, 0.45);
  box-shadow:
    inset 0 1px 0 var(--glass-rim-top),
    inset 0 0 0 1px var(--glass-rim-hairline),
    inset 0 -1px 0 var(--glass-rim-bottom);
  backdrop-filter: blur(10px) saturate(130%);
  -webkit-backdrop-filter: blur(10px) saturate(130%);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: #dff0ff;
  pointer-events: none;
  animation: probe-in 0.5s var(--ease) both;
  transition: border-color 0.6s var(--ease), box-shadow 0.6s var(--ease);
}
#frostProbe.is-below { border-color: rgba(178, 214, 255, 0.8); }
#frostProbe.is-held { box-shadow: 0 0 40px rgba(120, 180, 255, 0.28); }
#frostProbe.is-out { animation: probe-in 0.6s var(--ease) reverse forwards; }

.fp-label, .fp-state {
  font-size: 9.5px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(200, 226, 255, 0.6);
}
.fp-sep { color: rgba(200, 226, 255, 0.35); }
.fp-row { display: flex; align-items: baseline; gap: 4px; }
.fp-t {
  font-size: 32px;
  font-weight: 700;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.fp-u { font-size: 13px; letter-spacing: 0.16em; opacity: 0.8; }
/* the state line blinks only while it is still falling */
#frostProbe:not(.is-held) .fp-state { animation: blink 1.1s steps(2, end) infinite; }
@keyframes blink { 50% { opacity: 0.35; } }
@keyframes probe-in { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }

/* Under reduced motion the ice still appears — it is the point of the egg —
   but nothing draws, drifts or blinks its way in. */
@media (prefers-reduced-motion: reduce) {
  html.is-frost::before, html.is-frost::after { animation: none; opacity: 1; }
  #frostProbe { animation: none; }
  .reg { animation: none; opacity: 1; }
  .cold-bar { animation: none; transform: none; }
  .cb-live::before { animation: none; }
  .flake { display: none; }
  #frostProbe:not(.is-held) .fp-state { animation: none; }
}



/* ============================================================
   LANGUAGE SWITCHER
   Each link goes to the same page in the other language, not to its
   homepage — dumping a reader back at the top is the usual way these
   get it wrong.
   ============================================================ */
/* A segmented control with a sliding indicator rather than two bordered
   boxes. The pill rests under the current language and follows the cursor to
   the other one, sliding back when the pointer leaves -- so the control shows
   where you are and previews where you would go.

   It is glass: the same colourless tint and rim as the panels elsewhere, but
   no backdrop-filter. The header already carries one, which makes it a
   backdrop root, and a filter on a child of it samples the header's output
   rather than the page -- the same trap that made the mobile menu render the
   hero at full contrast. The rim does the work here and costs nothing.

   Width and travel are computed from --n, written by the builder, so this is
   still correct if a third language comes back. */
.langs {
  display: flex;
  align-items: center;
  gap: 0;
  flex: none;
  position: relative;
  isolation: isolate;
}
.langs::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  z-index: -1;
  width: calc(100% / var(--n, 2));
  transform: translateX(calc(var(--i, 0) * 100%));
  border-radius: 2px;
  border: 1px solid var(--glass-border);
  background: linear-gradient(180deg, var(--glass-tint-top), var(--glass-tint-bottom));
  box-shadow:
    inset 0 1px 0 var(--glass-rim-top),
    inset 0 0 0 1px var(--glass-rim-hairline),
    inset 0 -1px 0 var(--glass-rim-bottom);
  transition: transform 0.34s var(--ease);
}
.langs a {
  display: grid;
  place-items: center;
  min-width: 34px;
  min-height: 34px;
  padding: 6px 8px;
  font-size: var(--text-s);
  font-weight: 600;
  letter-spacing: 0.08em;
  /* --muted-2 measured 3.92:1 on solid navy -- already under the 4.5:1 an
     interactive control needs, before any glass existed. Putting a
     translucent panel behind it took it to 1.94:1, and no amount of thickening
     the glass recovered it: at 88% surface it still only reached 3.45, because
     the colour was the problem, not the panel. --muted is what the nav links
     beside it already use, and the current language stays distinct on white
     plus a border. */
  color: var(--muted);
  border: 1px solid transparent;
  border-radius: 2px;
  transition: color 0.25s var(--ease), border-color 0.25s var(--ease);
}
.langs a:hover { color: var(--white); }
/* the pill is the indicator now, so the current item needs no border of its own */
.langs a[aria-current="true"] { color: var(--white); }

/* The cursor moves the pill. Written per index rather than with a general
   rule because :has() cannot count siblings, and stated after the resting
   position so hover wins. Three covered, which is one more than there are. */
@media (hover: hover) and (pointer: fine) {
  .langs:has(a:nth-child(1):hover)::before { transform: translateX(0); }
  .langs:has(a:nth-child(2):hover)::before { transform: translateX(100%); }
  .langs:has(a:nth-child(3):hover)::before { transform: translateX(200%); }
}
@media (prefers-reduced-motion: reduce) { .langs::before { transition: none; } }

/* In the mobile menu it becomes a full-width row of three, below the CTA. */
@media (max-width: 980px) {
  .nav-links .langs {
    justify-content: center;
    gap: 0;
    margin-top: 18px;
    padding-top: 4px;
  }
  /* equal widths, or the pill's share of the row stops matching the items */
  .nav-links .langs a { flex: 1 1 0; min-width: 56px; min-height: 44px; }
  .nav-actions .langs { display: none; }
}
@media (min-width: 981px) { .nav-links .langs { display: none; } }

/* ============================================================
   PHONE LAYOUT
   The home page ran to 14.9 screens on a 390px phone, and it got there by
   being the desktop layout narrowed rather than a layout for a phone: every
   grid collapsed to one column and every card put a full-width 16:10
   photograph above its text. Four services alone were 1,840px -- more than two
   screens to say four things.

   Nothing here shrinks type. The sizes were already right: 40px h1, 19px card
   title, 14px body. It is the arrangement that was wrong.
   ============================================================ */
@media (max-width: 620px) {
  /* The row layout the desktop already uses for its compact cards. A
     photograph beside its title says the same thing as a photograph above it
     and costs a third of the height. */
  .card, .card--feature { flex-direction: row; align-items: stretch; }
  .card-media, .card--feature .card-media {
    width: 112px;
    flex: none;
    aspect-ratio: auto;
    align-self: stretch;
  }
  .card-body, .card--feature .card-body { padding: 15px 16px 14px; }
  .card h2, .card h3, .card--feature h2, .card--feature h3 {
    font-size: var(--text-l);
    margin-bottom: 6px;
  }
  .card p, .card--feature p { font-size: var(--text-s); }
  .card-more { padding-top: 12px; }

}

/* ============================================================
   CAREERS
   ============================================================ */
.notice {
  padding: 26px 28px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 2px solid var(--line-2);
  margin-bottom: 8px;
}
.notice p { color: var(--muted); margin: 0; }
.notice strong { color: var(--text); }

.position {
  padding: 28px 30px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 2px solid var(--accent);
  margin-bottom: 16px;
}
.position-head { margin-bottom: 14px; }
.position h3 { margin-bottom: 10px; }
.position-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
  font-size: var(--text-s);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted-2);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}


/* ============================================================
   HERO — SHIP REFRIGERATION PLANT
   Full-bleed behind the hero, not a panel beside it. `slice` on
   preserveAspectRatio lets it crop rather than letterbox, so it fills
   any viewport shape the way a drawing pinned to a wall would.
   ============================================================ */
.hero-drawing {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
  /* Fades out under the headline so the type keeps its contrast — the
     drawing is ground, and ground that competes with the text is a
     failure however good it looks on its own. */
  /* Suppressed only where the headline actually sits, not down the whole
     left-hand side — the compressor sets live there, and a diagonal fade hid
     the densest part of the drawing. */
  mask-image: radial-gradient(60% 78% at 24% 52%, transparent 24%, rgba(0,0,0,0.5) 62%, #000 90%);
  -webkit-mask-image: radial-gradient(60% 78% at 24% 52%, transparent 24%, rgba(0,0,0,0.5) 62%, #000 90%);
}

.rp-body rect, .rp-body circle {
  fill: none;
  stroke: rgba(255, 255, 255, 0.62);
  stroke-width: 1.6;
  stroke-linejoin: round;
}
.rp-thin line, .rp-thin circle, .rp-thin rect, .rp-thin path {
  fill: none;
  stroke: rgba(255, 255, 255, 0.22);
  stroke-width: 1;
}
.rp-pipe { fill: none; stroke-width: 2.4; stroke-linejoin: round; stroke-linecap: round; }

/* ---------- the plant is running ----------
   A short pip travels each run in the direction the refrigerant actually goes:
   out of the compressors on the discharge, down the liquid line to the rooms,
   back to the accumulators on the suction. It is the one thing a P&ID is
   about, so the drawing behaves like the thing it depicts rather than being
   decorated.

   16 on, 210 off, offset by exactly their sum so the loop closes with no jump.
   Slow -- nine seconds end to end -- because this sits behind the headline and
   anything quicker there is a distraction rather than a background.

   Paused whenever the hero is off screen. A CSS animation on an element that
   has scrolled away still burns a frame budget on a long page, and this one
   repaints an SVG that fills the viewport. */
.rp-flow {
  stroke-dasharray: 16 210;
  stroke-width: 2.8;
  animation: rp-flow 9s linear infinite;
}
.hero.is-idle .rp-flow { animation-play-state: paused; }
@keyframes rp-flow { to { stroke-dashoffset: -226; } }

.rp-flow.rp-hp { stroke: rgba(255, 214, 176, 0.95); }
.rp-flow.rp-lq { stroke: rgba(214, 232, 178, 0.9); }
.rp-flow.rp-lp { stroke: rgba(186, 219, 255, 0.95); }

@media (prefers-reduced-motion: reduce) {
  /* not merely stopped: a pip frozen mid-run is a dashed pipe, which on a
     P&ID means a different service. Take them off the drawing entirely. */
  .rp-flow { display: none; }
}
/* temperature coding, as on a real plant drawing */
.rp-hp { stroke: rgba(255, 206, 168, 0.55); }
.rp-lp { stroke: rgba(158, 201, 255, 0.58); }
.rp-arr path { fill: rgba(255, 255, 255, 0.45); }

.rp-sym path, .rp-sym line, .rp-sym circle {
  fill: none;
  stroke: rgba(255, 255, 255, 0.55);
  stroke-width: 1.4;
}
/* dash-dot: the convention for an axis */
.rp-cl { stroke: rgba(158, 201, 255, 0.4); stroke-width: 1; stroke-dasharray: 20 5 3 5; }

.rp-lbl, .rp-note, .rp-leg text, .rp-tb text {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.16em;
}
.rp-lbl { fill: rgba(255, 255, 255, 0.45); font-size: 13px; }
.rp-note { fill: rgba(255, 255, 255, 0.3); font-size: 11px; }
.rp-leg text { fill: rgba(255, 255, 255, 0.36); font-size: 11px; }
.rp-tb rect, .rp-tb line { fill: none; stroke: rgba(255, 255, 255, 0.28); stroke-width: 1; }
.rp-tb text { fill: rgba(255, 255, 255, 0.42); font-size: 10px; }
.rp-tb .rp-tb-b { fill: rgba(255, 255, 255, 0.8); font-weight: 700; }

/* ============================================================
   THE WORK LIGHT  —  double-click the hero
   The drawing is held back so the headline reads; the lamp brightens a
   circle of it under the cursor, the way a hand lamp works over a print.

   `backdrop-filter` brightens what is ALREADY painted behind the element,
   so there is no second copy of the drawing in the DOM — the earlier
   version needed a duplicate image, this one needs nothing.
   ============================================================ */
/* The lamp holds a SECOND, brighter copy of the same drawing, revealed only
   inside the circle. `backdrop-filter: brightness()` was tried first and is
   wrong here: on a dark navy ground it amplifies the background into a bright
   blue disc rather than picking out the linework, because there is very little
   luminance in the lines to amplify. Revealing brighter artwork is the thing
   that actually reads as a lamp. */
.hero-lamp {
  position: absolute;
  inset: 0;
  z-index: 2;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.45s var(--ease);
  background: radial-gradient(circle 190px at var(--mx, 50%) var(--my, 45%),
      rgba(255, 238, 198, 0.07) 0%, transparent 68%);
  mask-image: radial-gradient(circle 190px at var(--mx, 50%) var(--my, 45%),
      #000 0%, rgba(0,0,0,0.7) 44%, transparent 72%);
  -webkit-mask-image: radial-gradient(circle 190px at var(--mx, 50%) var(--my, 45%),
      #000 0%, rgba(0,0,0,0.7) 44%, transparent 72%);
}
.hero.is-lamp .hero-lamp { opacity: 1; }

@media (max-width: 900px) {
  .hero-drawing {
    /* The 34%-tall hole cleared the headline and nothing else. On a phone the
       copy is not a left-hand column -- it runs the full width from the
       eyebrow down to the step row, so the accumulators and their labels were
       running straight through "WE CONSULT / WE ORGANISE". Clear the whole
       block of type, and drop the ink as well: at this size the drawing can
       only be texture, never something anyone reads. */
    mask-image: radial-gradient(126% 64% at 42% 42%, transparent 26%, rgba(0,0,0,0.45) 66%, #000 92%);
    -webkit-mask-image: radial-gradient(126% 64% at 42% 42%, transparent 26%, rgba(0,0,0,0.45) 66%, #000 92%);
  }
  /* The lamp's brighter copy carries .hero-drawing too, and dimming that is
     the one thing the work light must not do. */
  .hero-drawing:not(.hero-drawing--lit) { opacity: 0.55; }
}


/* the lit copy: same geometry, stronger ink */
.hero-drawing--lit { mask-image: none; -webkit-mask-image: none; }
.hero-drawing--lit .rp-body rect,
.hero-drawing--lit .rp-body circle { stroke: rgba(255, 255, 255, 0.95); }
.hero-drawing--lit .rp-thin line,
.hero-drawing--lit .rp-thin circle,
.hero-drawing--lit .rp-thin rect,
.hero-drawing--lit .rp-thin path { stroke: rgba(255, 255, 255, 0.5); }
.hero-drawing--lit .rp-hp { stroke: rgba(255, 206, 158, 0.95); }
.hero-drawing--lit .rp-lp { stroke: rgba(158, 201, 255, 0.95); }
.hero-drawing--lit .rp-sym path,
.hero-drawing--lit .rp-sym line,
.hero-drawing--lit .rp-sym circle { stroke: rgba(255, 255, 255, 0.9); }
.hero-drawing--lit .rp-arr path { fill: rgba(255, 255, 255, 0.9); }
.hero-drawing--lit .rp-lbl { fill: rgba(255, 255, 255, 0.92); }
.hero-drawing--lit .rp-note,
.hero-drawing--lit .rp-leg text { fill: rgba(255, 255, 255, 0.75); }
.hero-drawing--lit .rp-tb text { fill: rgba(255, 255, 255, 0.85); }
.hero-drawing--lit .rp-tb rect,
.hero-drawing--lit .rp-tb line { stroke: rgba(255, 255, 255, 0.6); }
.hero-drawing--lit .rp-cl { stroke: rgba(158, 201, 255, 0.8); }

/* a double-click is a word-select gesture; suppress it while the lamp is on */
.hero.is-lamp { user-select: none; -webkit-user-select: none; }


/* ---------- P&ID additions ---------- */
/* the liquid line: a third colour, as on a real plant drawing */
.rp-lq { stroke: rgba(200, 218, 164, 0.5); }
.hero-drawing--lit .rp-lq { stroke: rgba(214, 232, 176, 0.95); }

.rp-room rect {
  fill: rgba(255, 255, 255, 0.028);
  stroke: rgba(255, 255, 255, 0.22);
  stroke-width: 1.2;
}
.hero-drawing--lit .rp-room rect {
  fill: rgba(255, 255, 255, 0.07);
  stroke: rgba(255, 255, 255, 0.6);
}
.rp-room-t, .rp-room-c, .rp-tag {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.14em;
}
.rp-room-t { fill: rgba(255, 255, 255, 0.62); font-size: 19px; font-weight: 700; }
.rp-room-c { fill: rgba(158, 201, 255, 0.7); font-size: 15px; }
.rp-tag { fill: rgba(255, 255, 255, 0.3); font-size: 10px; }
.hero-drawing--lit .rp-room-t { fill: rgba(255, 255, 255, 0.95); }
.hero-drawing--lit .rp-room-c { fill: rgba(178, 214, 255, 0.95); }
.hero-drawing--lit .rp-tag { fill: rgba(255, 255, 255, 0.72); }
.rp-sym rect { fill: none; }

/* A sample vacancy is marked on the page and withheld from JobPosting
   structured data — one flag controls both. */
.position-sample {
  padding: 3px 9px;
  border: 1px solid var(--line-2);
  color: var(--accent);
  font-size: 10px;
  letter-spacing: 0.2em;
}

/* ============================================================
   PRESS FEEDBACK
   There was not one :active rule anywhere on this site. Every button, card
   and nav link accepted a press and gave nothing back until the next page
   painted. On a mouse that gap is covered by the hover state; on a phone
   there is no hover, so a tap on a slow connection looked like a tap that
   missed.

   scale(0.97) rather than a colour change: it is the thing itself moving
   under the finger, it needs no new colour token, and because it is a
   transform it composites. 140ms because press feedback lives in the
   100-160ms band -- the .btn transition is 300ms, which is right for a
   hover crossfade and much too slow to read as a press.

   Placed after the hover rules on purpose: same specificity, so source
   order decides, and a press should beat a hover.
   ============================================================ */
.btn:active,
a.card:active,
.cc > a:active,
.doc-get:active,
.lang a:active,
.hero-tool:active { transform: scale(0.94); }
  transform: scale(0.97);
  transition-duration: 0.14s;
}
/* The lift and the press are both transforms, so the press replaces the lift
   outright rather than fighting it -- which is what pressing something ought
   to look like. */
@media (prefers-reduced-motion: reduce) {
  .btn:active, a.card:active, .cc > a:active,
  .doc-get:active, .lang a:active, .hero-tool:active { transform: none; }
}

/* ============================================================
   ABOUT — the page below the machine
   ============================================================
   It was one prose column: six h2s, a bare <ul>, and the company details as a
   paragraph of <br>s, everything at the same weight. Each block here is the
   shape its own content already had. */

/* Four stacked blocks, so they need a tighter rhythm than four pages worth of
   mixed content do. The site's section padding is 89px each side, which put
   179px of empty board between one table and the next -- correct spacing for
   sections that each look different, too much for four that all read as
   ruled panels. */
.about-body .section { padding-block: clamp(30px, 3.6vw, 58px); }
.about-body .section-tight { padding-block: clamp(24px, 2.6vw, 42px); }

/* ---- 01 scope of work: a numbered register, as a drawing lists parts ---- */
.scope { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--line); }
.scope-row {
  display: grid;
  grid-template-columns: 5ch minmax(0, 1fr);
  gap: clamp(14px, 2.4vw, 44px);
  align-items: baseline;
  padding: clamp(16px, 1.9vw, 26px) 0;
  border-bottom: 1px solid var(--line);
}
.scope-num {
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.14em;
  color: var(--accent);
  /* tabular so 01 through 05 sit on one axis rather than each setting its own */
  font-variant-numeric: tabular-nums lining;
}
.scope-text { font-size: var(--text-xl); line-height: 1.5; max-width: 64ch; }

/* ---- 02 the people: one statement, the warranty carrying the weight ---- */
.statement { display: grid; gap: clamp(16px, 2vw, 26px); max-width: 74ch; }
.statement-lead { font-size: var(--text-xl); line-height: 1.55; }
.statement strong { color: var(--white); font-weight: 600; }
.statement p:last-child { color: var(--muted); }

/* ---- 03 certification, insurance, representation ---- */
.asr { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 4px; }
.asr-item {
  /* label, body, then the link on a common baseline across all three. With
     align-content:start the two panels that have a link put it directly under
     their own text, so the three links sat at three different heights and the
     panel without one ended in a hole. */
  display: grid;
  grid-template-rows: auto 1fr auto;
  gap: 14px;
  padding: clamp(22px, 2.4vw, 34px);
  border: 1px solid var(--line);
  background: var(--panel);
}
.asr-label {
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted-2);
  margin: 0;
}
.asr-item p + p, .asr-item p { margin: 0; color: var(--muted); }
.asr-item strong { color: var(--white); font-weight: 600; }
.asr-more {
  grid-row: 3;
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  align-self: end;
  /* 44px of target on a phone, without opening a gap in the grid's own
     spacing the way display:block once did between two menu labels */
  padding-block: 12px;
  margin-block: -12px 0;
}
.asr-more:hover { color: var(--white); }

/* ---- 04 company details: an actual title block ---- */
.tblock { border: 1px solid var(--line); background: var(--panel); }
.tblock-head {
  display: grid;
  gap: 10px;
  padding: clamp(20px, 2.4vw, 34px);
  border-bottom: 1px solid var(--line);
}
.tblock-head h2 { margin: 0; font-size: var(--h3); }
.tblock-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
.tblock-cell {
  display: grid;
  gap: 10px;
  align-content: start;
  padding: clamp(20px, 2.4vw, 34px);
  border-left: 1px solid var(--line);
}
.tblock-cell:first-child { border-left: 0; }
.tblock-k {
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted-2);
  margin: 0;
}
.tblock-v { margin: 0; line-height: 1.6; font-variant-numeric: tabular-nums lining; }

@media (max-width: 860px) {
  .asr, .tblock-grid { grid-template-columns: minmax(0, 1fr); }
  .tblock-cell { border-left: 0; border-top: 1px solid var(--line); }
  .tblock-cell:first-child { border-top: 0; }
  .scope-row { grid-template-columns: minmax(0, 1fr); gap: 6px; }
}

/* ============================================================
   SERVICES INDEX — four rows, not four quarters
   ============================================================
   The index was the home page's card grid a second time: four cards abreast
   at 330px, a strip of photograph each, titles wrapping to two lines and the
   summary cut off. Four services is the whole of what the company does. */
.srows { display: grid; gap: clamp(4px, 0.6vw, 10px); }
.srow {
  display: grid;
  grid-template-columns: minmax(0, 1.02fr) minmax(0, 1fr);
  align-items: stretch;
  border: 1px solid var(--line);
  background: var(--panel);
  color: inherit;
  /* clip, not hidden. overflow:hidden makes the box a scroll container, and
     view() resolves against the nearest scroll container -- so the image's
     own timeline became "how far has the image moved inside this panel",
     which is never, and the animation sat frozen at exactly 50% progress
     (scale 1.08, the midpoint of 1.14 and 1.02) at every scroll position.
     Nothing errored and the page looked deliberate. clip crops without
     creating a scroll container, so the timeline is the page again. */
  overflow: clip;
  transition: background 0.35s var(--ease), border-color 0.35s var(--ease);
}
.srow:hover { background: var(--panel-2); border-color: var(--line-2); }
/* Alternating, so the eye crosses the page instead of running down one edge */
.srow:nth-child(even) .srow-media { grid-column: 2; grid-row: 1; }
.srow:nth-child(even) .srow-body  { grid-column: 1; grid-row: 1; }

.srow-media { position: relative; overflow: clip; min-height: clamp(240px, 26vw, 360px); }
.srow-media img {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  /* Cool and held back, so four photographs of workshops do not turn the page
     into a contact sheet -- they are the ground the type stands on. */
  filter: grayscale(0.34) saturate(0.72) brightness(0.72) contrast(1.06);
  transition: filter 0.4s var(--ease), transform 0.6s var(--ease);
}
.srow:hover .srow-media img { filter: grayscale(0.12) saturate(0.9) brightness(0.86) contrast(1.04); }
/* a hairline where the photograph meets the text, on the side it meets it */
.srow-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent 55%, rgba(7, 8, 36, 0.55));
}
.srow:nth-child(even) .srow-media::after {
  background: linear-gradient(270deg, transparent 55%, rgba(7, 8, 36, 0.55));
}

.srow-body {
  display: grid;
  align-content: center;
  gap: clamp(10px, 1.2vw, 18px);
  padding: clamp(26px, 3vw, 52px);
  min-width: 0;
}
.srow-num {
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.14em;
  color: var(--accent);
  font-variant-numeric: tabular-nums lining;
}
.srow-body h2 { font-size: var(--h3); margin: 0; max-width: 18ch; text-wrap: balance; }
.srow-lead { color: var(--muted); max-width: 46ch; }
.srow-body .tags { margin: 0; }
.srow-more {
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
}
.srow-more::after { content: " \2192"; }
.srow:hover .srow-more { color: var(--white); }

@media (max-width: 860px) {
  .srow { grid-template-columns: minmax(0, 1fr); }
  /* On one column the alternation has nothing to alternate across, and left
     as it was it put every second photograph BELOW its own text. */
  .srow:nth-child(even) .srow-media,
  .srow:nth-child(even) .srow-body { grid-column: 1; grid-row: auto; }
  .srow-media { min-height: clamp(180px, 52vw, 260px); }
  .srow-media::after,
  .srow:nth-child(even) .srow-media::after {
    background: linear-gradient(0deg, rgba(7, 8, 36, 0.6), transparent 60%);
  }
}

/* The scroll effect: the photograph settles as the row crosses the screen.
   Tied to the view timeline like every other entrance on this site, so it is
   a function of where the row is rather than of its own clock -- scroll half
   as far and it is half settled. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .js .srow-media img {
      animation: srow-settle linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
    }
    @keyframes srow-settle {
      from { transform: scale(1.14) translateY(2.5%); }
      to   { transform: scale(1.02) translateY(-2.5%); }
    }
  }
}

/* ============================================================
   COMPLETED WORKS — a schedule of plant, not a wrap of chips
   ============================================================
   The page was a prose column half the width of the screen with sixteen
   manufacturer names set at label size inside it. Those makes are the most
   checkable thing on the page -- the plant this company has actually had
   apart -- and they were the smallest type on it. */
.cw-body .section { padding-block: clamp(30px, 3.6vw, 58px); }
.mx {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 4px;
}
.mx--wide { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.mx-cell {
  display: grid;
  gap: 8px;
  align-content: center;
  min-height: clamp(84px, 8vw, 116px);
  padding: clamp(14px, 1.5vw, 22px);
  border: 1px solid var(--line);
  background: var(--panel);
  transition: background 0.3s var(--ease), border-color 0.3s var(--ease);
}
.mx-cell:hover { background: var(--panel-2); border-color: var(--line-2); }
.mx-num {
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.14em;
  color: var(--muted-2);
  font-variant-numeric: tabular-nums lining;
}
.mx-name {
  font-size: var(--h4);
  line-height: 1.15;
  letter-spacing: -0.01em;
  /* Wartsila, Hyundai Himsen and GRASSO / KUHLAUTOMAT are all one name; the
     cell may wrap them but must not let them run out of it. */
  overflow-wrap: anywhere;
}
.mx-more {
  margin: clamp(18px, 2vw, 28px) 0 0;
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.mx-more a {
  color: var(--accent);
  /* 14px of label is not a tap target. inline-block rather than block: the
     parent is a <p>, so there is no flex gap here to collapse the way a
     block display once merged two menu labels. */
  display: inline-block;
  padding-block: 12px;
  margin-block: -12px;
}
.mx-more a:hover { color: var(--white); }

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

/* ============================================================
   CONTACTS — dial code, and a calendar that is asked for
   ============================================================ */
/* Two controls, one field. The select sizes to its own content so the number
   keeps the rest, and both are the same height so the pair reads as one box
   rather than as a control with a badge stuck on it. */
.field-phone { display: flex; gap: 6px; align-items: stretch; }
.field-phone .dial {
  flex: 0 0 auto;
  width: auto;
  /* 16px, like every other input here: anything smaller and iOS Safari zooms
     the page on focus, which on a form is the whole layout jumping. */
  font-size: 16px;
  font-family: var(--mono);
  letter-spacing: 0.04em;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--line);
  padding: 0 34px 0 14px;
  min-height: 48px;
  border-radius: 0;
  appearance: none;
  -webkit-appearance: none;
  /* the chevron, drawn rather than fetched */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='7'%3E%3Cpath d='M1 1l4.5 4.5L10 1' fill='none' stroke='%238d90a6' stroke-width='1.4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 13px center;
}
.field-phone .dial:focus-visible { border-color: var(--line-2); outline: none; }
.field-phone input { flex: 1 1 auto; min-width: 0; }
/* The option list is painted by the platform, so it takes the platform's
   colours -- on a dark page that means white-on-white unless it is said. */
.field-phone .dial option { background: var(--navy-800); color: var(--text); }

.booking { display: grid; gap: 18px; }
.booking-ask {
  display: grid;
  justify-items: start;
  gap: 16px;
  max-width: 62ch;
  padding: clamp(22px, 2.6vw, 38px);
  border: 1px solid var(--line);
  background: var(--panel);
}
.booking-note { margin: 0; color: var(--muted); font-size: var(--text-m); }
.booking-alt { margin: 0; font-size: var(--text-s); }
.booking-alt a { color: var(--muted-2); padding-block: 10px; display: inline-block; }
.booking-alt a:hover { color: var(--white); }
.booking-alt.is-error a { color: var(--white); }
.booking-mount { border: 1px solid var(--line); background: var(--bg-2); }

/* ============================================================
   CAREERS — open roles, and a way into the form from each
   ============================================================ */
.jobs { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
.job {
  display: grid;
  gap: 14px;
  align-content: start;
  padding: clamp(24px, 2.8vw, 42px);
  border: 1px solid var(--line);
  background: var(--panel);
  transition: background 0.3s var(--ease), border-color 0.3s var(--ease);
}
.job:hover { background: var(--panel-2); border-color: var(--line-2); }
.job-head { display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap; }
.job-num {
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.14em;
  color: var(--accent);
  font-variant-numeric: tabular-nums lining;
}
.job-head h3 { margin: 0; font-size: var(--h3); }
.job-badge {
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted-2);
  border: 1px solid var(--line-2);
  padding: 4px 9px;
}
.job-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 22px;
  margin: 0;
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted-2);
}
.job-summary { margin: 0; max-width: 78ch; }
.job-needs { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; max-width: 78ch; }
.job-needs li { position: relative; padding-left: 26px; color: var(--muted); font-size: var(--text-m); }
.job-needs li::before {
  content: "";
  position: absolute;
  left: 0;
  top: calc(0.825em - 1px);
  top: calc(0.5lh - 1px);
  width: 10px; height: 1px;
  background: var(--grey-500);
}
.job-apply {
  justify-self: start;
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  border: 1px solid var(--line-2);
  padding: 13px 20px;      /* a 44px target without a display change */
  transition: background 0.25s var(--ease), color 0.25s var(--ease);
}
.job-apply:hover { background: var(--white); color: var(--navy-900); border-color: var(--white); }
.job-apply::after { content: " \2192"; }

/* ---- the CV control ----
   A <label> around the input, so the picker opens natively on the first press
   from mouse and keyboard alike. The input is not display:none -- that takes
   it out of the focus order and the control stops existing for a keyboard.
   It is transparent and laid over the face instead. */
.field-label {
  display: block;
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted-2);
  margin-bottom: 10px;
}
.drop {
  position: relative;
  display: block;
  border: 1px dashed var(--line-2);
  background: var(--panel);
  transition: background 0.25s var(--ease), border-color 0.25s var(--ease);
  cursor: pointer;
}
.drop:hover, .drop.is-over { background: var(--panel-2); border-color: var(--accent); }
.drop.has-file { border-style: solid; }
.drop-input {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  opacity: 0;
  cursor: pointer;
}
.drop-input:focus-visible + .drop-face { outline: 2px solid var(--accent); outline-offset: 2px; }
.drop-face {
  display: grid;
  gap: 6px;
  justify-items: center;
  text-align: center;
  padding: clamp(22px, 3vw, 38px) 20px;
  pointer-events: none;
}
.drop-main { color: var(--text); font-size: var(--text-l); }
.drop-hint { color: var(--muted-2); font-size: var(--text-s); }
.drop-file {
  margin: 10px 0 0;
  font-family: var(--mono);
  font-size: var(--text-s);
  color: var(--muted);
}
.drop-file.is-error { color: #ff8a8a; }
/* An author display always beats the hidden attribute; without this the file
   line shows before a file is chosen. */
[hidden] { display: none !important; }

/* The role picker: the same control as the dial code, full width. */
.select {
  width: 100%;
  font-size: 16px;
  font-family: inherit;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--line);
  padding: 0 40px 0 14px;
  min-height: 48px;
  border-radius: 0;
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='7'%3E%3Cpath d='M1 1l4.5 4.5L10 1' fill='none' stroke='%238d90a6' stroke-width='1.4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 15px center;
}
.select:focus-visible { border-color: var(--line-2); outline: none; }
.select option { background: var(--navy-800); color: var(--text); }
