/* ==========================================================================
   BN1 Systems - site styles
   Design tokens live in :root. The palette is monochrome: --accent carries every
   interactive state and --rule draws the chevron fold.
   ========================================================================== */

@font-face {
  font-family: 'Schibsted Grotesk';
  src: url('../fonts/schibsted-grotesk.e3b56e90.woff2') format('woff2-variations'),
       url('../fonts/schibsted-grotesk.e3b56e90.woff2') format('woff2');
  font-weight: 400 900;
  font-style: normal;
  font-display: swap;
}
/* Altivo Medium, the display face for every heading. One weight only, so the
   heading rule asks for 500 rather than 700 - anything else and the browser
   synthesises a bold, which smears the geometry. */
@font-face {
  font-family: 'Altivo';
  src: url('../fonts/altivo-medium.76eb1101.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

:root {
  --accent:       #000000;
  --accent-hover: #262626;
  /* The chevron fold sits on a black/white boundary, so the line that traces it
     has to read against both. A mid-grey does; pure black or white cannot. */
  --rule:         #8a8a8a;
  /* Hairlines inside the black services grid. --rule is far too bright for a full
     grid of them; this reads as a seam rather than a drawn line. */
  --hairline:     #2b2b2b;
  --ink:          #000000;
  --paper:        #ffffff;
  /* The hero band. The sticky header's chevron is a white shape; against a white
     hero it reads as a stray line rather than the header's own edge, so the band
     under it is a shade off-white. */
  --paper-2:      #f2f2f2;
  --grey-field:   #f1f1f1;
  --grey-field-2: #e8e8e8;
  --muted:        #6b6b6b;
  --muted-dark:   #a5a5a5;
  --error:        #d1483b;

  --display: 'Altivo', 'Schibsted Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
  --sans:  'Schibsted Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;

  --gutter:   clamp(20px, 8.2vw, 175px);
  --maxw:     2100px;
  --radius:   10px;
  --shell-pad: clamp(56px, 8vw, 130px);
  --chevron-h: clamp(44px, 6.9vw, 150px);
  /* Overlap that keeps a chevron's polygon edge off the opposite colour. See the
     section dividers below - this is load-bearing, not a nudge. */
  --seam: 2px;
}

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
img, svg { display: block; max-width: 100%; }
[hidden] { display: none !important; }
a { color: inherit; }
h1, h2, h3, h4 { margin: 0; font-family: var(--display); font-weight: 500; line-height: 1.06; }
p { margin: 0; }
button { font: inherit; }

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

.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; left: 12px; top: -100px; z-index: 200;
  background: var(--ink); color: #fff; padding: 12px 18px; border-radius: 6px;
  text-decoration: none; transition: top .15s ease;
}
.skip-link:focus { top: 12px; }

/* Altivo has no U+2011, so a non-breaking hyphen in a heading would fall back to
   the body face for that one glyph. A plain hyphen in a nowrap span keeps the
   headline in one face and still refuses to break at "off-the-shelf". */
.nowrap { white-space: nowrap; }

/* currentColor, not --accent: the footer links sit on black, where a black ring
   would be invisible. This inherits black on light sections, white on dark. */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 3px solid currentColor;
  outline-offset: 2px;
  border-radius: 4px;
}

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--sans); font-weight: 500;
  font-size: clamp(12px, .9vw, 14px);
  letter-spacing: .09em; text-transform: uppercase; text-decoration: none;
  padding: 1.15em 2.4em; border: 0; border-radius: 999px;
  cursor: pointer; transition: background-color .18s ease, transform .18s ease, color .18s ease;
}
.btn:active { transform: translateY(1px); }
.btn--accent { background: var(--accent); color: var(--paper); }
.btn--accent:hover { background: var(--accent-hover); }
.btn--dark { background: var(--ink); color: #fff; }
.btn--dark:hover { background: #262626; }
.btn[disabled] { opacity: .55; cursor: not-allowed; transform: none; }

/* ---------- header ---------- */
.site-header {
  position: sticky; top: 0; z-index: 100;
  background: var(--paper);
  transition: box-shadow .2s ease, filter .2s ease;
}
.site-header[data-scrolled='true'] { box-shadow: 0 1px 0 rgba(0,0,0,.09), 0 8px 24px -18px rgba(0,0,0,.35); }

/* Chevron-edged sticky header (home page).
   The bar keeps the white; the SVG below it continues that white down into the
   chevron and leaves everything under the line transparent, so the black section
   - pulled up by --chevron-h - shows through the notch and scrolls beneath it. */
.site-header--chevron { background: transparent; }
/* The chevron header's bottom edge is the notch, not a straight line, so a box-shadow
   would cast a rectangle across it. drop-shadow follows the painted shape instead -
   the bar plus the chevron - so the scroll shadow traces the notch. */
.site-header--chevron[data-scrolled='true'] {
  box-shadow: none;
  filter: drop-shadow(0 1px 0 rgba(0,0,0,.09)) drop-shadow(0 4px 8px rgba(0,0,0,.13));
}
.site-header--chevron .site-header__bar {
  background: var(--paper);
  /* Only the slack between the logo row and the chevron. The logo already sits in
     a min-height row that gives it its own breathing space below, and the chevron
     line starts a little way into the SVG on top of that, so this stays small or
     the mark floats. */
  padding-bottom: clamp(6px, .8vw, 16px);
}
.site-header__chevron { line-height: 0; pointer-events: none; }
.site-header__chevron .chev { display: block; width: 100%; height: var(--chevron-h); }
.site-header__inner {
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  min-height: clamp(74px, 7vw, 104px);
}
.site-header__logo { display: inline-flex; align-items: center; }
.site-header__logo img,
.site-header__logo svg { height: clamp(23px, 2vw, 29px); width: auto; }

/* Sits opposite the wordmark on the same centre line. The mark is a very wide
   lockup - about 10:1 - so below 780px there is no room left beside it and this
   drops out; the footer and the hero button carry the same details. */
.site-header__contact {
  display: flex; align-items: center;
  font-size: clamp(14px, 1.05vw, 17px);
  white-space: nowrap;
}
.site-header__contact a {
  color: var(--ink); text-decoration: none;
  padding: 0 clamp(12px, 1.2vw, 22px);
}
.site-header__contact a:first-child { padding-left: 0; }
.site-header__contact a:last-child { padding-right: 0; }
.site-header__contact a + a { border-left: 1px solid #d8d8d8; }
.site-header__contact a:hover { text-decoration: underline; text-underline-offset: 3px; }

/* ---------- section dividers (the chevrons) ---------- */
.divider { line-height: 0; }
.divider svg { width: 100%; height: var(--chevron-h); display: block; }

/* Every chevron - the header's own edge and both dividers - is drawn by the same
   pair of shapes, so the line that traces the fold is coloured here rather than
   on each inline SVG. var() does not work in a presentation attribute, so the
   markup carries no stroke of its own. */
.chev polyline,
.divider svg polyline { stroke: var(--rule); }

/* ---------- hero ---------- */
/* Slides up under the sticky header's chevron by exactly its height, the same way
   .services does under the divider below. Without this the notch has nothing behind
   it, so it shows the body background and the colour break runs straight across
   instead of following the fold. */
.hero {
  background: var(--paper-2);
  margin-top: calc(var(--chevron-h) * -1 + var(--seam));
}
/* The chevron overlaps the top of this section, so the content clears it first. */
.hero__inner {
  padding-block: calc(var(--chevron-h) + clamp(12px, 1.8vw, 32px)) clamp(56px, 8vw, 132px);
}
.hero__eyebrow {
  font-size: clamp(11px, .82vw, 13px);
  letter-spacing: .17em; text-transform: uppercase; color: var(--muted);
  margin-bottom: clamp(20px, 2.4vw, 34px);
}
.hero__title {
  font-size: clamp(38px, 6.1vw, 96px);
  line-height: 1.02;
  letter-spacing: -.035em;
  max-width: 17ch;
}
.hero__lead {
  margin-top: clamp(22px, 2.4vw, 36px);
  max-width: 58ch;
  color: #3a3a3a;
  font-size: clamp(16px, 1.28vw, 21px);
  line-height: 1.5;
}
.hero__cta { margin-top: clamp(26px, 3vw, 44px); }

/* ---------- hero schematic ----------
   A plain hub-and-spoke diagram of the thing the company actually sells: six
   systems that do not talk to each other, and the layer that makes them.
   Decorative reinforcement of the copy beside it, so it is aria-hidden - the
   labels say nothing the hero and the service cards do not already say.

   Every wire is drawn twice, once as a hairline and once as a pulse on the
   identical path. pathLength="100" rescales each path to the same nominal
   length, so a single dash pattern and a single keyframe drive all six no
   matter how long the real geometry is; only duration and delay differ. */
.hero__diagram { display: none; }

@media (min-width: 1080px) {
  .hero__inner {
    display: grid;
    grid-template-columns: minmax(0, 1.08fr) minmax(0, .92fr);
    gap: clamp(32px, 4vw, 80px);
    align-items: center;
  }
  .hero__diagram {
    /* Capped like the headline's font-size is capped. Uncapped, the diagram keeps
       growing with the column and the labels grow with it - they are inside the
       SVG, so they scale with the whole drawing rather than staying put. */
    display: flex; flex-direction: column; align-items: flex-end;
    width: 100%; max-width: 640px;
    margin-inline-start: auto;
  }
  .hero__schematic { width: 100%; height: auto; }
}

.sch__wire { stroke: #cdcdcd; stroke-width: 1; }
.sch__node { fill: var(--paper); stroke: #cdcdcd; stroke-width: 1; }
.sch__label {
  font-family: var(--sans); font-size: 9.5px; font-weight: 500;
  letter-spacing: .13em; text-transform: uppercase;
  fill: var(--muted); text-anchor: middle;
}
.sch__hub { fill: var(--ink); }
.sch__hub-label {
  font-family: var(--sans); font-size: 10.5px; font-weight: 500;
  letter-spacing: .13em; text-transform: uppercase;
  fill: var(--paper); text-anchor: middle;
}
.sch__pulse {
  stroke: var(--ink); stroke-width: 2.5; stroke-linecap: round;
  stroke-dasharray: 4 96;
  animation: sch-flow var(--dur, 6s) linear infinite;
  /* Negative, always. These stagger the pulses, but a paused animation's clock
     does not advance - so with a positive delay the hidden group sits out its
     whole delay the first time it is switched to, having never counted it down
     while paused. Starting mid-cycle instead means there is no delay to serve. */
  animation-delay: var(--delay, 0s);
}
/* Half the traffic runs the other way, so the diagram reads as an exchange
   rather than six things feeding one thing. */
.sch__pulse--out,
.sch__pulse--rev { animation-direction: reverse; }
/* Before integration, the traffic is the problem rather than the point. */
/* Stepped rather than linear, so the traffic moves in jerks. Same mechanism as
   the after state, read differently: this one is not working. */
.sch__pulse--bad {
  stroke: var(--error);
  stroke-dasharray: 5 95;
  animation-timing-function: steps(14, end);
}
/* The line that goes nowhere ends in a black hole: an accretion ring drawn whole
   behind the event horizon, then its near half redrawn in front, so the ring
   reads as passing around a solid body rather than sitting flat on it. The core
   is opaque and painted after the pulses, so the red pulse on that wire slides
   under it and is swallowed. */
.sch__void-halo { fill: none; stroke: var(--error); stroke-width: 1; opacity: .35; }
.sch__void-disc { fill: none; stroke: var(--error); stroke-width: 2; stroke-linecap: round; }
.sch__void-core { fill: var(--ink); }

/* Everything below scales about the shape's own centre. SVG's default origin is
   the nearest viewport's, which for a nested group is nowhere near the shape, so
   fill-box is not optional here - without it the hole flies off. */
.sch__void-body,
.sch__void-wave { transform-box: fill-box; transform-origin: 50% 50%; }

/* Clicking the hole sets it off. Interactive, and inside an aria-hidden graphic
   - deliberately. It is an easter egg carrying nothing the page does not say in
   words, and it takes no tab stop, so nobody navigating by keyboard or screen
   reader ever arrives at a control that does nothing for them. */
.sch__void-hit { fill: transparent; cursor: pointer; }
/* non-scaling-stroke matters here: the rings are animated by scaling them up,
   and without it the stroke scales too and the shock front dissolves into a
   soft blur instead of staying a line. */
.sch__void-wave {
  fill: none; stroke: var(--error); stroke-width: 1.6; opacity: 0;
  vector-effect: non-scaling-stroke;
}

/* Supernova: collapse, blow out, then reform in the same spot. The body carries
   the collapse and the regrowth; the two rings are the shock front; the core
   goes briefly hot before it goes with them. */
.sch__void.is-nova .sch__void-hit { pointer-events: none; }
.sch__void.is-nova .sch__void-body { animation: sch-nova-body 1.5s cubic-bezier(.3, .7, .4, 1) both; }
.sch__void.is-nova .sch__void-core { animation: sch-nova-core 1.5s linear both; }
.sch__void.is-nova .sch__void-wave { animation: sch-nova-wave .95s cubic-bezier(.1, .8, .3, 1) .08s both; }
.sch__void.is-nova .sch__void-wave--b { animation-duration: 1.15s; animation-delay: .2s; }

@keyframes sch-nova-body {
  0%   { transform: rotate(-24deg) scale(1);    opacity: 1; }
  7%   { transform: rotate(-24deg) scale(.45);  opacity: 1; }
  13%  { transform: rotate(-24deg) scale(2.4);  opacity: .95; }
  28%  { transform: rotate(-24deg) scale(4.8);  opacity: 0; }
  55%  { transform: rotate(-24deg) scale(0);    opacity: 0; }
  78%  { transform: rotate(-24deg) scale(.62);  opacity: 1; }
  90%  { transform: rotate(-24deg) scale(1.12); opacity: 1; }
  100% { transform: rotate(-24deg) scale(1);    opacity: 1; }
}
@keyframes sch-nova-core {
  0%, 6%    { fill: var(--ink); }
  11%       { fill: #ff9a63; }
  24%       { fill: var(--error); }
  32%, 100% { fill: var(--ink); }
}
@keyframes sch-nova-wave {
  0%   { transform: scale(.3);  opacity: 0; }
  10%  { transform: scale(.7);  opacity: 1; }
  100% { transform: scale(5.4); opacity: 0; }
}

/* ---------- the before / after switch ----------
   Two states of the same six systems: hand-placed and wired point to point, one
   line running into nothing; or laid out around the layer that connects them.

   Driven entirely by :checked, with no JS - which is also why the fallback is
   arranged the way it is. Where :has() is unavailable the switch is hidden and
   the after state stands, because a dead control showing the broken diagram for
   ever is the one outcome worth ruling out. */
.dgm { transition: opacity .3s ease; }
.dgm--before { opacity: 0; visibility: hidden; }
.dgm--before .sch__pulse { animation-play-state: paused; }
.dgm-switch { display: none; }

@supports selector(:has(*)) {
  .dgm-switch {
    display: inline-flex; gap: 2px;
    min-inline-size: 0; margin: clamp(16px, 1.6vw, 24px) 0 0; padding: 3px;
    border: 1px solid #cdcdcd; border-radius: 999px;
  }
  .hero__diagram:has(#dgm-before:checked) .dgm--before { opacity: 1; visibility: visible; }
  .hero__diagram:has(#dgm-before:checked) .dgm--after  { opacity: 0; visibility: hidden; }
  /* Whichever group is not on screen stops animating with it. */
  .hero__diagram:has(#dgm-before:checked) .dgm--before .sch__pulse { animation-play-state: running; }
  .hero__diagram:has(#dgm-before:checked) .dgm--after  .sch__pulse { animation-play-state: paused; }
}

.dgm-switch input { position: absolute; opacity: 0; pointer-events: none; }
.dgm-switch label {
  font-size: clamp(10px, .72vw, 11.5px); font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted); cursor: pointer;
  padding: .55em 1.2em; border-radius: 999px;
  transition: background-color .18s ease, color .18s ease;
}
.dgm-switch input:focus-visible + label { outline: 3px solid currentColor; outline-offset: 2px; }
@supports selector(:has(*)) {
  .hero__diagram:has(#dgm-before:checked) label[for='dgm-before'],
  .hero__diagram:has(#dgm-after:checked)  label[for='dgm-after'] {
    background: var(--ink); color: var(--paper);
  }
}
/* Note: a rising dashoffset walks the dash backwards along the path, so a pulse
   with no modifier travels from the end of its `d` to the start. --out and --rev
   flip it to run start to end. */
@keyframes sch-flow {
  from { stroke-dashoffset: 0; }
  to   { stroke-dashoffset: 100; }
}
/* Paused wholesale while the hero is off screen - see site.js. */
.hero__schematic[data-paused='true'] .sch__pulse { animation-play-state: paused; }

@media (prefers-reduced-motion: reduce) {
  /* The global rule collapses the duration, which would strand a dash mid-wire.
     The diagram is still the diagram without them. */
  .sch__pulse { display: none; }
  /* site.js leaves the hole alone here, so nothing happens on click - the cursor
     should not say otherwise. */
  .sch__void-hit { cursor: default; }
}

/* ---------- services ---------- */
.services {
  background: var(--ink); color: #fff;
  margin-top: calc(var(--chevron-h) * -1 + var(--seam));
  padding-block: clamp(64px, 10.2vw, 187px) var(--shell-pad);
}
.services__head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(24px, 4vw, 72px);
  align-items: end;
  margin-bottom: clamp(36px, 5vw, 76px);
}
.services__title {
  font-size: clamp(36px, 5vw, 71px);
  line-height: 1.05;
  letter-spacing: -.058em;
}
.services__lead {
  color: #b9b9b9;
  font-size: clamp(15px, 1.12vw, 19px);
  line-height: 1.55;
  max-width: 54ch;
  padding-bottom: .6em;
}

/* The grid is one object, not six floating boxes: cells share hairline seams
   drawn by the 1px gap letting the wrapper's background through. */
.services__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1px;
  background: var(--hairline);
  border: 1px solid var(--hairline);
}
.card {
  background: var(--ink); color: var(--paper);
  display: flex; flex-direction: column;
  padding: clamp(24px, 2.3vw, 42px);
  min-height: clamp(250px, 19vw, 330px);
  transition: background-color .22s ease, color .22s ease;
}
/* Inverting on hover reuses the palette's only two colours instead of
   introducing a third. Everything inside rides on currentColor, so it follows. */
@media (hover: hover) {
  .card:hover { background: var(--paper); color: var(--ink); }
}
.card:focus-within { background: var(--paper); color: var(--ink); }

.card__index {
  font-size: clamp(11px, .82vw, 13px);
  letter-spacing: .16em; opacity: .45;
}
.card__icon {
  width: clamp(40px, 3.4vw, 54px); height: auto; color: currentColor;
  margin-top: clamp(26px, 2.6vw, 44px);
}
.card__title {
  font-size: clamp(20px, 1.65vw, 28px);
  margin-top: clamp(20px, 2vw, 32px);
  margin-bottom: .55em;
  letter-spacing: -.012em;
}
.card__text { line-height: 1.5; opacity: .74; font-size: clamp(14px, 1.02vw, 16px); }
.card__tags {
  list-style: none; margin: auto 0 0; padding: clamp(22px, 2.2vw, 34px) 0 0;
  display: flex; flex-wrap: wrap; gap: 6px;
}
.card__tags li {
  font-size: clamp(10px, .72vw, 11.5px);
  letter-spacing: .07em; text-transform: uppercase;
  border: 1px solid currentColor; border-radius: 999px;
  padding: .45em .72em; opacity: .5;
  transition: opacity .18s ease;
}
/* Hovering one term pulls it out on every card that carries it. The tags are one
   shared vocabulary, so this is the overlap made visible rather than decoration -
   Specification lights four cards, Migration links EPOS to ERP. Added by site.js;
   without it the tags simply sit at their resting opacity. */
.services__grid.is-linking .card__tags li { opacity: .16; }
.services__grid.is-linking .card__tags li.is-linked { opacity: 1; }

/* ---------- footer ---------- */
.footer {
  position: relative;
  color: #fff;
  background-color: #141414;
  overflow: hidden;
  isolation: isolate;
}
/* animated isometric cube field - the SVG carries its own keyframes */
.footer__pattern {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  pointer-events: none; user-select: none;
}
.footer::after {
  content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 1;
  background: linear-gradient(105deg, rgba(0,0,0,.55) 0%, rgba(0,0,0,.15) 45%, rgba(0,0,0,.5) 100%);
}
/* A chevron is a polygon in the OUTGOING colour, with the incoming section showing
   through the notch beneath it. That polygon's top edge lands exactly on the box
   edge, so whatever sits behind it there is what a stray rasterised row will show -
   and rounding produces stray rows routinely, at fractional scroll offsets and at
   browser zoom levels that headless rendering at scroll 0 never reproduces.

   So the rule for every chevron on the page: the incoming section starts --seam
   BELOW the divider's top edge, and the divider overlaps the outgoing section by
   --seam above it. That puts the outgoing colour - the polygon's own colour -
   behind the polygon's top edge, where a stray row is the same colour as the
   polygon and therefore invisible. Level them up again and the line comes back.

   --seam must stay under 6.6% of --chevron-h: that is how far down the polygon
   still reaches at the far left edge, and the incoming section has to start above
   that point or a sliver opens there instead. At the 44px floor that allows 2.9px. */
.divider--to-dark { position: relative; z-index: 2; margin-top: calc(var(--seam) * -1); }
.divider--to-light { background: transparent; margin-top: calc(var(--seam) * -1); }
.divider--to-footer { position: relative; z-index: 2; margin-top: calc(var(--seam) * -1); }
.footer__inner { position: relative; z-index: 2; padding-block: clamp(52px, 7vw, 116px) clamp(28px, 3vw, 44px); }
.footer__title {
  font-size: clamp(34px, 4.6vw, 64px);
  letter-spacing: -.015em;
  margin-bottom: clamp(20px, 2.4vw, 34px);
}
.footer__contact { display: flex; flex-direction: column; gap: 2px; }
.footer__contact a { text-decoration: none; width: fit-content; }
.footer__contact a:hover { text-decoration: underline; text-underline-offset: 3px; }
.footer__legal { margin-top: clamp(48px, 7vw, 118px); color: #e6e6e6; font-size: .92em; }

/* ---------- contact page ---------- */
.contact {
  background: var(--paper);
  scroll-margin-top: var(--header-h, 260px);
  padding-block: clamp(72px, 8.5vw, 150px) clamp(90px, 10.5vw, 185px);
}
.contact__grid {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1fr);
  gap: clamp(28px, 5vw, 92px);
  align-items: start;
}
.contact__media { position: sticky; top: calc(var(--header-h, 260px) + 24px); }
.contact__media img {
  width: 100%; height: clamp(380px, 74vh, 900px); object-fit: cover; border-radius: 2px; background: var(--grey-field);
}
.contact__intro { margin-bottom: clamp(24px, 3vw, 40px); }
.contact__intro h2 {
  font-size: clamp(28px, 3.4vw, 46px);
  letter-spacing: -.015em;
  margin-bottom: .35em;
}
.contact__intro p { color: var(--muted); max-width: 52ch; }

/* ---------- form ---------- */
.form { display: flex; flex-direction: column; gap: clamp(18px, 2vw, 26px); }
.field { display: flex; flex-direction: column; gap: 8px; }
.field__label, .fieldset__legend {
  font-size: clamp(14px, 1vw, 16px); font-weight: 500; color: var(--ink); padding: 0;
}
.field__label .req, .fieldset__legend .req { color: var(--muted); font-weight: 400; }
.field__group { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(12px, 1.4vw, 20px); }
.field__hint { font-size: .86em; color: var(--muted); }

.input, .select, .textarea {
  width: 100%; font: inherit; color: var(--ink);
  background: var(--grey-field); border: 1px solid transparent;
  border-radius: var(--radius); padding: .85em 1em;
  transition: background-color .15s ease, border-color .15s ease;
}
.input::placeholder, .textarea::placeholder { color: #9a9a9a; }
.input:hover, .select:hover, .textarea:hover { background: var(--grey-field-2); }
.textarea { min-height: 150px; resize: vertical; background: #f7f7f7; border-color: #dcdcdc; }
.select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23111' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 9l7 7 7-7'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 1em center; background-size: 20px;
  padding-right: 3em; cursor: pointer;
}
.input[aria-invalid='true'], .select[aria-invalid='true'], .textarea[aria-invalid='true'] {
  border-color: var(--error); background: #fdf3f2;
}
.field__error { font-size: .86em; color: var(--error); min-height: 0; }
.field__error:empty { display: none; }

/* money input with a fixed £ prefix */
.input-money { display: inline-flex; align-items: center; background: var(--grey-field); border-radius: var(--radius); padding-left: 1em; max-width: 260px; }
.input-money span { color: var(--muted); }
.input-money .input { background: transparent; padding-left: .4em; }
.input-money:focus-within { outline: 3px solid currentColor; outline-offset: 2px; }
.input-money .input:focus-visible { outline: none; }

fieldset { border: 0; margin: 0; padding: 0; }
/* Six options stack into a tall column in a half-width form; let them pair up
   whenever the column is wide enough for two. */
.choices { display: grid; grid-template-columns: repeat(auto-fit, minmax(215px, 1fr));
           gap: 12px 20px; margin-top: 12px; }
.choice { display: flex; align-items: center; gap: 12px; cursor: pointer; width: fit-content; }
.choice input {
  appearance: none; margin: 0; flex: none;
  width: 20px; height: 20px; border: 1.5px solid #b6b6b6; border-radius: 5px;
  background: #fff; cursor: pointer; display: grid; place-content: center;
  transition: background-color .12s ease, border-color .12s ease;
}
.choice input::after {
  content: ''; width: 11px; height: 11px; transform: scale(0);
  transition: transform .12s ease-in-out;
  background: var(--paper);
  clip-path: polygon(14% 44%, 0 60%, 39% 100%, 100% 18%, 84% 3%, 38% 68%);
}
.choice input:checked { background: var(--accent); border-color: var(--accent); }
.choice input:checked::after { transform: scale(1); }
.choice:hover input:not(:checked) { border-color: #8a8a8a; }

.form__actions { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; margin-top: 6px; }
.form__status { font-size: .95em; }
.form__status[data-state='error']   { color: var(--error); }
.form__status[data-state='success'] { color: #1d7a33; }
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.form-success {
  border: 1px solid var(--accent); background: #f7f7f7;
  border-radius: var(--radius);
  padding: clamp(32px, 4vw, 56px) clamp(28px, 3.5vw, 48px);
}
.form-success h2 { font-size: clamp(22px, 2vw, 26px); margin-bottom: .6em; line-height: 1.2; }
.form-success p { color: #333; max-width: 54ch; }

/* ---------- responsive ---------- */
@media (max-width: 1080px) {
  .services__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .card { min-height: 260px; }
}
@media (max-width: 900px) {
  .services__head { grid-template-columns: minmax(0, 1fr); align-items: start; }
  .services__lead { padding-bottom: 0; }
}
@media (max-width: 900px) {
  .contact__grid { grid-template-columns: minmax(0, 1fr); }
  .contact__media { position: static; order: -1; }
  .contact__media img { height: clamp(240px, 46vw, 420px); }
}
/* The wordmark is a ~10:1 lockup, so it eats the bar on its own well before the
   viewport gets narrow. Past this there is no room opposite it. */
@media (max-width: 780px) {
  .site-header__contact { display: none; }
}
@media (max-width: 620px) {
  .services__grid { grid-template-columns: minmax(0, 1fr); }
  .card { min-height: 0; }
  .field__group { grid-template-columns: minmax(0, 1fr); }
  .site-header__inner { gap: 12px; }
  .btn { padding: 1.05em 1.5em; letter-spacing: .06em; }
}
@media (max-width: 380px) {
  .btn { padding: 1em 1.15em; font-size: 11px; }
  .site-header__logo img, .site-header__logo svg { height: 21px; }
}
