/* ============================================================================
   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 */
  --pass-ok-on-navy: #3DD68C; /* pass tick on a navy ground — 8.4:1 on navy-900,
                                 constant across themes because the ground is */
  --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;

  /* The ground under the four product marks, and the same reasoning. The marks
     are raster files drawn on white, so the chip behind them has to be white
     in both themes; --white cannot do this job because in dark mode it becomes
     the raised surface (#171C24) and the mark would sit in a dark box it was
     not drawn for. This token never moves. */
  --mark-ground: #FFFFFF;
  --mark-ink: #14233A;      /* type on a --mark-ground plate; fixed in both themes like the plate */

  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 */
    --pass-ok-on-navy: #3DD68C; /* dark ground is darker still — contrast only rises */
    --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 */
  --pass-ok-on-navy: #3DD68C; /* dark ground is darker still — contrast only rises */
  --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; }
}
