/* ============================================================================
   InfraEdge — foundation: reset, tokens, type scale, motion primitives.
   Component styles live in style.css.
   Token names only downstream — never a raw hex outside this file.
   ========================================================================= */

/* ---------------------------------------------------------------- reset */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body { -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }
img, svg, video { display: block; max-width: 100%; }
input, button, textarea, select { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }
p, li { overflow-wrap: break-word; }
/*
  `hidden` means hidden, whatever a component says about display.

  The browser's own rule for [hidden] is `display: none` from the user-agent
  stylesheet, which any author rule setting `display` silently beats. That is
  how /find-your-path shipped with its contact form visible underneath the
  questionnaire: the element had the attribute, the panel had `display: grid`,
  and the grid won. Nothing in the markup looked wrong, and a test asserting
  `el.hidden` agreed with it.

  This makes the attribute mean what everybody reading the markup assumes.
  Anything that needs to animate out belongs in a class, not here.
*/
[hidden] { display: none !important; }
h1, h2, h3, h4 { hyphens: none; }
ul, ol { list-style: none; padding: 0; }
table { border-collapse: collapse; width: 100%; }


/* ---------------------------------------------------------------- fonts */
/*
  Self-hosted, subset to Latin plus the punctuation this site actually renders
  (704 kB of full families down to 136 kB). Not a performance nicety: fetching
  fonts from Google sends every visitor's IP to a third party on every page
  load, which is indefensible on a site that invites a security reviewer and
  publishes a subprocessor table. It also means the downloadable report does
  not phone out to anyone when a prospect opens it.
*/
/*
  Two families and one exception.

  Source Serif 4 sets the headings. A serif at display size reads as a firm that
  writes and thinks, which is the impression this business actually wants; the
  previous face was Inter, which by now carries the association of every AI
  product shipped in the last three years however careful the words are.

  Source Sans 3 sets everything else. It was drawn as Source Serif's sibling, so
  the two agree on proportion and a heading sitting above a paragraph looks
  intended rather than assembled.

  JetBrains Mono survives for code, hashes and paths only — the places where
  monospace carries meaning because the characters have to line up. It used to
  set sixty-odd eyebrows, chips and diagram annotations, and small uppercase
  letter-spaced monospace is the visual shorthand for machine output. On a
  diagram explaining an engagement it undid the credibility the diagram existed
  to build.

  Both new files are variable, so one request covers every weight. The serif has
  its optical-size axis pinned at 20 — the text-to-subhead size we actually use —
  which takes it from 110 kB to 43.6 kB. Total font payload is 84.9 kB against
  136.6 kB before.
*/
@font-face {
  font-family: 'Source Serif 4';
  src: url('/assets/fonts/SourceSerif4-Variable.woff2') format('woff2-variations');
  font-weight: 200 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Source Sans 3';
  src: url('/assets/fonts/SourceSans3-Variable.woff2') format('woff2-variations');
  font-weight: 200 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'JetBrains Mono';
  src: url('/assets/fonts/JetBrainsMono-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* ---------------------------------------------------------------- tokens */
:root {
  /* Brand — see .cursor/rules/infraedge.mdc. These values are the contract. */
  --navy-900: #0B2545;
  --navy-800: #10315C;
  --navy-700: #1A4176;
  --sky-500:  #3A8DDE;   /* 3.47:1 on white — large display, shapes, illustration only */
  --sky-600:  #2477CC;
  --sky-700:  #1A63B0;   /* 6.08:1 on white — links and any text under 24px */
  --gold-400: #F5C242;   /* 1.66:1 on white — NEVER text on light. Surface or shape only. */
  --gold-600: #C4820E;   /* 3.21:1 on white — gold text on light at LARGE sizes only */
  --gold-700: #93620A;   /* 5.26:1 on white, 4.85:1 on mist — gold text at label sizes */
  --slate-500: #5B6B85;
  --slate-600: #4A5A73;
  --line:  #DDE5EF;
  --line-strong: #C6D3E4;
  --mist:  #F2F6FB;
  --base:  #F7F9FC;
  --white: #FFFFFF;

  /* Derived — on-navy surfaces, tints and hovers. Still tokens, not literals. */
  --on-navy:       #DCE6F2;   /* body text on navy-900 — 12.4:1 */
  --on-navy-quiet: #A9BFD8;   /* secondary text on navy — 7.6:1 */
  --on-navy-faint: #7C93AF;   /* legal small print on navy — 4.6:1 */
  --navy-line:     #23446E;   /* hairlines inside navy sections */
  --navy-line-2:   #2E5686;   /* outline buttons on navy */
  --sky-tint:      #E4EFFB;   /* badge and chip fill on light */
  --gold-hover:    #FFD264;
  --gold-tint:     #FFF8E8;   /* the partial / void surface */
  --amber-tint:    #FDEBD8;   /* high-severity badge fill */
  --amber-ink:     #9A4C05;   /* 5.9:1 on --amber-tint */
  --pass-ok:       #22A06B;   /* pipeline pass / checklist tick */
  --header-bg:     rgba(247, 249, 252, .88);
  --header-bg-stuck: rgba(255, 255, 255, .94);

  /* The navy band. Its own tokens because --navy-900 carries two unrelated
     jobs — the strongest ink and stroke, and the ground of a .tone-navy
     section. Those want opposite things in dark mode, so the band is named
     separately and the ink keeps the shared token. */
  --tone-navy-bg:   var(--navy-900);
  --tone-navy-card: var(--navy-800);
  --tone-navy-ink:  var(--white);

  /* Gold is a fill in both themes — it is the one brand colour that does not
     move — so anything written on it needs ink that does not move either.
     --navy-900 cannot do this job: it is the ink token, and it goes light in
     dark mode, which would put pale type on a gold button. */
  --ink-on-gold: #0B2545;

  color-scheme: light;

  /* Semantic */
  --ink: var(--navy-900);
  --ink-soft: var(--slate-600);
  --ink-quiet: var(--slate-500);
  --surface: var(--base);
  --surface-raised: var(--white);
  --accent: var(--sky-700);
  /* Surface hover — border + title ink. Remap via sky/gold; navy bands override. */
  --hover-border: var(--sky-600);
  --hover-ink: var(--sky-700);

  /* Type */
  --display: 'Source Serif 4', Georgia, 'Times New Roman', serif;
  --font: 'Source Sans 3', system-ui, -apple-system, 'Segoe UI', sans-serif;
  /* Code, hashes and paths. Never a label — see the note above the @font-face
     rules for why that mattered. */
  --mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  --t-display: clamp(2.4rem, 5vw, 3.9rem);      /* 38 → 62 */
  --t-h1:      clamp(2.5rem, 5vw, 3.5rem);      /* 40 → 56 */
  --t-h2:      clamp(1.875rem, 3.4vw, 2.5rem);  /* 30 → 40 */
  --t-h3:      clamp(1.25rem, 1.8vw, 1.375rem); /* 20 → 22 */
  --t-lead:    1.0625rem;                        /* 17 */
  --t-body:    1rem;                             /* 16 — Source Sans has a
                                                    smaller x-height than the
                                                    face this replaced, so the
                                                    same pixel size read a step
                                                    small */
  --t-small:   0.875rem;                         /* 14 */
  --t-micro:   0.75rem;                          /* 12 */

  /* Space — 8px base */
  --s-1: .5rem;  --s-2: 1rem;   --s-3: 1.5rem;  --s-4: 2rem;
  --s-5: 3rem;   --s-6: 4rem;   --s-7: 5rem;
  --section: clamp(4rem, 8vw, 7rem);
  --container: 1200px;
  --gutter: clamp(1.5rem, 4vw, 2.5rem);

  /* Radii — 8 small / 16 card / 24 panel. No shadows anywhere. */
  --r-s: 8px; --r-m: 16px; --r-l: 24px;

  /* Motion */
  --ease: cubic-bezier(.2, .7, .3, 1);
  --dur: 500ms;
  --dur-fast: 150ms;
  --stagger: 70ms;
}

/* ------------------------------------------------------------ dark theme */
/*
  A role swap, not an inversion. Every token below keeps the job it does in
  the light palette and only changes value: --white is still "the raised
  surface a card or a diagram box sits on", --navy-900 is still "the strongest
  ink and stroke". That is what lets ~250 uses of the literal tokens across
  five stylesheets — including every SVG fill and stroke in the diagrams —
  follow along without being touched.

  The exception is the navy band. --navy-900 was doing two jobs, ink and the
  ground of a .tone-navy section, and in dark mode those want opposite values.
  The band moved to its own --tone-navy-* tokens above so the ink could keep
  the shared one.

  The two blocks below are deliberately identical: the media query serves a
  reader who has never touched the toggle, and the attribute serves one who
  has. Keep them in step — check-theme.js fails the build if they drift.
*/
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    color-scheme: dark;

    --navy-900: #E8EDF4;   /* now the ink: 15.3:1 on the page ground */
    --navy-800: #CBD5E2;
    --navy-700: #AEBBCB;
    --sky-500:  #4A90DC;   /* shapes and illustration only, as on light */
    --sky-600:  #5FA0E8;
    --sky-700:  #7DB3F0;   /* links and text under 24px — 7.9:1 on a card */
    --gold-400: #F5C242;   /* 10.5:1 on a card, so it may be text here */
    --gold-600: #F5C242;
    --gold-700: #F5C242;
    --slate-500: #93A0B2;  /* 6.5:1 on a card */
    --slate-600: #AAB6C6;
    --line:  #262C36;
    --line-strong: #39424F;
    --mist:  #12161C;
    --base:  #0F1319;
    --white: #171C24;      /* now the raised surface */

    --on-navy:       #DDE5F0;
    --on-navy-quiet: #A9B7C9;
    --on-navy-faint: #8593A5;
    --navy-line:     #232C38;
    --navy-line-2:   #35404F;
    --sky-tint:      #16273A;
    --gold-hover:    #FFD264;
    --gold-tint:     #2A2313;
    --amber-tint:    #31220F;
    --amber-ink:     #F0B27A;
    --pass-ok:       #3DD68C;   /* lifted for dark — 9.1:1 on --white */
    --header-bg:       rgba(15, 19, 25, .88);
    --header-bg-stuck: rgba(23, 28, 36, .94);

    --tone-navy-bg:   #0A0E14;
    --tone-navy-card: #12181F;
    --tone-navy-ink:  #F0F5FB;
  }
}

:root[data-theme='dark'] {
  color-scheme: dark;

  --navy-900: #E8EDF4;
  --navy-800: #CBD5E2;
  --navy-700: #AEBBCB;
  --sky-500:  #4A90DC;
  --sky-600:  #5FA0E8;
  --sky-700:  #7DB3F0;
  --gold-400: #F5C242;
  --gold-600: #F5C242;
  --gold-700: #F5C242;
  --slate-500: #93A0B2;
  --slate-600: #AAB6C6;
  --line:  #262C36;
  --line-strong: #39424F;
  --mist:  #12161C;
  --base:  #0F1319;
  --white: #171C24;

  --on-navy:       #DDE5F0;
  --on-navy-quiet: #A9B7C9;
  --on-navy-faint: #8593A5;
  --navy-line:     #232C38;
  --navy-line-2:   #35404F;
  --sky-tint:      #16273A;
  --gold-hover:    #FFD264;
  --gold-tint:     #2A2313;
  --amber-tint:    #31220F;
  --amber-ink:     #F0B27A;
  --pass-ok:       #3DD68C;   /* lifted for dark — 9.1:1 on --white */
  --header-bg:       rgba(15, 19, 25, .88);
  --header-bg-stuck: rgba(23, 28, 36, .94);

  --tone-navy-bg:   #0A0E14;
  --tone-navy-card: #12181F;
  --tone-navy-ink:  #F0F5FB;
}

/* ---------------------------------------------------------------- type */
body {
  font-family: var(--font);
  font-size: var(--t-body);
  line-height: 1.65;
  /* No negative tracking. -0.011em was drawn for Inter, which is wide enough to
     take it; pulling this face in makes text at reading size feel cramped. */
  letter-spacing: 0;
  color: var(--ink);
  background: var(--surface);
}

/*
  Headings are the serif. The tracking is looser than it was: -0.03em was drawn
  for Inter, and pulling a serif that tight closes its counters and turns the
  bracketed serifs into ink traps at display size.
*/
h1, h2, h3, h4 {
  font-family: var(--display);
  line-height: 1.18;
  letter-spacing: -0.012em;
  font-weight: 600;
  text-wrap: balance;
}
h1, .h1 { font-size: var(--t-h1); }
h2, .h2 { font-size: var(--t-h2); }
h3, .h3 { font-size: var(--t-h3); letter-spacing: -0.008em; }
.h1, .h2, .h3 { font-family: var(--display); }
.display { font-family: var(--display); font-size: var(--t-display); font-weight: 700; }

p { max-width: 68ch; }
.lead { font-size: var(--t-lead); color: var(--ink-soft); line-height: 1.6; }
.small { font-size: var(--t-small); }
.muted { color: var(--ink-quiet); }
strong, b { font-weight: 600; color: var(--ink); }

a { color: var(--sky-700); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { color: var(--hover-border); }

/* Mono utility — hashes, ARNs, paths and plan output. Labels use the sans. */
.mono {
  font-family: var(--mono);
  font-size: var(--t-small);
  letter-spacing: -0.01em;
  font-variant-ligatures: none;
}

/* Tabular figures. Belt and braces: not every stack honours the shorthand. */
.num {
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1, 'lnum' 1;
}

/* The eyebrow: a 40px rule, then a 12px uppercase label in sky-700. Set in the
   text face — the rule and the tracking are what make it a label, not the
   typeface. */
.eyebrow {
  display: flex;
  align-items: center;
  gap: .75rem;
  font-size: var(--t-micro);
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--sky-700);
}
.eyebrow::before {
  content: '';
  width: 40px;
  height: 1px;
  background: currentColor;
  flex: none;
}
.eyebrow.is-centred { justify-content: center; }
.tone-navy .eyebrow { color: var(--gold-400); }

/* Section index — "03 / 12" running down the page. Progression, zero JS. */
.index {
  font-family: var(--font);
  font-size: var(--t-micro);
  letter-spacing: .1em;
  color: var(--ink-quiet);
}
.index b { color: var(--ink); font-weight: 600; }

/* ---------------------------------------------------------------- a11y */
:focus-visible {
  outline: 2px solid var(--sky-700);
  outline-offset: 2px;
  border-radius: 2px;
}
.tone-navy :focus-visible { outline-color: var(--gold-400); }

.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; top: -100%; left: var(--s-2); z-index: 200;
  background: var(--tone-navy-bg); color: var(--tone-navy-ink);
  padding: .75rem 1.25rem; border-radius: var(--r-s);
}
.skip-link:focus { top: var(--s-2); color: var(--tone-navy-ink); }

/* ---------------------------------------------------------------- motion */
/* Entrances: 16px rise + fade, 500ms, --ease, 70ms stagger via --d. */
html.js [data-reveal] {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
  transition-delay: calc(var(--d, 0) * var(--stagger));
}
html.js [data-reveal].revealed { opacity: 1; transform: none; }

/* Self-drawing line art. Every stroke carries pathLength="1". */
.draw [data-draw] {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset 1.1s var(--ease);
  transition-delay: calc(var(--i, 0) * 90ms);
}
.draw.is-drawn [data-draw] { stroke-dashoffset: 0; }

/* Labels on a drawing appear only once the lines have landed. */
.draw [data-draw-label] {
  opacity: 0;
  transition: opacity .5s ease calc(.5s + var(--i, 0) * 90ms);
}
.draw.is-drawn [data-draw-label] { opacity: 1; }

/*
  Reduced motion renders the FINAL state immediately — never a slower animation.
  Toggles and controls stay operable; only the movement goes.
*/
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  html.js [data-reveal] { opacity: 1; transform: none; transition: none; }
  .draw [data-draw] { stroke-dashoffset: 0; transition: none; }
  .draw [data-draw-label] { opacity: 1; transition: none; }
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
  }
  .theme-toggle-icon,
  .theme-toggle:hover .theme-toggle-icon,
  .theme-toggle:focus-visible .theme-toggle-icon {
    transform: none !important;
  }
  .theme-toggle-sheen { transform: none !important; }
}
