/* MSP3D — the whole site's styling. Hand-written, one file, no framework, no build step, and nothing
 * fetched from another host: with the network unplugged this page still renders exactly as designed.
 *
 * WHAT THIS IS TRYING TO LOOK LIKE. MSP3D sells measurement: point clouds registered to survey control,
 * orthomosaics, CAD held to a tolerance, parts printed to a layer height. The buyer is a surveyor, a
 * project engineer or a facilities manager, and what convinces that reader is not warmth -- it is
 * evidence that this shop is precise. So the page is built like a drawing sheet and an instrument
 * readout: hairline rules that actually align, square corners, no drop shadows, equipment names set in
 * a monospace face where a spec sheet would set them, and a visible measurement grid behind the hero.
 * The site this code was ported from was a friendly consumer layout -- rounded cards, soft shadows,
 * pill chips, a tint on every surface. Every one of those has been taken out on purpose: two businesses
 * that share a stylesheet look like one business, and this one is not selling the same thing.
 *
 * PALETTE. Three roles, each with exactly one job:
 *   ink       graphite, and the primary action. A filled button is ink on paper (18.2:1) in light mode
 *             and paper on ink (16.6:1) in dark. The call to action is the most legible thing on the
 *             page rather than the most colourful, which is how a technical document behaves.
 *   accent    the brand's cyan, kept because the wordmark, the existing site and whatever is printed on
 *             the van are cyan -- but demoted to a data colour: links, icons, the measured surfaces in
 *             the point cloud. #0d6377 is 6.8:1 on white; #5fdcf0 is 11.2:1 on the dark sheet. The neon
 *             #00ffff of the old page is 1.3:1 on white and is used nowhere as text.
 *   mark      survey orange (#b45309, 5.0:1 on white; #fbbf24, 10.9:1 on the slab). Reserved for
 *             registration marks: the section index on a sheet rule, the tick before a capability, the
 *             corner ticks of the point-cloud frame, the required-field asterisk. It never fills a
 *             button and never sets a sentence. It is the colour of a control point, used like one.
 * Neutrals are cool rather than grey so the cyan looks native to them. Every text pair clears 4.5:1 and
 * every border that carries meaning (inputs, the ghost button) clears 3:1; the numbers are in comments
 * beside the tokens they were checked against.
 *
 * TYPE. Two system stacks, no web font: a font CDN is a third party on the critical path (the old site
 * lost its icons exactly that way) and a self-hosted binary is a FOUT on site wifi. The sans carries
 * prose. The mono carries what a spec sheet sets in mono -- indices, labels, equipment names, hours --
 * which is where the technical register comes from without a single downloaded byte.
 *
 * Conventions: tokens on :root, redefined once for dark and once inside .slab. Blocks in document
 * order. Every interactive thing has a visible :focus-visible ring. Nothing animates for a visitor who
 * has asked for less motion.
 */

/* --- tokens ------------------------------------------------------------------------------------- */

:root {
  color-scheme: light dark;

  /* The page is the drafting table; content sits on white sheets laid on it. */
  --ground: #f1f4f7;
  --sheet: #ffffff;
  --sheet-2: #f7f9fb;

  --ink: #10161d; /* 18.2:1 on white */
  --ink-2: #4b5765; /* 7.4:1 on white, 6.7:1 on the ground */
  --ink-3: #5c6b79; /* 5.5:1 on white, 5.0:1 on the ground, 5.2:1 on sheet-2 */

  --rule: #dde3e9;
  --rule-2: #c3cdd6;
  --field: #7e8d9b; /* input borders: 3.4:1 on white, clears the 3:1 a control boundary needs */

  --accent: #0d6377; /* 6.8:1 on white */
  --accent-2: #0a4b5b;
  --accent-ring: rgb(13 99 119 / 30%);

  --mark: #b45309; /* 5.0:1 on white */

  --danger: #b91c1c;
  --danger-ink: #991b1b; /* 7.2:1 on the pale wash the rule below mixes */

  --btn-bg: #10161d;
  --btn-bg-hover: #2a3644;
  --btn-ink: #ffffff;

  /* The survey grid behind the hero, and the ground plane inside the point-cloud plate. */
  --grid: rgb(16 22 29 / 7%);
  --pc-grid: rgb(16 22 29 / 16%);
  --pc-lo: #1e4e6b;
  --pc-mid: #0e94ad;
  --pc-hi: #c2600a;

  /* The dark slab: the "Solutions in Action" band and the footer. It has its own tokens because it is
     the same graphite in both colour schemes, so nothing inside it needs a local override. */
  --slab: #0d1319;
  --slab-sheet: #161d26;
  --slab-ink: #eef3f8; /* 16.3:1 */
  --slab-ink-2: #a7b8c6; /* 8.9:1 */
  --slab-rule: rgb(255 255 255 / 14%);
  --slab-accent: #67e8f9; /* 12.6:1 */
  --slab-mark: #fbbf24; /* 10.9:1 */

  --sp-1: 0.25rem;
  --sp-2: 0.5rem;
  --sp-3: 0.75rem;
  --sp-4: 1rem;
  --sp-5: 1.5rem;
  --sp-6: 2rem;
  --sp-7: 3rem;

  /* Square, not rounded. 3px on controls only, so an input still reads as a control. */
  --radius: 3px;

  --shell: 76rem;
  --measure: 62ch;

  --sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial,
    "Noto Sans", sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", "Courier New",
    monospace;
}

@media (prefers-color-scheme: dark) {
  :root {
    --ground: #0a0e13;
    --sheet: #10161d;
    --sheet-2: #151c25;

    --ink: #e8eef4; /* 15.6:1 on the sheet */
    --ink-2: #9daebd; /* 8.0:1 */
    --ink-3: #7b8b9a; /* 5.2:1 */

    --rule: #212b36;
    --rule-2: #2e3a47;
    --field: #5b6977; /* ~3.3:1 on the sheet */

    --accent: #5fdcf0; /* 11.2:1 on the sheet */
    --accent-2: #a5f3fc;
    --accent-ring: rgb(95 220 240 / 32%);

    --mark: #fbbf24; /* 10.9:1 */

    --danger: #ef4444;
    --danger-ink: #fca5a5;

    --btn-bg: #e8eef4;
    --btn-bg-hover: #ffffff;
    --btn-ink: #0a0e13; /* 16.6:1 */

    --grid: rgb(255 255 255 / 6%);
    --pc-grid: rgb(255 255 255 / 12%);
    --pc-lo: #2b7f9b;
    --pc-mid: #22d3ee;
    --pc-hi: #fbbf24;

    /* Deeper than the page, so the slab still reads as a slab when the page is already dark. */
    --slab: #05080b;
    --slab-sheet: #0c1218;
  }
}

/* Anything inside the dark slab re-points the tokens instead of needing rules of its own: a button, a
   link or a rule dropped in there lands on the light-on-dark pairing automatically, in both schemes. */
.slab {
  --ground: var(--slab);
  --sheet: var(--slab-sheet);
  --sheet-2: #131b23;
  --ink: var(--slab-ink);
  --ink-2: var(--slab-ink-2);
  --ink-3: var(--slab-ink-2);
  --rule: var(--slab-rule);
  --rule-2: rgb(255 255 255 / 22%);
  --accent: var(--slab-accent);
  --accent-2: #a5f3fc;
  --mark: var(--slab-mark);
  /* The slab is dark in both schemes, so an error message inside one has to be light in both. */
  --danger: #ef4444;
  --danger-ink: #fca5a5;
  --btn-bg: var(--slab-ink);
  --btn-bg-hover: #ffffff;
  --btn-ink: var(--slab);

  background: var(--slab);
  color: var(--slab-ink);
}

/* --- reset -------------------------------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 1.0625rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

h1,
h2,
h3 {
  margin: 0;
  line-height: 1.12;
  /* Tight tracking is most of what separates a drawn headline from a typed one at these sizes. */
  letter-spacing: -0.025em;
  font-weight: 660;
  text-wrap: balance;
}

p {
  margin: 0;
}

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

dl,
dd {
  margin: 0;
}

a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
}

a:hover {
  color: var(--accent-2);
}

img,
svg {
  max-width: 100%;
}

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

.sprite {
  display: none;
}

.icon {
  width: 1.25em;
  height: 1.25em;
  flex: none;
  vertical-align: -0.25em;
}

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

.skip-link {
  position: absolute;
  left: var(--sp-4);
  top: var(--sp-2);
  z-index: 20;
  transform: translateY(-200%);
  padding: 0.6rem 1rem;
  background: var(--btn-bg);
  color: var(--btn-ink);
  font-family: var(--mono);
  font-size: 0.8125rem;
  text-decoration: none;
}

.skip-link:focus {
  transform: translateY(0);
}

/* A label set the way an instrument sets one: mono, upper case, widely tracked, short. It is used for
   the nav, the section indices, field labels, capability tags and the footer headings -- never for a
   sentence, because tracked upper case is slow to read in quantity. */
.mono {
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* --- layout ------------------------------------------------------------------------------------- */

.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: 1.25rem;
}

@media (width >= 48em) {
  .shell {
    padding-inline: 2rem;
  }
}

.section {
  padding-block: clamp(2.75rem, 7vw, 4.5rem);
  /* The masthead is sticky, so an anchor jump has to stop short of it. */
  scroll-margin-top: 3.5rem;
}

/* The drawing-sheet device the whole page is built on: a hairline across the full measure, the section
   index sitting on it in survey orange, the heading under it. Every band of the page starts this way,
   which is what makes the page read as one sheet rather than a stack of cards. */
.section__head {
  margin-bottom: clamp(1.75rem, 4vw, 2.5rem);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--ink);
}

.section__head h2 {
  margin-top: var(--sp-3);
  font-size: clamp(1.55rem, 1.1rem + 1.7vw, 2.15rem);
}

/* :not(.index) so this lead-paragraph rule cannot outrank .index below, which is also a direct <p>
   child of a head -- at equal weight it would win on source order, at (0,1,1) it wins regardless. */
.section__head > p:not(.index) {
  max-width: var(--measure);
  margin-top: var(--sp-3);
  color: var(--ink-2);
  font-size: 1rem;
}

.index {
  display: block;
  color: var(--mark);
  font-family: var(--mono);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.14em;
}

/* --- buttons ------------------------------------------------------------------------------------ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.75rem 1.35rem;
  border: 1px solid var(--btn-bg);
  border-radius: var(--radius);
  background: var(--btn-bg);
  color: var(--btn-ink);
  font-family: var(--mono);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.btn:hover {
  background: var(--btn-bg-hover);
  border-color: var(--btn-bg-hover);
  color: var(--btn-ink);
}

/* The second action. Its border is the field grey rather than a hairline tint, because the border is
   the only thing identifying it as a control and has to clear 3:1 on every ground it is used on:
   3.1:1 on the light hero, 3.5:1 on the dark one. */
.btn--ghost {
  background: transparent;
  border-color: var(--field);
  color: var(--ink);
}

.btn--ghost:hover {
  background: transparent;
  border-color: var(--ink);
  color: var(--ink);
}

.btn--sm {
  padding: 0.5rem 0.9rem;
  font-size: 0.75rem;
}

/* --- masthead ----------------------------------------------------------------------------------- */

.masthead {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--ground);
  background: color-mix(in srgb, var(--ground) 90%, transparent);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--rule);
}

.masthead__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3) var(--sp-5);
  min-height: 3.5rem;
  padding-block: 0.5rem;
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--ink);
  font-family: var(--mono);
  font-size: 0.9375rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-decoration: none;
}

.wordmark__logo {
  width: 1.4rem;
  height: 1.4rem;
  color: var(--accent);
  flex: none;
}

.masthead__nav {
  display: flex;
  gap: var(--sp-5);
  margin-inline-start: auto;
}

.masthead__nav a {
  color: var(--ink-2);
  text-decoration: none;
}

.masthead__nav a:hover {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 0.4em;
}

@media (width < 46em) {
  /* Below this the three jump links and the call to action cannot share a line without wrapping into
     something that looks broken. Every one of them is repeated in the footer, so a small-screen
     visitor loses nothing. */
  .masthead__nav {
    display: none;
  }

  .masthead__cta {
    margin-inline-start: auto;
  }
}

/* --- hero --------------------------------------------------------------------------------------- */
/* Paper, not a glowing panel: the survey grid behind it is the whole statement. The old site opened on
   a near-black hero washed in neon, which is a nightclub, not a measurement company. */

.hero {
  position: relative;
  overflow: hidden;
  border-bottom: 1px solid var(--rule);
}

.hero::before {
  /* A station grid on a fixed 4rem module, faded out at the edges so it reads as a drawing underlay
     rather than as wallpaper. Two 1px gradients: no image, and crisp at any zoom. */
  content: "";
  position: absolute;
  inset: 0;
  background-image: linear-gradient(to right, var(--grid) 1px, transparent 1px),
    linear-gradient(to bottom, var(--grid) 1px, transparent 1px);
  background-size: 4rem 4rem;
  -webkit-mask-image: radial-gradient(120% 100% at 30% 0%, #000 35%, transparent 78%);
  mask-image: radial-gradient(120% 100% at 30% 0%, #000 35%, transparent 78%);
  pointer-events: none;
}

.hero__inner {
  position: relative;
  display: grid;
  gap: clamp(2rem, 5vw, 3.5rem);
  padding-block: clamp(2.5rem, 7vw, 4.5rem);
}

@media (width >= 64em) {
  .hero__inner--split {
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.95fr);
    align-items: center;
  }
}

.hero__where {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  max-width: 46ch;
  color: var(--ink-2);
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  line-height: 1.55;
}

.hero__where .icon {
  width: 1rem;
  height: 1rem;
  margin-top: 0.1rem;
  color: var(--mark);
}

.hero h1 {
  margin-top: var(--sp-4);
  max-width: 14ch;
  font-size: clamp(2.5rem, 1.4rem + 4.6vw, 4.25rem);
  font-weight: 700;
  letter-spacing: -0.038em;
}

.hero__lead {
  max-width: 54ch;
  margin-top: var(--sp-5);
  color: var(--ink-2);
  font-size: clamp(1.0625rem, 1rem + 0.35vw, 1.1875rem);
}

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

.hero__contact {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: var(--sp-5);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--rule);
  color: var(--ink-2);
  font-family: var(--mono);
  font-size: 0.8125rem;
}

.hero__contact .icon {
  width: 1rem;
  height: 1rem;
  color: var(--accent);
}

/* A detail page's h1 is a service name and its hero carries a breadcrumb, an icon and no picture, so
   the headline is set two steps down from the home page's. */
.hero--detail h1 {
  max-width: 20ch;
  font-size: clamp(2.1rem, 1.5rem + 2.4vw, 3rem);
}

/* --- the point-cloud plate ----------------------------------------------------------------------- */
/* The one picture on the site, and it is drawn rather than photographed: an elevation-coloured point
 * cloud of a lot with a building on it, generated as SVG so it is exact, weighs 10KB, needs no host and
 * re-colours itself for the colour scheme. The old page used a placeholder pulled from a stock-image
 * host for one of its three showcase images, and a placeholder is worse than nothing. This says what
 * the business actually hands over. It is framed like a viewer window with orange registration ticks at
 * the corners, because the frame is the claim: measured data, not an illustration.
 */

.plate {
  position: relative;
  padding: clamp(1rem, 3vw, 1.75rem);
  border: 1px solid var(--rule-2);
  background: var(--sheet);
}

.plate::before {
  /* Eight 1px gradients: an L-shaped tick in each corner of the frame. */
  content: "";
  position: absolute;
  inset: -1px;
  pointer-events: none;
  background-image: linear-gradient(var(--mark), var(--mark)),
    linear-gradient(var(--mark), var(--mark)), linear-gradient(var(--mark), var(--mark)),
    linear-gradient(var(--mark), var(--mark)), linear-gradient(var(--mark), var(--mark)),
    linear-gradient(var(--mark), var(--mark)), linear-gradient(var(--mark), var(--mark)),
    linear-gradient(var(--mark), var(--mark));
  background-repeat: no-repeat;
  background-size: 0.9rem 1px, 1px 0.9rem, 0.9rem 1px, 1px 0.9rem, 0.9rem 1px, 1px 0.9rem,
    0.9rem 1px, 1px 0.9rem;
  background-position: left top, left top, right top, right top, left bottom, left bottom,
    right bottom, right bottom;
}

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

.pc-grid {
  stroke: var(--pc-grid);
}

.pc-lo {
  stroke: var(--pc-lo);
}

.pc-mid {
  stroke: var(--pc-mid);
}

.pc-hi {
  stroke: var(--pc-hi);
}

/* --- the matrix: services, and any other ruled grid of cells -------------------------------------- */
/* Hairlines between cells rather than gaps between cards. A 1px grid gap over a ruled background gives
 * a continuous grid at every column count, on any screen, with no half-pixel seams -- the difference
 * between a page that looks drawn and a page that looks assembled. */

.matrix {
  display: grid;
  gap: 1px;
  border: 1px solid var(--rule-2);
  background: var(--rule-2);
}

@media (width >= 44em) {
  .matrix--services {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* Four across on a wide screen, which is what four services want: three and a lonely fourth on a second
   row is the layout to avoid. */
@media (width >= 68em) {
  .matrix--services {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* The custom-work band: three offerings, so three tracks wide and two below that. The odd-last-child
   span is the same guard .matrix--specs carries -- this grid draws its hairlines by letting the
   container background through a 1px gap, so a grid area no cell covers renders as a solid block of
   seam colour, not as white space. It has to be reset at three tracks, where all three cells land in
   one row and a spanning third would drop to a row of its own. */
@media (width >= 44em) {
  .matrix--custom {
    grid-template-columns: repeat(2, 1fr);
  }

  .matrix--custom .cell:last-child:nth-child(odd) {
    grid-column: 1 / -1;
  }
}

@media (width >= 68em) {
  .matrix--custom {
    grid-template-columns: repeat(3, 1fr);
  }

  .matrix--custom .cell:last-child:nth-child(odd) {
    grid-column: auto;
  }
}

.cell {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: var(--sp-5);
  background: var(--sheet);
  transition: background-color 0.15s ease;
}

.cell:hover {
  background: var(--sheet-2);
}

.cell::before {
  /* A 2px rule that draws itself across the top of the cell on hover: the page's one piece of
     movement, and it is a measuring line. */
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.2s ease;
}

.cell:hover::before,
.cell:focus-within::before {
  transform: scaleX(1);
}

.cell__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  color: var(--accent);
}

.cell__top .icon {
  width: 1.75rem;
  height: 1.75rem;
}

.cell__top .index {
  color: var(--ink-3);
}

.cell h3 {
  margin-top: var(--sp-4);
  font-size: 1.125rem;
  font-weight: 640;
}

.cell__blurb {
  margin-top: var(--sp-3);
  color: var(--ink-2);
  font-size: 0.9375rem;
  line-height: 1.55;
}

.cell__link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: var(--sp-5);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--rule);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: none;
}

.cell__link::after {
  /* Stretches the one link over the whole cell, so the cell is a big tap target on a phone without
     nesting anything clickable inside anything else clickable. */
  content: "";
  position: absolute;
  inset: 0;
}

.cell__link .icon {
  width: 1rem;
  height: 1rem;
  color: var(--accent);
  transition: transform 0.15s ease;
}

.cell:hover .cell__link .icon {
  transform: translateX(3px);
}

.cell:has(.cell__link:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* The capability tags: a parts list. Mono, in the owner's own casing, each one behind an orange
   dimension tick. The layout this was ported from made them pill-shaped chips, which reads as a tag
   cloud on a blog rather than as the equipment a job will be shot with. */
.specs {
  display: grid;
  gap: 0.35rem;
  margin-top: var(--sp-4);
  color: var(--ink-2);
  font-family: var(--mono);
  font-size: 0.8125rem;
  line-height: 1.45;
}

.specs li {
  position: relative;
  padding-inline-start: 1.15rem;
}

.specs li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  top: 0.6em;
  width: 0.6rem;
  height: 2px;
  background: var(--mark);
}

.cell .specs {
  /* flex: 1 pins .cell__link to the bottom, which makes this grid taller than its rows; without
     align-content the auto rows stretch to absorb the slack and the dimension ticks drift apart, by a
     different amount in every cell of the row. The slack belongs below the list. */
  flex: 1;
  align-content: start;
}

/* --- solutions in action -------------------------------------------------------------------------- */

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

@media (width >= 52em) {
  .records {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--sp-6);
  }
}

.record {
  position: relative;
  padding-top: var(--sp-5);
  border-top: 1px solid var(--rule-2);
}

.record::before {
  /* A short accent segment sitting on the hairline, like the start of a dimension line. */
  content: "";
  position: absolute;
  inset-block-start: -1px;
  inset-inline-start: 0;
  width: 3rem;
  height: 2px;
  background: var(--accent);
}

.record__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  color: var(--accent);
}

.record__top .icon {
  width: 1.6rem;
  height: 1.6rem;
}

.record__top .index {
  color: var(--ink-3);
}

.record h3 {
  margin-top: var(--sp-4);
  font-size: 1.125rem;
}

.record p {
  margin-top: var(--sp-3);
  color: var(--ink-2);
  font-size: 0.9375rem;
}

/* One line of data under the records: label left, value right, hairline box. It is the shape a station
   log uses, and it is how the service area gets stated as a fact rather than as a slogan. */
.strip {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-2) var(--sp-5);
  margin-top: clamp(2rem, 5vw, 3rem);
  padding: var(--sp-4) var(--sp-5);
  border: 1px solid var(--rule);
}

.strip__label {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  color: var(--mark);
  font-family: var(--mono);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.strip__label .icon {
  width: 1rem;
  height: 1rem;
}

.strip p {
  color: var(--ink-2);
  font-size: 0.9375rem;
}

/* --- FAQ ------------------------------------------------------------------------------------------ */

.faq {
  max-width: 54rem;
  border-top: 1px solid var(--rule-2);
}

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

.faq__q {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding-block: var(--sp-4);
  cursor: pointer;
  list-style: none;
}

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

.faq__q h3 {
  flex: 1;
  font-size: 1.0625rem;
  font-weight: 620;
}

.faq__q:hover h3 {
  color: var(--accent);
}

/* A crosshair that opens to a cross: the same registration mark the rest of the page is marked up
   with, rather than a chevron borrowed from an accordion widget. */
.faq__sign {
  position: relative;
  width: 0.9rem;
  height: 0.9rem;
  flex: none;
  color: var(--accent);
}

.faq__sign::before,
.faq__sign::after {
  content: "";
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 0;
  width: 100%;
  height: 1px;
  background: currentcolor;
  transition: transform 0.18s ease;
}

.faq__sign::after {
  transform: rotate(90deg);
}

.faq__item[open] .faq__sign::before {
  transform: rotate(45deg);
}

.faq__item[open] .faq__sign::after {
  transform: rotate(-45deg);
}

.faq__a {
  max-width: var(--measure);
  padding-bottom: var(--sp-5);
  color: var(--ink-2);
  font-size: 1rem;
}

/* --- contact: the quote sheet and the data sheet beside it --------------------------------------- */

.contact {
  display: grid;
  gap: var(--sp-5);
  align-items: start;
}

@media (width >= 64em) {
  .contact {
    /* The form first and wider: it is what the section is for. */
    grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr);
    gap: var(--sp-6);
  }
}

.datasheet {
  padding: clamp(1.25rem, 3vw, 1.75rem);
  border: 1px solid var(--rule-2);
  background: var(--sheet);
}

.datasheet h2,
.datasheet h3 {
  font-family: var(--mono);
  font-size: 0.9375rem;
  font-weight: 700;
  letter-spacing: 0.16em;
}

.datasheet__lead {
  margin-top: var(--sp-3);
  color: var(--ink-2);
  font-size: 0.9375rem;
}

/* Label and value on ruled rows, the way a control sheet states what is known. */
.rows {
  margin-top: var(--sp-5);
  border-top: 1px solid var(--rule);
}

.rows > div {
  display: grid;
  gap: 0.15rem var(--sp-4);
  padding-block: var(--sp-3);
  border-bottom: 1px solid var(--rule);
}

@media (width >= 26em) {
  .rows > div {
    grid-template-columns: 5rem minmax(0, 1fr);
  }
}

.rows dt {
  color: var(--ink-3);
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  line-height: 1.9;
}

.rows dd {
  color: var(--ink-2);
  font-size: 0.9375rem;
}

.datasheet .btn {
  margin-top: var(--sp-5);
}

.rows dd a {
  font-family: var(--mono);
  font-size: 0.875rem;
}

.tags {
  /* A grid, like .matrix: each wrapped flex line distributed its own leftover space, so the 1px seams
     could not line up between rows. Two tracks divide the four capabilities exactly; an odd last tag
     spans both, because an area no tag covers paints the rule colour. */
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;
  margin-top: var(--sp-5);
  border: 1px solid var(--rule);
  background: var(--rule);
}

.tags li {
  padding: 0.45rem 0.65rem;
  background: var(--sheet);
  color: var(--ink-2);
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.tags li:last-child:nth-child(odd) {
  grid-column: 1 / -1;
}

/* --- quote form ----------------------------------------------------------------------------------- */
/* The markup belongs to the inquiries app -- {% quote_form %} renders it, because the fields, the CSRF
 * token, the honeypot and the anti-spam check only work as a set, and a hand-copy that drops one of
 * them looks fine and rejects every real visitor. What it hands over is plain markup with documented
 * class hooks on it (quote-form, quote-field, quote-field-wide, quote-note, form-label, form-control,
 * req, errorlist, btn); this block is where those become a work order rather than a contact form.
 * Selectors reach for the element as well as the class, so a widget that arrives without its class is
 * still styled.
 */

.quote {
  /* The masthead is sticky and this is an anchor target from every page. */
  scroll-margin-top: 4.5rem;
  padding: clamp(1.25rem, 4vw, 2.25rem);
  border: 1px solid var(--rule-2);
  border-top: 2px solid var(--ink);
  background: var(--sheet);
}

/* Capped but not centred: the band's rule, index and heading start at the shell's left edge, so the
   sheet has to as well, or the head overhangs the box it introduces by up to 128px a side. */
.quote--solo {
  max-width: 56rem;
}

.quote-form {
  display: grid;
  gap: var(--sp-4);
}

@media (width >= 40em) {
  .quote-form {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--sp-4) var(--sp-5);
  }

  .quote-field-wide,
  .quote-form > .errorlist {
    grid-column: 1 / -1;
  }
}

.quote-field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  min-width: 0;
}

.form-label {
  color: var(--ink-2);
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.req {
  color: var(--mark);
  margin-inline-start: 0.2rem;
}

input[type="text"],
input[type="email"],
input[type="tel"],
select,
textarea {
  width: 100%;
  padding: 0.65rem 0.75rem;
  border: 1px solid var(--field);
  border-radius: var(--radius);
  /* Recessed rather than the same white as the sheet it sits on: a field that looks like something to
     fill in is worth more, on the one page that collects the lead, than a flat surface. */
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  /* 1rem, not smaller: iOS zooms the whole page in when a focused input is under 16px. */
  font-size: 1rem;
  line-height: 1.4;
  transition: border-color 0.14s ease, box-shadow 0.14s ease;
}

textarea {
  min-height: 8.5rem;
  resize: vertical;
}

select {
  appearance: none;
  /* The chevron is drawn in the background, so it needs no script and no extra element. */
  background-image: linear-gradient(45deg, transparent 50%, currentcolor 50%),
    linear-gradient(135deg, currentcolor 50%, transparent 50%);
  background-position: calc(100% - 1.15rem) calc(50% - 0.08rem),
    calc(100% - 0.8rem) calc(50% - 0.08rem);
  background-size: 0.36rem 0.36rem, 0.36rem 0.36rem;
  background-repeat: no-repeat;
  padding-inline-end: 2.4rem;
}

input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring);
}

input::placeholder,
textarea::placeholder {
  color: var(--ink-2);
}

/* Django's own error markup, from {{ form.field.errors }} and {{ form.non_field_errors }}. */
.errorlist {
  display: grid;
  gap: 0.2rem;
  padding: var(--sp-3) var(--sp-4);
  border-inline-start: 3px solid var(--danger);
  background: color-mix(in srgb, var(--danger) 8%, var(--sheet));
  color: var(--danger-ink);
  font-size: 0.875rem;
  font-weight: 560;
}

/* The submit row: the last wide field is the only one holding a button, and it also carries the
   anti-spam status line and the note about the confirmation email. */
.quote-field:has(> .btn) {
  margin-top: var(--sp-2);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--rule);
}

.quote-note {
  margin: 0;
  max-width: 48ch;
  color: var(--ink-2);
  font-size: 0.8125rem;
  line-height: 1.45;
}

@media (width >= 40em) {
  .quote-field:has(> .btn) {
    flex-flow: row wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4) var(--sp-5);
  }

  .quote-field:has(> .btn) .quote-note {
    /* Reads left of the button though the markup puts it after: the reassurance belongs beside the
       thing it reassures about, and re-ordering here costs nothing that source order was carrying. */
    order: -1;
    flex: 1 1 18rem;
  }

  .quote-field:has(> .btn) .btn {
    flex: none;
  }
}

/* core/challenge_fields.html's status line. Empty unless the proof of work is slow or has failed, so it
   must not reserve space while it is empty. */
[data-challenge-status] {
  margin: 0;
  color: var(--ink-2);
  font-family: var(--mono);
  font-size: 0.75rem;
}

[data-challenge-status]:empty {
  display: none;
}

/* --- service detail page -------------------------------------------------------------------------- */

.crumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  color: var(--ink-3);
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
}

.crumbs a {
  color: var(--ink-2);
  text-decoration: none;
}

.crumbs a:hover {
  color: var(--accent);
  text-decoration: underline;
}

.detail__icon {
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  margin-top: var(--sp-5);
  border: 1px solid var(--rule-2);
  background: var(--sheet);
  color: var(--accent);
}

.detail__icon .icon {
  width: 1.6rem;
  height: 1.6rem;
}

.detail__body {
  display: grid;
  gap: clamp(2rem, 5vw, 3rem);
}

@media (width >= 60em) {
  .detail__body {
    grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
    align-items: start;
  }
}

/* Each capability gets a cell of its own: on a page whose job is to say what is covered, a ruled matrix
   of them reads as a specification where a bulleted list reads as marketing. */
/* Explicit steps rather than auto-fit: the track count has to divide the capability count, because an
   area no cell covers paints the .matrix rule colour instead of a cell. Two tracks divide 2 and 4, and
   an odd last cell spans both, so the bottom row closes at any count. Below 44em the single column
   .matrix declares is what fills it, the same stop .matrix--services uses. */
@media (width >= 44em) {
  .matrix--specs {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .matrix--specs .cell:last-child:nth-child(odd) {
    grid-column: 1 / -1;
  }
}

.matrix--specs .cell {
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
}

.matrix--specs .cell:hover {
  background: var(--sheet);
}

.matrix--specs .cell::before {
  content: none;
}

.matrix--specs p {
  font-family: var(--mono);
  font-size: 0.875rem;
  line-height: 1.4;
}

/* A ruled list of links: every other service, one to a row. */
.links {
  border-top: 1px solid var(--rule-2);
}

.links a {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding-block: var(--sp-4);
  border-bottom: 1px solid var(--rule-2);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 0.875rem;
  letter-spacing: 0.06em;
  text-decoration: none;
}

.links a .icon {
  width: 1.25rem;
  height: 1.25rem;
  color: var(--accent);
}

.links__go {
  margin-inline-start: auto;
  color: var(--ink-3);
  transition: transform 0.15s ease;
}

.links a:hover {
  color: var(--accent);
}

.links a:hover .links__go {
  color: var(--accent);
  transform: translateX(3px);
}

/* --- message pages (404, and anything else with one thing to say) -------------------------------- */

.notice {
  max-width: 36rem;
  margin-inline: auto;
  padding: clamp(1.5rem, 5vw, 2.5rem);
  border: 1px solid var(--rule-2);
  border-top: 2px solid var(--ink);
  background: var(--sheet);
}

.notice h1 {
  margin-top: var(--sp-3);
  font-size: clamp(1.5rem, 1.2rem + 1.2vw, 2rem);
}

/* :not(.index) for the same reason .section__head's lead rule carries it: 404.html opens with
   <p class="index">404</p>, and a bare `.notice p` at (0,1,1) outranks `.index` at (0,1,0), so the
   survey-orange index rendered as grey body copy with a margin it never asked for. Source order does
   not settle a specificity tie-break, so the guard has to be here rather than further down the sheet. */
.notice p:not(.index) {
  margin-top: var(--sp-4);
  max-width: var(--measure);
  color: var(--ink-2);
}

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

/* --- footer --------------------------------------------------------------------------------------- */

.footer__grid {
  display: grid;
  gap: clamp(2rem, 5vw, 3rem);
  padding-block: clamp(2.5rem, 6vw, 3.5rem);
}

@media (width >= 52em) {
  .footer__grid {
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, 1fr);
  }
}

.footer__brand .wordmark__logo {
  width: 1.75rem;
  height: 1.75rem;
  color: var(--slab-accent);
}

.footer__name {
  margin-top: var(--sp-3);
  font-family: var(--mono);
  font-weight: 700;
  letter-spacing: 0.16em;
}

.footer__tagline {
  margin-top: 0.25rem;
  color: var(--slab-ink-2);
  font-size: 0.9375rem;
}

.footer__area {
  display: flex;
  gap: 0.5rem;
  margin-top: var(--sp-4);
  max-width: 36ch;
  color: var(--slab-ink-2);
  font-family: var(--mono);
  font-size: 0.75rem;
  line-height: 1.6;
}

.footer__area .icon {
  width: 1rem;
  height: 1rem;
  margin-top: 0.15rem;
  color: var(--slab-mark);
}

.footer__heading {
  padding-bottom: var(--sp-3);
  border-bottom: 1px solid var(--slab-rule);
  color: var(--slab-ink-2);
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.footer__links {
  display: grid;
  gap: 0.6rem;
  margin-top: var(--sp-4);
  font-size: 0.9375rem;
}

.footer__links a {
  color: var(--slab-ink);
  text-decoration: none;
}

.footer__links a:hover {
  color: var(--slab-accent);
  text-decoration: underline;
}

.footer__legal {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-5);
  padding-block: var(--sp-4);
  border-top: 1px solid var(--slab-rule);
  color: var(--slab-ink-2);
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
}

/* --- reduced motion -------------------------------------------------------------------------------- */

@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;
  }

  /* These two are movement rather than a change of state, so they are dropped entirely rather than made
     instant: neither says anything the colour change beside it does not. */
  .cell::before,
  .cell:hover::before,
  .cell:focus-within::before {
    transform: none;
    background: transparent;
  }

  .cell:hover .cell__link .icon,
  .links a:hover .links__go {
    transform: none;
  }
}
