/* =========================================================================
   BuildBox.to — single stylesheet
   =========================================================================
   No build step: this file is authored by hand and served as-is.
   The site's CSP sets `style-src 'self'`, so there are NO inline style
   attributes and NO <style> blocks in the HTML. Everything lives here.

   Layer order (do not reorder):
     1. Tokens          2. Fonts       3. Reset       4. Typography
     5. Layout          6. Components  7. Page blocks 8. Utilities

   Breakpoints are 48em (768px) and 64em (1024px). Those are the only two
   layout switches on the site — do not introduce a third.
   ========================================================================= */


/* =========================================================================
   1. TOKENS
   -------------------------------------------------------------------------
   Contrast ratios below are measured, not estimated. Text pairs are checked
   against WCAG 2.2 AA (4.5:1 body, 3:1 large); control boundaries and focus
   indicators against SC 1.4.11 (3:1).
   ========================================================================= */

:root {
  /* Surfaces */
  --bg: #0B0D10;
  --surface: #14171C;
  --surface-2: #1C2027;

  /* Borders.
     --border is DECORATIVE ONLY — hairline dividers and section rules. It
     measures 1.45:1 on --bg and must never carry a control boundary.
     --border-strong is for anything that identifies a control: form fields,
     buttons, card edges. 4.02:1 on --bg, 3.72:1 on --surface, 3.38:1 on
     --surface-2 — clears SC 1.4.11 against every surface the site uses. */
  --border: #2A2F38;
  --border-strong: #6B7280;

  /* Text.  --text 17.66:1 on --bg / 16.30:1 on --surface
            --text-muted 7.63:1 on --bg / 7.04:1 on --surface
            --text-dim  5.95:1 on --bg / 5.49:1 on --surface / 5.00:1 on --surface-2

     --text-dim is the dimmest colour any TEXT may use. Do not reach for
     --border-strong here: at 4.02:1 it clears the 3:1 that SC 1.4.11 asks of a
     control boundary but not the 4.5:1 that small text needs, and axe catches
     it on every page carrying a footer label. Border tokens are for borders. */
  --text: #F2F4F7;
  --text-muted: #9AA3AF;
  --text-dim: #868F9C;

  /* Brand accent. 7.46:1 on --bg. --accent-fg on --accent is 7.46:1. */
  --accent: #FF7A1A;
  --accent-fg: #0B0D10;

  /* Status colours — deliberately outside the single-accent rule (KTD10),
     so a form error never renders in the same orange as the primary action.
     --danger 10.25:1 on --bg / 9.46:1 on --surface
     --success 13.86:1 on --bg / 12.79:1 on --surface */
  --danger: #FCA5A5;
  --success: #86EFAC;

  /* Spacing scale */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --space-7: 3rem;
  --space-8: 4rem;
  --space-9: 6rem;

  /* Radii */
  --radius-sm: 5px;
  --radius: 7px;
  --radius-lg: 10px;

  /* Layout */
  --container: 1180px;
  --gutter: 1.5rem;

  /* Minimum interactive target, SC 2.5.8 */
  --target-min: 24px;
  --target-comfortable: 44px;

  /* Type */
  --font-display: "Space Grotesk", "Segoe UI", system-ui, sans-serif;
  --font-body: "Inter", "Segoe UI", system-ui, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;
}

@media (min-width: 48em) {
  :root { --gutter: 2.5rem; }
}


/* =========================================================================
   2. FONTS
   -------------------------------------------------------------------------
   Self-hosted latin-subset variable fonts (KTD7). No third-party request on
   page load — a font CDN would breach R17 and put an EU data question on
   every visit. Licence: SIL Open Font License 1.1, see assets/fonts/OFL.txt.
   ========================================================================= */

@font-face {
  font-family: "Space Grotesk";
  src: url("../fonts/space-grotesk-latin.woff2") format("woff2");
  font-weight: 500 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Inter";
  src: url("../fonts/inter-latin.woff2") format("woff2");
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "JetBrains Mono";
  src: url("../fonts/jetbrains-mono-latin.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}


/* =========================================================================
   3. RESET
   ========================================================================= */

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4, p, figure, blockquote, dl, dd { margin: 0; }

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

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

button, input, select, textarea { font: inherit; color: inherit; }

address { font-style: normal; }

/* Focus.
   A single light ring is invisible on the accent-filled button (2.37:1), so
   the ring is two-tone: a dark inner stroke that sits on the accent fill
   (7.46:1) and a light outer stroke that sits on the page around it
   (17.66:1 on --bg, 16.30:1 on --surface). Every adjacency clears 3:1. */
:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--text);
  border-radius: var(--radius-sm);
}

:focus:not(:focus-visible) { outline: none; }


/* =========================================================================
   4. TYPOGRAPHY
   ------------------------------------------------------------------------- */

.display-1,
.display-2,
.display-3 {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: -0.03em;
  text-wrap: pretty;
}

.display-1 { font-size: clamp(2.35rem, 1.30rem + 5.25vw, 4.75rem); line-height: 1.03; }
.display-2 { font-size: clamp(1.95rem, 1.35rem + 3.00vw, 3.60rem); line-height: 1.07; }
.display-3 { font-size: clamp(1.60rem, 1.25rem + 1.75vw, 2.35rem); line-height: 1.15; }

.h-card {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.25rem;
  line-height: 1.25;
  letter-spacing: -0.01em;
}

.eyebrow {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.72rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}

.label {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.66rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.lead { font-size: clamp(1.03rem, 0.97rem + 0.30vw, 1.19rem); line-height: 1.65; color: var(--text-muted); }
.prose { font-size: clamp(1rem, 0.96rem + 0.20vw, 1.06rem); line-height: 1.75; }
.prose-muted { color: var(--text-muted); }
.small { font-size: 0.875rem; line-height: 1.6; }
.muted { color: var(--text-muted); }
.dim { color: var(--text-dim); }

a { color: var(--text); text-decoration: none; }
a:hover { color: var(--accent); }
/* A link inside a block of text must be distinguishable by more than colour
   alone, so every in-prose link is underlined. Nav, footer, buttons and card
   links are excluded deliberately: they are standalone targets, not links
   embedded in a sentence. */
.prose a, .small a, .lead a, address a { text-decoration: underline; text-underline-offset: 0.15em; }


/* =========================================================================
   5. LAYOUT
   ------------------------------------------------------------------------- */

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

.section { padding-block: var(--space-8); }
.section-lg { padding-block: var(--space-9); }
.section-rule { border-top: 1px solid var(--border); }
.section-surface { background: var(--surface); }

@media (min-width: 48em) {
  .section { padding-block: var(--space-9); }
  .section-lg { padding-block: 7rem; }
}

.stack { display: flex; flex-direction: column; }
.stack-2 { gap: var(--space-2); }
.stack-3 { gap: var(--space-3); }
.stack-4 { gap: var(--space-4); }
.stack-5 { gap: var(--space-5); }
.stack-6 { gap: var(--space-6); }

.row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-5); }
.row-between { justify-content: space-between; }
.row-baseline { align-items: baseline; }

.grid { display: grid; gap: var(--space-5); grid-template-columns: repeat(1, minmax(0, 1fr)); }

@media (min-width: 48em) {
  .grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Split layouts are a single stacked column until 64em. The base definition
   lives outside the media query so the stacked state still has a gap. */
.split,
.split-wide,
.split-sticky {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-6);
  align-items: start;
}

@media (min-width: 64em) {
  .grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .split { grid-template-columns: 0.95fr 1.05fr; gap: var(--space-9); }
  .split-wide { grid-template-columns: 1.15fr 0.85fr; gap: var(--space-9); }
  .split-sticky { grid-template-columns: 0.82fr 1.18fr; gap: var(--space-9); }
}

/* position: sticky silently does nothing on a stretched grid item, so the
   align-self is load-bearing, not decoration. */
.sticky-col { align-self: start; }

@media (min-width: 64em) {
  .sticky-col { position: sticky; top: var(--space-7); }
}

.skip-link {
  position: absolute;
  left: var(--space-4);
  top: var(--space-4);
  z-index: 100;
  transform: translateY(-200%);
  background: var(--accent);
  color: var(--accent-fg);
  font-weight: 600;
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius);
}

.skip-link:focus {
  transform: translateY(0);
  color: var(--accent-fg);
}

main:focus { outline: none; }


/* =========================================================================
   6. COMPONENTS
   ------------------------------------------------------------------------- */

/* --- Buttons --------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--target-comfortable);
  padding: var(--space-3) var(--space-6);
  border: 1px solid transparent;
  border-radius: var(--radius);
  font-size: 0.97rem;
  font-weight: 600;
  cursor: pointer;
  text-align: center;
}

.btn-primary { background: var(--accent); color: var(--accent-fg); }
.btn-primary:hover { background: #FF8C39; color: var(--accent-fg); }

.btn-quiet {
  background: transparent;
  color: var(--text);
  border-color: var(--border-strong);
}
.btn-quiet:hover { background: var(--surface-2); color: var(--text); }

.btn[disabled] { opacity: 0.6; cursor: not-allowed; }

.btn-sm { min-height: var(--target-min); padding: var(--space-2) var(--space-5); font-size: 0.9rem; }

.link-arrow {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  min-height: var(--target-min);
}

/* --- Header ---------------------------------------------------------- */

.site-header { border-bottom: 1px solid var(--border); }

.site-header .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  min-height: 78px;
  flex-wrap: wrap;
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 0.65rem;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.19rem;
  letter-spacing: -0.02em;
  color: var(--text);
  min-height: var(--target-comfortable);
}

.wordmark:hover { color: var(--text); }
.wordmark svg { color: var(--accent); flex: none; }

.nav-toggle { display: none; }

.site-nav {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-6);
  font-size: 0.91rem;
}

/* :not(.btn) is load-bearing. Without it this rule out-specifies .btn-primary
   and paints the header's accent-filled call to action in --text-muted on
   --accent: 1.02:1, i.e. invisible. It only shows above the mobile breakpoint,
   where the nav is expanded. */
.site-nav a:not(.btn) {
  color: var(--text-muted);
  display: inline-flex;
  align-items: center;
  min-height: var(--target-comfortable);
}

.site-nav a:not(.btn):hover { color: var(--accent); }

/* Current page. Driven from <body data-page>, which sits OUTSIDE the
   sentinel-delimited chrome block — so the header stays byte-identical
   across all pages and the drift gate keeps passing. */
body[data-page="services"] .site-nav a[href="services.html"],
body[data-page="approach"] .site-nav a[href="approach.html"],
body[data-page="about"] .site-nav a[href="about.html"] {
  color: var(--text);
  box-shadow: inset 0 -2px 0 var(--accent);
}

.site-nav .btn { min-height: var(--target-comfortable); }

/* Progressive enhancement.
   With scripting off, the `js` class is never set, so the nav stays a plain
   visible stacked bar and the toggle button never appears. Nothing is hidden
   behind a control that cannot work. */
.nav-ready .nav-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-width: var(--target-comfortable);
  min-height: var(--target-comfortable);
  padding: var(--space-2) var(--space-4);
  background: transparent;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  color: var(--text);
  font-size: 0.9rem;
  cursor: pointer;
}

.nav-ready .site-nav {
  display: none;
  width: 100%;
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  padding-bottom: var(--space-4);
}

.nav-ready .site-nav[data-open="true"] { display: flex; }

.nav-ready .site-nav a {
  min-height: var(--target-comfortable);
  border-top: 1px solid var(--border);
}

.nav-ready .site-nav .btn { margin-top: var(--space-4); border-top: 0; }

@media (min-width: 48em) {
  .nav-ready .nav-toggle { display: none; }
  .nav-ready .site-nav {
    display: flex;
    width: auto;
    flex-direction: row;
    align-items: center;
    gap: var(--space-2) var(--space-6);
    padding-bottom: 0;
  }
  .nav-ready .site-nav a { border-top: 0; }
  .nav-ready .site-nav .btn { margin-top: 0; }
}

/* --- Cards and hairline lists ---------------------------------------- */

.card {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  color: var(--text);
}

.card:hover { background: var(--surface-2); color: var(--text); }

/* Stretched link: the whole card is one target and one tab stop, rather than
   six duplicate stops for six cards. */
.card-link::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

.card:has(.card-link:focus-visible) {
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--text);
}

.card-index {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  color: var(--accent);
}

.hairlines { display: flex; flex-direction: column; }

.hairlines > * {
  border-top: 1px solid var(--border);
  padding-block: var(--space-3);
  font-size: 0.9rem;
}

.hairlines-lg > * { padding-block: var(--space-4); font-size: 0.95rem; }
.hairlines > :first-child { border-top-color: var(--border-strong); }
.hairlines-closed > :last-child { border-bottom: 1px solid var(--border); }

/* --- Stage list (approach) -------------------------------------------- */

.stage { border-top: 1px solid var(--border); padding-block: var(--space-6) var(--space-7); }
.stage:first-child { border-top-color: var(--border-strong); }
.stage:last-child { border-bottom: 1px solid var(--border); }

.stage-head { display: flex; align-items: baseline; gap: var(--space-5); }

.stage-num {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2.6rem, 1.8rem + 4vw, 4.25rem);
  line-height: 0.9;
  letter-spacing: -0.05em;
  color: var(--accent);
  flex: none;
}

.stage-parts { display: grid; gap: var(--space-5); margin-top: var(--space-5); }

@media (min-width: 48em) {
  .stage-parts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* --- Tech chips ------------------------------------------------------- */

.chips { display: flex; flex-wrap: wrap; gap: var(--space-2); }

.chip {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--text-muted);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
}

/* --- Stat band -------------------------------------------------------- */

.stat-value {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2rem, 1.5rem + 2.5vw, 2.9rem);
  letter-spacing: -0.04em;
  line-height: 1;
}

.stat { border-top: 1px solid var(--border-strong); padding-top: var(--space-5); }


/* --- Contact form ------------------------------------------------------ */

.contact-panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
}

@media (min-width: 48em) {
  .contact-panel { padding: var(--space-7); }
}

.field { display: flex; flex-direction: column; gap: var(--space-2); }

.field > label { font-size: 0.85rem; color: var(--text-muted); }

.field-optional { color: var(--text-dim); }

.field input,
.field select,
.field textarea {
  width: 100%;
  min-height: var(--target-comfortable);
  padding: var(--space-3) var(--space-4);
  background: var(--bg);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  color: var(--text);
  font-size: 0.97rem;
}

.field textarea { min-height: 9rem; resize: vertical; line-height: 1.6; }

.field input:hover,
.field select:hover,
.field textarea:hover { border-color: var(--text-dim); }

.field-pair { display: grid; gap: var(--space-5); grid-template-columns: minmax(0, 1fr); }

@media (min-width: 48em) {
  .field-pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* The honeypot must be off-screen rather than display:none - a bot that reads
   computed styles skips anything hidden, and the point is that it fills this
   in. Kept out of the tab order and out of the accessibility tree. */
.honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form-result { display: none; }
.form-result[data-state="success"],
.form-result[data-state="error"] { display: flex; gap: var(--space-4); align-items: flex-start; }
.form-result[data-state="success"] .form-result-title { color: var(--success); }
.form-result[data-state="error"] .form-result-title { color: var(--danger); }

.form-result-title { font-weight: 600; font-size: 1.05rem; }

.form-hidden { display: none; }

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

.site-footer { border-top: 1px solid var(--border); background: var(--surface); }
.footer-grid { display: grid; gap: var(--space-7); padding-block: var(--space-8) var(--space-5); }

@media (min-width: 48em) {
  .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 64em) {
  .footer-grid { grid-template-columns: 1.6fr 1fr 1fr 1fr; }
}

.footer-col { display: flex; flex-direction: column; gap: var(--space-3); }

.footer-col a {
  color: var(--text-muted);
  font-size: 0.9rem;
  display: inline-flex;
  align-items: center;
  min-height: var(--target-comfortable);
}

.footer-col a:hover { color: var(--accent); }
.footer-col .label { margin-bottom: var(--space-1); }

.footer-bottom {
  border-top: 1px solid var(--border);
  padding-block: var(--space-5) var(--space-7);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  flex-wrap: wrap;
}

.footer-contact { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-6); }

.footer-contact a {
  color: var(--text);
  font-size: 0.9rem;
  display: inline-flex;
  align-items: center;
  min-height: var(--target-comfortable);
}

.copyright {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  color: var(--text-dim);
}


/* =========================================================================
   7. PAGE BLOCKS
   ------------------------------------------------------------------------- */

.hero { padding-block: var(--space-8) var(--space-8); }

@media (min-width: 48em) {
  .hero { padding-block: 8rem 7rem; }
}

.hero-title { max-width: 20ch; }
.hero-title .accent-line { color: var(--accent); display: block; }
.hero-sub { max-width: 42ch; margin-top: var(--space-5); }
.hero-actions { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-5); margin-top: var(--space-7); }

.note {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

.page-head { padding-block: var(--space-8) var(--space-7); }

@media (min-width: 48em) {
  .page-head { padding-block: 6.5rem 5rem; }
}

.page-head .display-1 { max-width: 22ch; }
.page-head .lead { max-width: 52ch; margin-top: var(--space-5); }

.cta-band { text-align: center; }
.cta-band .display-2 { max-width: 18ch; margin-inline: auto; }
.cta-band .lead { max-width: 44ch; margin-inline: auto; margin-top: var(--space-5); }
.cta-band .btn { margin-top: var(--space-7); }

.section-anchor { scroll-margin-top: var(--space-6); }


/* =========================================================================
   8. UTILITIES
   ------------------------------------------------------------------------- */

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

.mt-4 { margin-top: var(--space-4); }
.mt-5 { margin-top: var(--space-5); }
.mt-6 { margin-top: var(--space-6); }
.mt-7 { margin-top: var(--space-7); }
.max-prose { max-width: 62ch; }

/* Reveal on scroll. The hidden start state is scoped to .js so that with
   scripting off — where the observer never runs — content is simply visible
   rather than stranded at opacity 0. */
/* The `js` class is set in <head> before first paint, so the hidden start
   state applies without a flash. That means if main.js never runs — a failed
   load, an early throw, a CSP mismatch on the pinned hash — this content would
   stay invisible forever with nothing left to reveal it.

   The failsafe animation is the guarantee: with a 4s delay and no duration, it
   snaps the element visible if nothing has set data-shown by then. An inline
   `onerror` on the script tag would only catch a load failure, and our own CSP
   blocks inline handlers anyway. This covers every failure mode with no JS.
   Once revealed normally, [data-shown] cancels it. */
.js .reveal {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 0.5s ease, transform 0.5s ease;
  animation: reveal-failsafe 0s linear 4s forwards;
}

.js .reveal[data-shown="true"] { opacity: 1; transform: none; animation: none; }

@keyframes reveal-failsafe {
  to { opacity: 1; transform: none; }
}

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