/* ==========================================================================
   Node9 LLC — "slate, set properly"

   The board is the surface, not the theme. Chalk is spent deliberately and
   sparingly: the logo, the section rules, the label dash and stage ticks, the
   frame around the director's card, and the architecture sketch. Everything
   that rules a grid stays a geometric hairline — the restraint of that grid is
   what makes a chalkboard read as an engineering firm rather than a café menu,
   and the drawn marks only mean something because they are outnumbered.

   No heading is set in a handwriting face. Source Serif 4 does the display
   work, Inter does everything a client actually has to read, and nothing on
   the page is written by hand below 15px.
   ========================================================================== */

:root {
  /* --- board ---
     Only the fallback under the gradient stack; it is never visible, because
     every stop in the ramp below is opaque. The board's real tone lives in
     --slate and its dials, and it is NOT the brief's nominal palette: those
     values are what the photograph's pixels read before the three grain layers
     go over them, and overlay blending on a dark base lifts the result by
     6-8 luma. The numbers that matter are the RENDERED corners, sampled from
     a real 1440x900 and 390x844 capture against reference-board.jpg:

                        UL     UR     LL     LR
       photograph      49.5   31.2   27.3   22.7
       rendered 1440   49.3   32.0   28.7   22.9    (corner RMS 0.82)
       rendered  390   49.5   30.2   27.3   23.3    (corner RMS 0.58)

     Re-measure, do not re-derive, if any of this is touched. */
  --board-base: #1f2125;

  /* --- chalk --- */
  --chalk:      #f2f3f0;   /* headings, the few bright things */
  --chalk-dim:  #cdd0cb;   /* ALL running copy. The brief's own measured value.
                              An earlier pass ran service, process, technology
                              and checklist copy at #b9bdb8/14.5px, which
                              computes fine on paper and reads as a whisper on
                              a grained dark surface at 1440 — worse on a
                              laptop at half brightness. There is now one body
                              colour and its floor is 15px. */
  --chalk-mute: #b1b6b1;   /* labels, numerals, meta — measured off the render
                              at 4.5:1+ against the LIGHTEST patch of the board,
                              not just its average */

  /* --- the one accent: the blue of the 9 --- */
  --blue:    #80a6c6;      /* rules and ticks only — decorative */
  --blue-br: #9dc0dc;      /* anything blue that is actually read */

  /* --- hairlines ---
     The ruled grids stay hairlines: they are the typographic system, and a
     wobbly line under six service cards would be noise, not craft. */
  --rule:      rgba(226, 232, 236, .13);
  --rule-soft: rgba(226, 232, 236, .075);

  /* --- drawn strokes ---
     The three places a line is a GESTURE rather than a grid: the section
     rules, the label dash, the stage ticks. Turbulence-displaced paths with a
     faint under-stroke and end ticks, so they were drawn, not drafted.
     Everything else on the page stays geometric on purpose.
     The two long rules carry preserveAspectRatio='none': consumed at
     `background-size: 100% 100%` the default xMidYMid *meet* letterboxes them,
     scaling a 1200x12 stroke down to fit a 10px-tall box and centring it — the
     footer rule was rendering 1000px wide inside a 1240px slot with 120px of
     nothing at each end. The strokes are near-horizontal, so stretching them
     costs no visible weight. */
  --rule-img:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1200' height='12' viewBox='0 0 1200 12' preserveAspectRatio='none'%3E%3Cfilter id='c' x='-2%25' y='-70%25' width='104%25' height='240%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.5' numOctaves='2' seed='9' result='n'/%3E%3CfeDisplacementMap in='SourceGraphic' in2='n' scale='2'/%3E%3C/filter%3E%3Cg filter='url(%23c)' fill='none' stroke='%23e2e8ec' stroke-linecap='round'%3E%3Cpath d='M6 6.6C210 3.6 420 8.6 640 5.4 840 2.5 1010 8.2 1194 5' stroke-width='2.1' opacity='.34'/%3E%3Cpath d='M150 8.8C360 6.6 560 10 800 7.2' stroke-width='1.2' opacity='.15'/%3E%3Cpath d='M66 3 54 8.6' stroke-width='1.3' opacity='.17'/%3E%3Cpath d='M1136 8.4 1150 3.2' stroke-width='1.3' opacity='.17'/%3E%3C/g%3E%3C/svg%3E");

  --rule-sm-img:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='10' viewBox='0 0 300 10' preserveAspectRatio='none'%3E%3Cfilter id='c' x='-4%25' y='-70%25' width='108%25' height='240%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.7' numOctaves='2' seed='14' result='n'/%3E%3CfeDisplacementMap in='SourceGraphic' in2='n' scale='1.8'/%3E%3C/filter%3E%3Cpath d='M4 5.6C70 3.4 150 7.4 220 4.8 250 3.7 274 6.2 296 4.4' filter='url(%23c)' fill='none' stroke='%23e2e8ec' stroke-width='1.9' stroke-linecap='round' opacity='.26'/%3E%3C/svg%3E");

  --dash-img:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='10' viewBox='0 0 30 10'%3E%3Cfilter id='c' x='-20%25' y='-60%25' width='140%25' height='220%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' seed='6' result='n'/%3E%3CfeDisplacementMap in='SourceGraphic' in2='n' scale='1.6'/%3E%3C/filter%3E%3Cpath d='M3 6.4C10 4.6 19 6.6 27 3.8' filter='url(%23c)' fill='none' stroke='%2380a6c6' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");

  --tick-img:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='34' height='10' viewBox='0 0 34 10'%3E%3Cfilter id='c' x='-16%25' y='-60%25' width='132%25' height='220%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='2' seed='12' result='n'/%3E%3CfeDisplacementMap in='SourceGraphic' in2='n' scale='1.5'/%3E%3C/filter%3E%3Cpath d='M2 5.2C11 3.4 21 6.4 32 4.4' filter='url(%23c)' fill='none' stroke='%2380a6c6' stroke-width='2.1' stroke-linecap='round'/%3E%3C/svg%3E");

  /* --- the slate's tone dials ---
     The gradients below are written once and never duplicated; these six
     numbers are the only things a breakpoint is allowed to retune. That
     matters because the two structural pieces of the falloff — the box-shadow
     frame and the corner radial — are specified in ABSOLUTE px and % of a box
     whose aspect ratio changes by 2x between a 1440x900 desktop and a 390x844
     phone. Left at one setting the phone board came out flat: the lit corner
     6.5 luma too dark and the shadowed corner 8.9 too light, i.e. no lamp at
     all. Every value here was measured against reference-board.jpg by corner
     sampling, at both viewports, not eyeballed. */
  --lit-1:  .076;   /* the lamp, at its centre */
  --lit-2:  .028;   /*   ... and its shoulder */
  --bounce: .026;   /* secondary fill, keeps the middle from going flat */
  --fall-1: .36;    /* the fall-off into the lower right */
  --fall-2: .20;
  --vig-1:  .32;    /* frame darkening */
  --vig-2:  .22;
  --vig-blur: 190px;
  --vig-spread: 26px;

  /* --- the slate, defined exactly ONCE ---
     Held in a variable so the fixed board, the document-sized canvas, the
     header strip and the mobile drawer all consume the identical surface.
     Retuning the board means editing one place; drift between the surfaces is
     structurally impossible, which is the whole of the client's brief.
     Every consumer is a `background-size: 100% 100%` box, so the gradients
     reproduce over whatever box they are given. */
  --slate:
    /* the light — off-centre, upper left. This is a POOL, not a ramp: it is
       what makes the board read as a lamp on a wall rather than a bevel. */
    radial-gradient(96% 80% at 16% 9%,
      rgba(158, 174, 194, var(--lit-1)) 0%,
      rgba(146, 162, 182, var(--lit-2)) 30%,
      rgba(0, 0, 0, 0) 58%),
    /* a soft secondary bounce, keeps the middle from going flat */
    radial-gradient(64% 50% at 62% 26%,
      rgba(146, 160, 180, var(--bounce)) 0%,
      rgba(0, 0, 0, 0) 60%),
    /* fall-off into the lower right. Measured against reference-board.jpg:
       the photograph's darkest corner is #16171a, NOT black — the board keeps
       being a material all the way into the corner. */
    radial-gradient(112% 104% at 96% 108%,
      rgba(10, 11, 14, var(--fall-1)) 0%,
      rgba(13, 14, 17, var(--fall-2)) 34%,
      rgba(0, 0, 0, 0) 70%),
    /* base tone shift across the slab */
    linear-gradient(152deg,
      #1f2125 0%,
      #1b1d21 32%,
      #18191d 64%,
      #17181c 85%,
      #17181c 100%);

  /* Alpha-carrying tooth for the CANVAS copy of the slate. The board's own
     tiles are opaque grey driven through mix-blend-mode, which a background on
     `html` cannot do; these carry the same structure in the alpha channel so
     they composite normally. Both are pushed through a zeroed colour matrix,
     so — unlike raw feTurbulence — they emit luminance, never chroma. */
  --canvas-grain:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='d' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' seed='7' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.20 0.20 0.20 0 -0.20'/%3E%3C/filter%3E%3Cfilter id='l' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' seed='19' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0.15 0.15 0.15 0 -0.22'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23d)'/%3E%3Crect width='200' height='200' filter='url(%23l)'/%3E%3C/svg%3E");

  --canvas-tooth:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='260'%3E%3Cfilter id='d' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.28' numOctaves='3' seed='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0.15 0.15 0 -0.17'/%3E%3C/filter%3E%3Cfilter id='l' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.28' numOctaves='3' seed='23' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0.11 0.11 0.11 0 -0.19'/%3E%3C/filter%3E%3Crect width='260' height='260' filter='url(%23d)'/%3E%3Crect width='260' height='260' filter='url(%23l)'/%3E%3C/svg%3E");

  /* --- type --- */
  --f-sans: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --f-serif: 'Source Serif 4', 'Iowan Old Style', 'Palatino Linotype', Georgia, serif;

  /* --- measure --- */
  --wrap: 1240px;
  --pad: clamp(20px, 5vw, 64px);
  --col-label: 226px;
  --gap-col: clamp(28px, 4.4vw, 76px);
  --nav-h: 94px;   /* sized around the logo, not the other way round */

  --ease: cubic-bezier(.22, .7, .25, 1);
}

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

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

html {
  /* THE CANVAS CARRIES A SECOND, NON-FIXED PAINTING OF THE SAME SLATE, sized
     to the whole document. It is never seen while browsing — the fixed .board
     is opaque and sits on top of it — but it matters in the two places a fixed
     board cannot reach, and both are places the client will look:

       - a rubber-band overscroll at the foot of the page. A single flat value
         behind a board that runs #292b31 to #17181c is exactly the "rectangle
         of a slightly different dark" the brief is written against;
       - a full-page screenshot. Several capture paths (Chrome's capture-full-
         size, Playwright fullPage) paint a position:fixed layer once and leave
         the rest of the document flat — which is how a page like this is most
         often shown to a client.

     Sized 100% 100%, so it is one continuous wash over the full document
     height with no tiling seam; the two tooth tiles repeat over it.
     background-color is the ramp's DARK end, because the only overscroll a
     long page actually gets is the bounce at its foot. */
  background-color: #17181c;
  background-image: var(--canvas-grain), var(--canvas-tooth), var(--slate);
  background-size: 200px 200px, 260px 260px, 100% 100%, 100% 100%, 100% 100%, 100% 100%;
  background-repeat: repeat, repeat, no-repeat, no-repeat, no-repeat, no-repeat;
  background-position: 0 0;
  /* and where it is honoured, there is no bounce to paint at all */
  overscroll-behavior-y: none;
  overflow-x: clip;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + 26px);
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: transparent;
  color: var(--chalk-dim);
  font-family: var(--f-sans);
  font-size: 15.5px;
  line-height: 1.65;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, p, dl, dd, dt, figure, ul, ol { margin: 0; }
ul, ol { padding: 0; list-style: none; }
img { display: block; max-width: 100%; height: auto; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
a { color: inherit; }

:focus-visible {
  outline: 2px solid var(--blue-br);
  outline-offset: 3px;
  border-radius: 1px;
}

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

.skip {
  position: fixed; top: 0; left: -9999px; z-index: 120;
  font-size: 14px; text-decoration: none;
}
.skip:focus {
  left: 16px; top: 16px;
  padding: 12px 20px;
  color: var(--chalk);
  background-color: #1a1c20;
  border: 1px solid var(--blue-br);
  border-radius: 2px;
}

/* ==========================================================================
   THE BOARD
   One fixed slate: base light + fall-off, dusty wipe arcs, two grains,
   an edge vignette. Nothing on the page paints an opaque background of its
   own — and the two surfaces that MUST sit above scrolling content (the
   header strip, the mobile drawer) repaint this same element from the same
   --slate token, fixed to the same viewport box and masked or clipped to
   their own strip. Same geometry, same tile phase, same blend: they are not
   copies that could drift, they are the surface.
   ========================================================================== */

.board {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  isolation: isolate;
  background-color: var(--board-base);
  background-image: var(--slate);
  background-size: 100% 100%;
}

.board > i { display: block; position: absolute; }

/* frame darkening + the faint dust that always settles along the bottom rail.
   Measured, not eyeballed: the photograph's lower-right corner sits at #16171a
   (luma 22.7), so this vignette stacks on a ramp that must not already be
   black. Halved from the first pass, which crushed the bottom-right eighth of
   every viewport out of being a material at all. */
.board::after {
  content: "";
  position: absolute;
  inset: 0;
  box-shadow:
    inset 0 0 var(--vig-blur) var(--vig-spread) rgba(8, 9, 11, var(--vig-1)),
    inset 0 0 62px rgba(8, 9, 11, var(--vig-2));
  background-image:
    radial-gradient(130% 20% at 50% 103%, rgba(226, 232, 236, .045), rgba(0, 0, 0, 0) 70%);
}

/* Broad wipe arcs — a board is cleaned in long sweeps. The soft ellipses
   below give the sweep its SHAPE; the mask breaks them into chalk dust so
   they read as residue on stone rather than as airbrushed smoke. */
.board__wipes {
  inset: -30%;
  transform: rotate(-7deg);
  background-image:
    radial-gradient(44% 5.0% at 26% 17%, rgba(226, 232, 236, 0.150), rgba(0, 0, 0, 0) 72%),
    radial-gradient(52% 4.2% at 69% 30%, rgba(226, 232, 236, 0.110), rgba(0, 0, 0, 0) 74%),
    radial-gradient(36% 6.4% at 44% 51%, rgba(226, 232, 236, 0.126), rgba(0, 0, 0, 0) 70%),
    radial-gradient(56% 3.8% at 33% 67%, rgba(222, 229, 234, 0.088), rgba(0, 0, 0, 0) 76%),
    radial-gradient(32% 5.6% at 77% 78%, rgba(222, 229, 234, 0.082), rgba(0, 0, 0, 0) 72%),
    radial-gradient(40% 4.6% at 12% 88%, rgba(210, 219, 226, 0.070), rgba(0, 0, 0, 0) 74%);
  -webkit-mask-repeat: repeat;
          mask-repeat: repeat;
  -webkit-mask-size: 900px 620px;
          mask-size: 900px 620px;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='900' height='620'%3E%3Cfilter id='w' x='0' y='0' width='100%25' height='100%25' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.0056 0.021' numOctaves='4' seed='9' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 1.35 1 0 0 -0.72'/%3E%3C/filter%3E%3Crect width='900' height='620' filter='url(%23w)'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='900' height='620'%3E%3Cfilter id='w' x='0' y='0' width='100%25' height='100%25' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.0056 0.021' numOctaves='4' seed='9' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 1.35 1 0 0 -0.72'/%3E%3C/filter%3E%3Crect width='900' height='620' filter='url(%23w)'/%3E%3C/svg%3E");
}

/* a second, crossing set — real wipes are not all parallel.
   It inherits the parent's dust mask, which cross-hatches the two. */
.board__wipes::after {
  content: "";
  position: absolute;
  inset: -10%;
  transform: rotate(13deg);
  background-image:
    radial-gradient(48% 3.8% at 55% 22%, rgba(226, 232, 236, 0.078), rgba(0, 0, 0, 0) 76%),
    radial-gradient(42% 4.6% at 22% 60%, rgba(226, 232, 236, 0.066), rgba(0, 0, 0, 0) 74%),
    radial-gradient(46% 3.4% at 70% 86%, rgba(214, 223, 230, 0.056), rgba(0, 0, 0, 0) 78%);
}

/* mid-scale mottling — the slate is not one flat pigment.
   Kept very low: any higher and it reads as smoke, not stone. */
.board__mottle {
  inset: 0;
  opacity: .17;
  mix-blend-mode: soft-light;
  background-repeat: repeat;
  background-size: 500px 500px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='500' height='500'%3E%3Cfilter id='m' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.024' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='500' height='500' filter='url(%23m)'/%3E%3C/svg%3E");
}

/* the coarse tooth — the speckle you actually see on slate at arm's length */
.board__tooth {
  inset: 0;
  opacity: .36;
  mix-blend-mode: overlay;
  background-repeat: repeat;
  background-size: 260px 260px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='260'%3E%3Cfilter id='c' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.28' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='260' height='260' filter='url(%23c)'/%3E%3C/svg%3E");
}

/* the fine grain — random, so it tiles with no seam at any size */
.board__grain {
  inset: 0;
  opacity: .40;
  mix-blend-mode: overlay;
  background-repeat: repeat;
  background-size: 200px 200px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='t' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23t)'/%3E%3C/svg%3E");
}

/* The header strip: the same board — but MASKED, not clipped. A hard clip
   ends in an edge, and an edge on a continuous surface is a seam by another
   name; it also slices whatever line of text happens to be crossing it. A 44px
   fade means content passing under the header dissolves INTO the slate instead
   of being cut off by it. That fade is also why there is no rule under the
   header: it used to double up with the first section hairline scrolling
   towards it and read as a mistake. */
.board--nav {
  -webkit-mask-image: linear-gradient(180deg,
    #000 0,
    #000 calc(var(--nav-h) - 10px),
    rgba(0, 0, 0, .55) calc(var(--nav-h) + 10px),
    transparent calc(var(--nav-h) + 34px));
          mask-image: linear-gradient(180deg,
    #000 0,
    #000 calc(var(--nav-h) - 10px),
    rgba(0, 0, 0, .55) calc(var(--nav-h) + 10px),
    transparent calc(var(--nav-h) + 34px));
}

/* the mobile menu: the same board, clipped to everything below the bar.
   Only ever painted while the menu is actually open. */
.board--menu { display: none; clip-path: inset(var(--nav-h) 0 0 0); }

/* ==========================================================================
   GRID
   A book margin down the whole page: label hangs left, everything else
   starts on one hard left edge.
   ========================================================================== */

.wrap {
  width: min(var(--wrap), 100% - var(--pad) * 2);
  margin-inline: auto;
}

.sec {
  display: grid;
  grid-template-columns: minmax(0, var(--col-label)) minmax(0, 1fr);
  column-gap: var(--gap-col);
  align-items: start;
}

.sec__main { min-width: 0; }

.section { padding-block: clamp(74px, 8.5vw, 132px); }

/* ---------- the marginal label + its single accent dash ---------- */

.label {
  position: sticky;
  top: calc(var(--nav-h) + 34px);
  display: flex;
  align-items: baseline;
  gap: 13px;
  font-size: 12px;
  font-weight: 500;
  line-height: 1.6;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--chalk-mute);
  padding-top: .8em;
  text-wrap: balance;
}

/* the accent dash: a stick-stroke, not a 1px box */
.dash {
  flex: 0 0 auto;
  width: 24px;
  height: 9px;
  background: var(--dash-img) no-repeat center / 100% 100%;
  transform: translateY(-.30em);
}

/* ---------- rules ----------
   These three — the hero rule, the footer rule, the rule inside the director
   card — are the page's only DRAWN lines. Everything that rules a grid stays a
   1px hairline: six wobbly lines under six service cards would be costume, and
   the restraint of that grid is the reason the page reads as an engineering
   firm. But a section rule is a gesture, and a perfect 1px line drawn on stone
   is the one thing on this page that could not have been made by hand. */

.rule {
  height: 10px;
  border: 0;
  background: var(--rule-img) no-repeat center / 100% 100%;
}

.rule--soft {
  height: 9px;
  background: var(--rule-sm-img) no-repeat center / 100% 100%;
  margin-block: 20px;
}

/* ==========================================================================
   TYPE
   Source Serif 4 for display (optical size does the work at 80px),
   Inter for everything a client actually has to read.
   Line heights carry Azerbaijani: ə ğ ı İ ü ö ş ç need the vertical room.
   ========================================================================== */

h1 {
  font-family: var(--f-serif);
  font-weight: 400;
  font-size: clamp(2.2rem, 6vw, 4.85rem);
  line-height: 1.07;
  letter-spacing: -.022em;
  color: var(--chalk);
  text-shadow: 0 0 26px rgba(196, 214, 228, .13), 0 1px 1px rgba(0, 0, 0, .3);
}

.h1__a, .h1__b { display: block; }
.h1__b {
  font-style: italic;
  font-weight: 400;
  color: #dde0db;
}

h2 {
  font-family: var(--f-serif);
  font-weight: 400;
  font-size: clamp(1.85rem, 3.15vw, 2.9rem);
  line-height: 1.16;
  letter-spacing: -.017em;
  color: var(--chalk);
  /* 21ch dropped 'a vendor' alone onto line two of the page's most quotable
     heading. Balance decides the break instead of the measure doing it. */
  max-width: 25ch;
  text-wrap: balance;
  text-shadow: 0 0 22px rgba(196, 214, 228, .10), 0 1px 1px rgba(0, 0, 0, .28);
}

.sec__sub {
  margin-top: clamp(16px, 1.7vw, 22px);
  max-width: 54ch;
  font-size: clamp(15.5px, 1.15vw, 17px);
  line-height: 1.62;
  color: var(--chalk-dim);
}

.lead {
  margin-top: clamp(22px, 2.4vw, 30px);
  max-width: 50ch;
  font-size: clamp(16px, 1.25vw, 17.5px);
  line-height: 1.64;
  color: var(--chalk-dim);
}

.body {
  font-size: 15.5px;
  line-height: 1.72;
  color: var(--chalk-dim);
}
.body + .body { margin-top: 1.1em; }

/* ==========================================================================
   NAV
   No panel, no blur, no tint. The bar is a clipped repaint of the fixed
   board, so content slides behind the same slate it was sitting on.
   The only thing that appears on scroll is a chalk hairline — the same
   hairline the rest of the page is ruled with.
   ========================================================================== */

.nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 50;
}

.nav__inner {
  display: flex;
  align-items: center;
  gap: clamp(14px, 2.4vw, 34px);
  height: var(--nav-h);
}

/* The cut-outs carry a wide transparent border (ink starts 21.4% in from the
   left, 19% down). Every placement below cancels that padding with negative
   margins, so the chalk itself — not the PNG frame — lands on the grid, and
   the stray dust is free to drift past the column edge. Both cuts share the
   identical crop, so one set of fractions serves every placement.

   The header and footer draw from chalk-logo-nav (260x196), NOT from the
   1400px hero file. The lockup is an OUTLINE drawing: downsampling it by 20x
   merges the two strokes of every letter and "node" becomes a grey smudge.
   The nav cut was dilated and hardened in the alpha channel before resampling,
   so it survives the size. No brightness/contrast filter is used to rescue it
   — a filter on a chalk drawing bleaches the dust and re-creates the pasted-on
   look the whole page exists to avoid. */
.brand { position: relative; display: block; line-height: 0; flex: 0 0 auto; }
.brand__img {
  --b-h: 76px;
  height: var(--b-h);
  width: auto;
  /* The negative margins below shrink this img's MARGIN box, which is what
     sizes the shrink-to-fit .brand around it — and the global
     `img { max-width: 100% }` then resolves against that smaller box and
     squeezes the picture horizontally. The mark was rendering at 72x76 in a
     260x196 frame: not merely small, ANAMORPHIC, which is most of why "node"
     read as a smudge rather than a wordmark. Same fix in the footer. */
  max-width: none;
  margin-left: calc(var(--b-h) * -0.2845);
  margin-right: calc(var(--b-h) * -0.09);
}

/* the faintest possible dust — the mark still has to sit ON the board here,
   but at header scale A's hero-strength bloom would read as a glow */
.brand::before,
.foot__brand picture::before {
  content: "";
  position: absolute;
  z-index: -1;
  pointer-events: none;
  inset: -34% -16%;
  background-image: radial-gradient(52% 50% at 48% 46%,
    rgba(255, 255, 255, .030) 0%,
    rgba(255, 255, 255, .011) 46%,
    rgba(255, 255, 255, 0) 74%);
}

.nav__links {
  display: flex;
  align-items: center;
  margin-left: auto;
}
.nav__links-in {
  display: flex;
  align-items: center;
  gap: clamp(16px, 2.1vw, 30px);
}

.nav__links a {
  position: relative;
  font-size: 13.5px;
  font-weight: 450;
  letter-spacing: .012em;
  color: var(--chalk-dim);
  text-decoration: none;
  padding-block: 6px;
  transition: color .25s var(--ease);
}
.nav__links a::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background-color: var(--blue);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .32s var(--ease);
}
.nav__links a:hover { color: var(--chalk); }
.nav__links a:hover::after { transform: scaleX(1); }

.nav__links .nav__cta-m { display: none; }

.nav__actions {
  display: flex;
  align-items: center;
  gap: clamp(12px, 1.6vw, 22px);
  flex: 0 0 auto;
}

/* The control a bilingual audience uses most, and the one that was 22x29 CSS
   px — under the WCAG 2.2 AA 24x24 target minimum on width. */
.lang { display: flex; align-items: center; gap: 2px; }
.lang__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  min-width: 40px;
  padding: 8px 12px;
  border-radius: 2px;
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: .13em;
  color: var(--chalk-mute);
  transition: color .25s var(--ease);
}
.lang__btn:hover { color: var(--chalk-dim); }
.lang__btn.is-active { color: var(--chalk); }
.lang__sep { width: 1px; height: 11px; background-color: rgba(226, 232, 236, .22); }

.burger {
  display: none;
  width: 44px; height: 44px;   /* WCAG 2.2 AA target size, with room over */
  place-items: center;
  border: 1px solid var(--rule);
  border-radius: 2px;
}
.burger__bars { display: block; width: 15px; }
.burger__bars i {
  display: block;
  height: 1px;
  background-color: var(--chalk-dim);
  transition: transform .3s var(--ease);
}
.burger__bars i + i { margin-top: 5px; }
.burger[aria-expanded="true"] .burger__bars i:first-child { transform: translateY(3px) rotate(45deg); }
.burger[aria-expanded="true"] .burger__bars i:last-child { transform: translateY(-3px) rotate(-45deg); }

/* ==========================================================================
   BUTTONS
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  font-size: 13.5px;
  font-weight: 500;
  letter-spacing: .022em;
  line-height: 1;
  text-decoration: none;
  transition: border-color .3s var(--ease), background-color .3s var(--ease), color .3s var(--ease);
}

.btn--line {
  padding: 15px 26px;
  color: var(--chalk);
  border: 1px solid rgba(226, 232, 236, .30);
  border-radius: 2px;
  background-color: rgba(232, 238, 242, .028);
}
.btn--line:hover {
  border-color: rgba(226, 232, 236, .60);
  background-color: rgba(232, 238, 242, .085);
}

.btn--sm { padding: 11px 18px; font-size: 13px; }
.btn--block { display: flex; width: 100%; }

.btn--text {
  padding: 15px 0 12px;
  color: var(--chalk-dim);
  border-bottom: 1px solid rgba(226, 232, 236, .24);
}
/* The `/ ""` is the alternative-text half of `content`: it keeps the arrow
   visible but empties it for assistive tech, which otherwise folds generated
   content into the link's accessible name ("What we do →"). A pseudo-element
   cannot be aria-hidden, so this is the only way to say it. Older engines
   ignore the alt and simply behave as before. */
.btn--text::after {
  content: "\2009\2192" / "";
  transition: transform .3s var(--ease);
}
.btn--text:hover { color: var(--chalk); border-bottom-color: var(--blue); }
.btn--text:hover::after { transform: translateX(4px); }

/* ==========================================================================
   HERO
   Mostly empty board. The logo is drawn at the top of it, at the same left
   edge every heading on the page uses.
   ========================================================================== */

.hero {
  padding-top: clamp(118px, 15vh, 186px);
  padding-bottom: clamp(54px, 6.5vw, 92px);
}

.hero__eyebrow { top: calc(var(--nav-h) + 44px); }

.hero__mark {
  position: relative;
  --mark-w: clamp(310px, 42vw, 560px);
  margin-bottom: clamp(24px, 3vw, 40px);
}

/* THE CHALK DUST THE LOGO WAS DRAWN IN.
   Look at reference-board.jpg: the drawing does not sit on clean slate. There
   is a broad, soft bloom of dust on the board all around it, from the side of
   the hand and from the stick itself. Without it a perfectly keyed PNG is the
   crispest object on the page no matter how well the board is matched — which
   is the second half of the client's sentence, and the one thing this page was
   still failing. Sized off --mark-w rather than the element box, because the
   element is a full-width block and the ink is only part of it. */
.hero__mark::before {
  content: "";
  position: absolute;
  z-index: -1;
  pointer-events: none;
  left: calc(var(--mark-w) * -0.20);
  top: calc(var(--mark-w) * -0.17);
  width: calc(var(--mark-w) * 1.20);
  height: calc(var(--mark-w) * 1.06);
  background-image: radial-gradient(54% 50% at 46% 45%,
    rgba(255, 255, 255, .055) 0%,
    rgba(255, 255, 255, .018) 46%,
    rgba(255, 255, 255, 0) 74%);
}

.hero__mark img {
  width: var(--mark-w);
  margin-left: calc(var(--mark-w) * -0.214);
  margin-top: calc(var(--mark-w) * -0.085);
  margin-bottom: calc(var(--mark-w) * -0.125);
}

.cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(16px, 2.2vw, 30px);
  margin-top: clamp(30px, 3.4vw, 44px);
}

.hero__rule { margin-top: clamp(56px, 7vw, 96px); }

.stats {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  column-gap: 0;
  margin-top: clamp(26px, 2.8vw, 36px);
}

.stat {
  padding-left: clamp(16px, 2.4vw, 32px);
  padding-right: clamp(14px, 2.2vw, 30px);
  border-left: 1px solid var(--rule-soft);
}
.stat:first-child { border-left: 0; padding-left: 0; }

.stat__k {
  font-family: var(--f-serif);
  font-weight: 400;
  font-size: clamp(1.02rem, 1.45vw, 1.26rem);
  line-height: 1.28;
  color: var(--chalk);
  letter-spacing: -.005em;
}

.stat__v {
  margin: 7px 0 0;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--chalk-mute);
  max-width: 22ch;
}

/* ==========================================================================
   RULED ITEM GRIDS
   No column gaps anywhere — that is what makes the hairline run unbroken
   across a whole row. Separation is padding, never a box.
   ========================================================================== */

.items {
  display: grid;
  column-gap: 0;
  row-gap: clamp(38px, 4.2vw, 58px);
  margin-top: clamp(46px, 5.2vw, 74px);
}
.items--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.item {
  border-top: 1px solid var(--rule);
  padding: 22px clamp(20px, 3vw, 44px) 0 0;
}
.items--3 > .item:nth-child(3n) { padding-right: 0; }

.item__n {
  display: block;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .14em;
  color: var(--chalk-mute);
  font-variant-numeric: tabular-nums;
  margin-bottom: 14px;
}

.item h3, .step h3 {
  font-size: 16px;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: -.004em;
  color: var(--chalk);
  margin-bottom: 10px;
}

.item p, .step p {
  font-size: 15.5px;
  line-height: 1.66;
  color: var(--chalk-dim);
}

/* ---------- process ---------- */

.steps {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  column-gap: 0;
  row-gap: clamp(36px, 4vw, 52px);
  margin-top: clamp(46px, 5.2vw, 74px);
}

.step {
  position: relative;
  border-top: 1px solid var(--rule);
  padding: 24px clamp(20px, 2.8vw, 40px) 0 0;
}
.steps > .step:last-child { padding-right: 0; }

/* the blue tick where each stage begins — drawn, like the dash */
.step::before {
  content: "";
  position: absolute;
  top: -5px; left: 0;
  width: 32px; height: 9px;
  background: var(--tick-img) no-repeat center / 100% 100%;
}

.step__n {
  display: block;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .14em;
  color: var(--blue-br);
  font-variant-numeric: tabular-nums;
  margin-bottom: 14px;
}

/* ---------- the reference architecture sketch ----------
   Grafted, then reset into this page's type system. Two things about the
   original had to go:

   1. Its labels were set in a handwriting face at 15.5px and then scaled a
      further 0.77 by a responsive viewBox — around a 5px x-height at 390px.
      SVG text in a scaled viewBox also ignores the reader's own font-size
      setting, so browser text zoom cannot rescue it. The fix is not a bigger
      script face; it is to STOP SCALING. The drawing renders at its natural
      470 user units — one user unit = one CSS pixel — at every viewport, and
      where 470px will not fit, the plate scrolls instead of shrinking. Every
      label is therefore exactly the size written below, on a phone and on a
      1440 display, and the labels are Inter.
   2. Sixteen of its strings were baked into the SVG in English and never
      translated, under an Azerbaijani caption. Every one now carries a
      data-i18n key, including the <title> and <desc> that are the accessible
      name of the role="img".
   ---------------------------------------------------------------- */

.dia {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 470px) minmax(0, 1fr);
  column-gap: clamp(30px, 3.6vw, 58px);
  align-items: start;
  margin: clamp(46px, 5vw, 70px) 0 0;
  padding-top: 26px;
  border-top: 1px solid var(--rule);
}

.dia__plate {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  padding-block: 3px;   /* room for the stroke bloom the scroll box would clip */

  /* The trailing flex spacer below is what lets the drawing scroll clear of the
     edge fade. Without it the fade sits permanently over the rightmost labels
     ("write" / "yazır", "Postgres") even at maximum scroll, because the fade is
     painted on the parent and does not move. */
  display: flex;
}
.dia__plate::after {
  content: "";
  flex: 0 0 var(--dia-fade, 0px);
}

/* An `outline` is painted OUTSIDE the border box, and a mask-box defaults to
   the border box — so once the edge fade is applied the ring is masked away
   entirely, not merely dimmed. This was the page's only invisible focus
   indicator. An inset ring is painted inside the masked area, so it survives. */
.dia__plate:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--blue-br);
  border-radius: 2px;
}

.sketch {
  display: block;
  /* flex-shrink must be off: the plate is a flex row, and the whole point of
     this drawing is that 470 user units are 470 CSS px at every viewport, so
     the 12px edge labels never scale below the legibility floor. Letting it
     shrink would silently undo that. */
  flex: 0 0 auto;
  width: 470px;
  height: auto;
  fill: none;
  stroke: rgba(205, 208, 203, .74);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* one filter instance for the whole drawing, not one per stroke: a sub-pixel
     bloom is the difference between a chalk line and a vector line */
  filter: drop-shadow(0 0 1.2px rgba(242, 243, 240, .2));
}
.sketch .sk-b { stroke: rgba(157, 192, 220, .82); }
.sketch .sk-arrow { stroke: rgba(205, 208, 203, .56); stroke-width: 1.35; }
.sketch .sk-note-arrow { stroke: rgba(157, 192, 220, .6); stroke-width: 1.35; }

.sketch .sk-type { stroke: none; }
.sketch .sk-n {
  font-family: var(--f-sans);
  font-size: 15.5px;
  font-weight: 600;
  letter-spacing: -.004em;
  fill: var(--chalk);
}
.sketch .sk-n--b { fill: var(--blue-br); }
.sketch .sk-s {
  font-family: var(--f-sans);
  font-size: 12px;
  font-weight: 450;
  fill: var(--chalk-mute);
}
/* the edge labels are the page's small-caps label kit, at the same size and
   tracking as .item__n — not a script face at 12px */
.sketch .sk-e {
  font-family: var(--f-sans);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .14em;
  text-transform: uppercase;
  fill: var(--chalk-mute);
}
.sketch .sk-a {
  font-family: var(--f-sans);
  font-size: 13px;
  font-weight: 500;
  fill: var(--blue-br);
}

.dia__cap {
  padding-top: 2px;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--chalk-mute);
  max-width: 34ch;
}
.dia__cap-k {
  display: block;
  margin-bottom: 9px;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--chalk-mute);
}

/* ---------- technology ---------- */

.stack {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  column-gap: 0;
  margin-top: clamp(42px, 4.6vw, 64px);
}

.stack li {
  border-top: 1px solid var(--rule-soft);
  padding: 13px clamp(14px, 2vw, 28px) 13px 0;
  font-size: 15px;
  line-height: 1.4;
  color: var(--chalk-dim);
  letter-spacing: .004em;
}

/* ==========================================================================
   ABOUT
   ========================================================================== */

.about {
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) minmax(0, .7fr);
  column-gap: clamp(32px, 4.4vw, 68px);
  row-gap: clamp(40px, 4.5vw, 56px);
  margin-top: clamp(34px, 3.8vw, 54px);
  align-items: start;
}

.checks { margin-top: clamp(26px, 3vw, 36px); }

.checks li {
  position: relative;
  border-top: 1px solid var(--rule-soft);
  padding: 14px 0 14px 32px;
  font-size: 15.5px;
  line-height: 1.62;
  color: var(--chalk-dim);
}
.checks li::before {
  content: "";
  position: absolute;
  left: 0; top: 20px;
  width: 17px; height: 8px;
  background: var(--tick-img) no-repeat center / 100% 100%;
  opacity: .9;
}

/* THE ONE FRAMED BLOCK ON THE PAGE — and the one a buyer actually studies,
   which is why it gets the most care and the least material.
   It used to carry a 168deg wash. However faint the numbers looked, at 1440 it
   rendered as a visibly lighter rectangle around the director's credentials:
   precisely the "panel of a different material" the brief forbids, wrapped
   around the block that has to look most legitimate. There is now NO fill of
   any kind — the slate runs straight through — and no 1px border either. The
   separation is a single drawn line, which is what the brief asks for and what
   a real board would give you.
   One reusable shape, stretched: the wobble is in the path, the even stroke
   weight comes from vector-effect, so the same frame serves any size the card
   takes without a second hand-authored geometry anywhere on the page. */
.dir {
  position: relative;
  padding: clamp(28px, 3vw, 38px);
}

.dir__frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  fill: none;
  stroke: rgba(226, 232, 236, .34);
  stroke-linecap: round;
  /* the sub-pixel bloom that separates a chalk stroke from a vector one */
  filter: drop-shadow(0 0 1.2px rgba(242, 243, 240, .2));
}
/* without this the frame is drawn with preserveAspectRatio="none", so a card
   that is not 400x300 gets a heavier stroke on one axis than the other */
.dir__frame path { vector-effect: non-scaling-stroke; stroke-width: 1.3; }
.dir__frame .dir__frame-b { stroke-width: 1; opacity: .42; }

.dir__mono {
  display: grid;
  place-items: center;
  width: 58px; height: 58px;
  border: 1px solid rgba(226, 232, 236, .24);
  border-radius: 50%;
  font-family: var(--f-serif);
  font-size: 19px;
  letter-spacing: .04em;
  color: var(--chalk);
}

.dir__name {
  margin-top: 22px;
  font-family: var(--f-serif);
  font-size: 21px;
  font-weight: 400;
  line-height: 1.25;
  letter-spacing: -.008em;
  color: var(--chalk);
}

.dir__role {
  margin-top: 5px;
  font-size: 13.5px;
  color: var(--chalk-mute);
}

.dir__meta > div {
  padding-block: 11px;
  border-top: 1px solid var(--rule-soft);
}
.dir__meta > div:first-child { border-top: 0; padding-top: 0; }

.dir__meta dt {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--chalk-mute);
}
.dir__meta dd {
  margin: 5px 0 0;
  font-size: 14px;
  color: var(--chalk-dim);
}

.dir .btn--block { margin-top: 26px; }

/* ==========================================================================
   CONTACT
   ========================================================================== */

.section--contact {
  position: relative;
  overflow: clip;
}

/* the single deliberate accent mark: the 9, left on the board.
   Its cut-out is 48% empty on the left, so the box is oversized and pulled
   right until the drawn 9 sits where it is wanted. */
.nine {
  --n: min(880px, 86vw);
  position: absolute;
  top: 9%;
  right: calc(var(--n) * -0.16);
  width: var(--n);
  opacity: .11;
  pointer-events: none;
}
.nine img { width: 100%; }

.contacts { margin-top: clamp(42px, 4.6vw, 64px); }

.crow__in {
  display: grid;
  grid-template-columns: minmax(0, 220px) minmax(0, 1fr) auto;
  align-items: baseline;
  column-gap: clamp(16px, 2.6vw, 40px);
  padding-block: clamp(19px, 2.1vw, 26px);
  border-top: 1px solid var(--rule);
  text-decoration: none;
}
.contacts > li:last-child .crow__in { border-bottom: 1px solid var(--rule); }

.crow__k {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--chalk-mute);
}

.crow__v {
  font-family: var(--f-serif);
  font-weight: 400;
  font-size: clamp(1.12rem, 1.85vw, 1.45rem);
  line-height: 1.3;
  letter-spacing: -.008em;
  color: var(--chalk);
  overflow-wrap: anywhere;
  transition: color .3s var(--ease);
}

.crow__go {
  color: var(--blue-br);
  font-size: 17px;
  line-height: 1;
  opacity: 0;
  transform: translateX(-7px);
  transition: opacity .32s var(--ease), transform .32s var(--ease);
}

a.crow__in:hover .crow__v,
a.crow__in:focus-visible .crow__v { color: var(--blue-br); }
a.crow__in:hover .crow__go,
a.crow__in:focus-visible .crow__go { opacity: 1; transform: none; }

/* ==========================================================================
   FOOTER
   ========================================================================== */

.foot { padding-bottom: clamp(34px, 4vw, 52px); }

.foot__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: clamp(24px, 3vw, 44px);
  padding-block: clamp(34px, 4vw, 50px);
}

.foot__brand picture { position: relative; display: block; width: max-content; }
.foot__brand img {
  --b-h: 84px;
  height: var(--b-h);
  width: auto;
  max-width: none;   /* see .brand__img — same anamorphic squeeze */
  margin-left: calc(var(--b-h) * -0.2845);
  margin-block: calc(var(--b-h) * -0.13);
  opacity: .9;
}
.foot__brand p {
  margin-top: 16px;
  font-size: 13.5px;
  color: var(--chalk-mute);
}

.foot__links {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(18px, 2.4vw, 34px);
}
.foot__links a {
  font-size: 14px;
  color: var(--chalk-dim);
  text-decoration: none;
  border-bottom: 1px solid rgba(226, 232, 236, .18);
  padding-bottom: 3px;
  transition: color .28s var(--ease), border-color .28s var(--ease);
}
.foot__links a:hover { color: var(--blue-br); border-bottom-color: var(--blue); }

.foot__bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px 24px;
  padding-top: clamp(20px, 2.4vw, 30px);
  border-top: 1px solid var(--rule-soft);
  font-size: 13.5px;
  color: var(--chalk-mute);
}
.foot__dom { letter-spacing: .1em; }

/* ==========================================================================
   REVEAL — visible by DEFAULT.
   The hidden state exists ONLY inside prefers-reduced-motion: no-preference,
   and only behind the .js class app.js sets on itself. That single structure
   retires three separate failure modes:

     - the page can no longer render blank. An ungated `.reveal{opacity:0}`
       means a 404, a parse error or a CSP block leaves the document invisible;
       this one fails VISIBLE, which is the only acceptable direction;
     - a reduced-motion visitor never has the transition defined at all,
       rather than having it neutralised by !important further down;
     - and because the CSS cannot fail closed, no safety keyframe and no
       setTimeout backstop are needed. The old `setTimeout(showAll, 2600)`
       revealed the entire document 2.6s after load regardless of scroll
       position, which quietly deleted the feature for anyone who paused to
       read — measured: 4 of 25 reveals still pending after 3s.
   ========================================================================== */

.reveal { opacity: 1; }

@media (prefers-reduced-motion: no-preference) {
  .js .reveal {
    opacity: 0;
    transform: translateY(13px);
    transition: opacity .78s var(--ease), transform .78s var(--ease);
  }
  .js .reveal.in { opacity: 1; transform: none; }

  .js .reveal[data-delay="1"] { transition-delay: 80ms; }
  .js .reveal[data-delay="2"] { transition-delay: 160ms; }
  .js .reveal[data-delay="3"] { transition-delay: 240ms; }
  .js .reveal[data-delay="4"] { transition-delay: 320ms; }
}

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */

@media (max-width: 1180px) {
  :root { --col-label: 172px; }
  .steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .steps > .step:last-child { padding-right: clamp(20px, 2.8vw, 40px); }
  .steps > .step:nth-child(2n) { padding-right: 0; }
}

@media (max-width: 880px) {
  /* the caption column stops being a column and becomes a legend under the
     plate; the drawing itself never scales — the plate scrolls instead */
  .dia { grid-template-columns: minmax(0, 1fr); row-gap: 18px; }
  .dia__cap { max-width: 52ch; }

  /* A hard clip at the viewport edge reads as a rendering fault; a fade reads
     as "this continues". The mask stays — being a mask is what makes it match
     the board exactly, where a gradient to a flat colour could not.
     See .dia__plate:focus-visible for why the focus ring is drawn inset. */
  :root { --dia-fade: 38px; }

  .dia__plate {
    -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - var(--dia-fade)), transparent 100%);
            mask-image: linear-gradient(90deg, #000 calc(100% - var(--dia-fade)), transparent 100%);
  }
}

@media (max-width: 1000px) {
  .items--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .items--3 > .item:nth-child(3n) { padding-right: clamp(20px, 3vw, 44px); }
  .items--3 > .item:nth-child(2n) { padding-right: 0; }
  .stack { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .about { grid-template-columns: minmax(0, 1fr); }
  .dir { max-width: 460px; }
}

@media (max-width: 940px) {
  .sec { grid-template-columns: minmax(0, 1fr); }

  .label {
    position: static;
    padding-top: 0;
    margin-bottom: clamp(20px, 3vw, 28px);
  }
  .hero__eyebrow { margin-bottom: clamp(24px, 4vw, 34px); }

  .nav__cta { display: none; }
  .burger { display: grid; }
  .nav__actions { margin-left: auto; }

  /* The menu is not a dropdown panel — it is the board itself, clipped to
     everything below the bar, with the links written on it. */
  .nav__links {
    position: fixed;
    inset: var(--nav-h) 0 0 0;
    display: none;
    margin-left: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    z-index: 1;
  }
  .nav__links.is-open { display: block; }
  .nav__links.is-open > .board--menu { display: block; }

  .nav__links-in {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    width: min(var(--wrap), 100% - var(--pad) * 2);
    margin-inline: auto;
    padding-block: 8px clamp(40px, 12vh, 90px);
  }
  .nav__links a {
    padding: 18px 0;
    font-size: 16px;
    color: var(--chalk-dim);
    border-top: 1px solid var(--rule-soft);
  }
  .nav__links a::after { display: none; }

  .nav__links a.nav__cta-m {
    display: flex;
    margin-top: 32px;
    padding: 17px 26px;
    font-size: 14px;
    color: var(--chalk);
    border: 1px solid rgba(226, 232, 236, .30);
  }

  /* Without JS the burger cannot open anything, so the small-screen nav stops
     being a menu: the header leaves the fixed layer, takes its own space in
     the flow, and the links wrap onto a second row.

     Gated on `html:not(.js)`, NOT on @media (scripting: none). The media query
     is true only when scripting is genuinely disabled — it is FALSE in the
     production failure mode that actually happens, where scripting is enabled
     and app.js 404s, throws, or is blocked by CSP. The class is written by
     app.js itself, so its absence covers both cases. The media query is kept
     as a second belt for the handful of engines that support it. */
  html:not(.js) { scroll-padding-top: 12px; }
  html:not(.js) .nav { position: static; }
  html:not(.js) .board--nav { display: none; }
  html:not(.js) .nav__inner { flex-wrap: wrap; height: auto; padding-block: 16px 14px; }
  html:not(.js) .burger { display: none; }
  html:not(.js) .hero { padding-top: clamp(30px, 5vh, 56px); }
  html:not(.js) .hero__eyebrow { margin-bottom: clamp(20px, 3vw, 28px); }

  html:not(.js) .nav__links {
    position: static;
    order: 3;
    display: flex;
    width: 100%;
    margin-left: 0;
    overflow: visible;
  }
  html:not(.js) .nav__links-in {
    flex-flow: row wrap;
    align-items: center;
    width: auto;
    margin: 0;
    padding: 10px 0 0;
    gap: 6px 20px;
  }
  html:not(.js) .nav__links a { padding: 4px 0; font-size: 13.5px; border-top: 0; }
  html:not(.js) .nav__links a.nav__cta-m { display: none; }

  @media (scripting: none) {
    .nav { position: static; }
    .board--nav { display: none; }
    .nav__inner { flex-wrap: wrap; height: auto; padding-block: 16px 14px; }
    .burger { display: none; }
    .nav__links { position: static; order: 3; display: flex; width: 100%; margin-left: 0; overflow: visible; }
    .nav__links-in { flex-flow: row wrap; align-items: center; width: auto; margin: 0; padding: 10px 0 0; gap: 6px 20px; }
    .nav__links a { padding: 4px 0; font-size: 13.5px; border-top: 0; }
    .nav__links a.nav__cta-m { display: none; }
  }
}

@media (max-width: 720px) {
  :root { --nav-h: 80px; }

  /* Re-measured for the portrait box: the lamp has to work harder and the
     corner has to fall further, because the same absolute frame darkening
     covers 55% of a 390px width against 15% of a 1440px one. */
  :root {
    --lit-1: .175;
    --lit-2: .066;
    --fall-1: .96;
    --fall-2: .58;
    --vig-1: .26;
    --vig-2: .18;
    --vig-blur: 110px;
    --vig-spread: 12px;
  }

  .brand__img { --b-h: 62px; }
  .foot__brand img { --b-h: 70px; }

  .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 26px; }
  .stat:nth-child(odd) { border-left: 0; padding-left: 0; }
  .stat__v { max-width: none; }

  .crow__in {
    grid-template-columns: minmax(0, 1fr) auto;
    row-gap: 7px;
  }
  .crow__k { grid-column: 1 / -1; }
  .crow__go { opacity: 1; transform: none; }

  .nine { top: auto; bottom: -1%; right: -14%; width: 74vw; opacity: .10; }
}

@media (max-width: 620px) {
  .items--3 { grid-template-columns: minmax(0, 1fr); row-gap: 32px; }
  .items--3 > .item { padding-right: 0; }
  .steps { grid-template-columns: minmax(0, 1fr); row-gap: 30px; }
  .steps > .step { padding-right: 0; }
  .stack { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cta { gap: 18px; }
  .btn--line { width: 100%; }
  .btn--text { width: auto; justify-content: flex-start; }
}

@media (max-width: 400px) {
  h2 { max-width: none; }
  .hero__mark { --mark-w: min(80vw, 274px); }
  .stats { grid-template-columns: minmax(0, 1fr); row-gap: 20px; }
  .stat { border-left: 0; padding-left: 0; }
}

/* ==========================================================================
   REDUCED MOTION
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    transition-delay: 0ms !important;
  }

  .reveal { opacity: 1; transform: none; }
  .crow__go { opacity: 1; transform: none; }
}

/* ==========================================================================
   PRINT — the board does not print; the company details must.
   ========================================================================== */

@media print {
  /* Remap the whole palette to ink rather than patching colours one by one:
     browsers drop background images, so the board goes with them and every
     chalk value would otherwise print as pale grey on white. */
  :root {
    --chalk:      #14161a;
    --chalk-dim:  #23262b;
    --chalk-mute: #43474e;
    --blue:       #2c4a63;
    --blue-br:    #2c4a63;
    --rule:       rgba(20, 22, 26, .40);
    --rule-soft:  rgba(20, 22, 26, .22);
  }
  html { background: none; }
  .board, .nav, .nine, .skip { display: none !important; }
  body { color: #23262b; }
  h1, h2, h3, .crow__v, .stat__k, .dir__name, .dir__mono, .dir__meta dd { color: #14161a; text-shadow: none; }
  .rule, .rule--soft, .dash, .step::before, .checks li::before { background-image: none; }
  .rule, .rule--soft { height: 1px; background-color: rgba(20, 22, 26, .35); }
  .dash, .step::before, .checks li::before { height: 1px; background-color: #2c4a63; }
  .dir__frame { stroke: rgba(20, 22, 26, .45); filter: none; }
  .sketch { stroke: #4a5058; filter: none; }
  .sketch .sk-n { fill: #14161a; }
  .sketch .sk-s, .sketch .sk-e { fill: #33373d; }
  .sketch .sk-n--b, .sketch .sk-a { fill: #2c4a63; }
  /* THE LOCKUP IS A HOLLOW WHITE OUTLINE ON TRANSPARENT. Left alone it prints
     white on white and the client's mark disappears from the page entirely —
     and a B2B one-pager gets printed and PDF'd by procurement. Knocking it to
     black keeps the drawing and loses the white. */
  .hero__mark img, .brand__img, .foot__brand img { filter: brightness(0) opacity(.88); }
  .hero__mark::before, .brand::before, .foot__brand picture::before { display: none; }
  .js .reveal { opacity: 1 !important; transform: none !important; }
}
