/* ===== 00-tokens.css ===== */
/* Design tokens.
   Rebranding a site should mean editing THIS FILE ONLY. Nothing below this
   layer hardcodes a colour, a font family, or a spacing value.

   Contrast note: every foreground/background pair below is checked to meet
   WCAG AA (4.5:1 for body text, 3:1 for large text and UI borders). If you
   change --accent, re-check it against BOTH --bg and --surface — an accent
   that reads well on dark frequently fails on light. */

:root {
  /* --- palette ---------------------------------------------------------
     Taken from the Caminobits brand identity. BRAND BLUE #479BD5 on INK
     #0A1417, which the identity and this site already shared.

     Note for anyone reading the kit: the _ds/ folder inside it is a generic
     bundled system called Modernist, red on white in Archivo. That is not this
     brand and its tokens must not be used here.

     Contrast ratios below are measured against --bg. */
  --bg: #0A1417;            /* near-black teal */
  --surface: #0F1E20;       /* raised panels, cards */
  --surface-2: #142629;     /* input fields, wells */
  --text: #E6F1F0;          /* body text on --bg            -> 15.6:1 */
  --text-muted: #93AAA9;    /* secondary text on --bg       ->  7.1:1 */
  --text-faint: #6E8584;    /* captions on --bg             ->  4.6:1 AA */
  --accent: #479BD5;        /* BRAND BLUE, primary          ->  6.2:1 */
  --accent-ink: #0A1417;    /* ink placed ON --accent       ->  6.2:1 */
  --accent-2: #A9D3EE;      /* light blue, eyebrows and links -> 11.8:1
                               From the brand ramp. The kit's deep blue
                               #245F89 is only 2.7:1 on ink, so it belongs to
                               the mark's extrusion geometry and must never
                               carry text or borders here. */
  --border: #17302F;
  --border-strong: #244443;

  /* Status colours, used by form feedback. */
  --ok: #5FD3A6;
  --error: #FF9A8B;

  /* --- typography ----------------------------------------------------- */
  --font-sans: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  /* Chakra Petch is the identity's wordmark face. Used for the wordmark and
     headings; body copy stays on the sans stack, since a geometric display
     face is tiring at paragraph length. */
  --font-display: 'Chakra Petch', 'Inter', system-ui, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;

  /* Fluid type. clamp() means no per-breakpoint font-size overrides. */
  --step-hero: clamp(2.5rem, 1.6rem + 4.2vw, 5rem);
  --step-h1:   clamp(2rem, 1.4rem + 2.8vw, 3.5rem);
  --step-h2:   clamp(1.5rem, 1.2rem + 1.4vw, 2.25rem);
  --step-h3:   clamp(1.15rem, 1.05rem + 0.5vw, 1.4rem);
  --step-body: clamp(1rem, 0.97rem + 0.15vw, 1.0625rem);
  --step-sm:   0.875rem;
  --step-xs:   0.75rem;

  --leading-tight: 1.12;
  --leading-snug: 1.3;
  --leading-body: 1.65;

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

  /* Vertical rhythm between page sections. */
  --section-y: clamp(3.5rem, 2rem + 7vw, 7.5rem);

  /* --- layout --------------------------------------------------------- */
  /* Fluid container. Below 1180 the wrapper is already full width minus the
     gutter, so the floor keeps today's behaviour on laptops unchanged. Above
     that it grows with the viewport and stops at 1600, which is where line
     lengths and grid columns start to get uncomfortable regardless of how much
     screen is available.
       1180 viewport -> 1180   (floor, unchanged)
       1600          -> 1312
       1920          -> 1574
       2560          -> 1600   (ceiling) */
  --wrap: clamp(1180px, 82vw, 1600px);
  --wrap-narrow: 720px;   /* legal pages, article text */
  --gutter: clamp(1.25rem, 0.8rem + 2vw, 2.5rem);

  /* --- form / surface detail ------------------------------------------ */
  --radius-sm: 4px;
  --radius: 8px;
  --radius-lg: 16px;
  --shadow: 0 1px 2px rgb(0 0 0 / 0.30), 0 8px 24px rgb(0 0 0 / 0.20);

  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --dur: 220ms;
  --dur-slow: 620ms;
}

/* Light theme. Applied with <body class="theme-light">, or flip the values in
   :root if the whole site is light. Contrast re-checked for this ground. */
.theme-light {
  --bg: #F6FAFA;
  --surface: #FFFFFF;
  --surface-2: #ECF3F3;
  --text: #0A1417;          /* on --bg -> 16.9:1 */
  --text-muted: #4A5C5C;    /* on --bg ->  7.4:1 */
  --text-faint: #5E7070;    /* on --bg ->  5.3:1 AA */
  --accent: #245F89;        /* on --bg ->  6.5:1 AA. BRAND BLUE #479BD5 is
                               only 2.9:1 on this ground, so the deeper brand
                               ramp step carries the light theme. */
  --accent-ink: #FFFFFF;    /* on --accent -> 6.8:1 */
  --accent-2: #215782;      /* on --bg ->  7.3:1 AA */
  --border: #DCE7E7;
  --border-strong: #BFD0D0;
  --shadow: 0 1px 2px rgb(10 20 23 / 0.06), 0 8px 24px rgb(10 20 23 / 0.08);
}


/* ===== 10-base.css ===== */
/* Chakra Petch, the identity's wordmark face. Self hosted rather than loaded
   from Google's CDN: that would add a processor the privacy policy does not
   declare, and would make a third party request on every page load. Latin
   subset only, since nothing on the site renders Thai or Vietnamese. */
@font-face {
  font-family: 'Chakra Petch';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/assets/fonts/chakra-petch-500.woff2?v=3b139cf8') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+20AC, U+2122;
}
@font-face {
  font-family: 'Chakra Petch';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/assets/fonts/chakra-petch-600.woff2?v=5d46de65') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+20AC, U+2122;
}

/* Reset + element defaults. */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Anchored headings clear the sticky header. */
  scroll-padding-top: 5rem;
  /* Set on html as well as body. Background normally propagates from body to
     the canvas, but not once anything gives html its own background — and the
     overscroll ("rubber band") area past the end of the page is painted from
     html, so without this it flashes the browser default on a bounce scroll. */
  background: var(--bg);
  min-height: 100%;
  /* Tell the browser this is a dark UI, so form controls, scrollbars and the
     overscroll area match instead of rendering light. */
  color-scheme: dark;
}
.theme-light { color-scheme: light; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--step-body);
  line-height: var(--leading-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;

  /* Sticky footer. Without this a short page (404, a thin contact page, or any
     page on a tall monitor) ends wherever its content ends, leaving the footer
     floating mid-window with dead space beneath it.
     dvh accounts for mobile browser chrome; vh is the fallback for browsers
     that do not support it. */
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

h1, h2, h3, h4 {
  margin: 0 0 var(--sp-4);
  font-family: var(--font-display);
  /* Chakra Petch runs wider than Inter, so the display tracking is pulled in
     slightly to keep headline measure comparable. */
  letter-spacing: -0.02em;
  font-weight: 600;
  line-height: var(--leading-tight);
  letter-spacing: -0.015em;
  text-wrap: balance;   /* avoids a single orphaned word on a heading line */
}

h1 { font-size: var(--step-h1); }
h2 { font-size: var(--step-h2); }
h3 { font-size: var(--step-h3); line-height: var(--leading-snug); }

p, ul, ol { margin: 0 0 var(--sp-4); }
p { max-width: 68ch; text-wrap: pretty; }

a { color: inherit; text-decoration-color: var(--border-strong); text-underline-offset: 0.18em; }
a:hover { text-decoration-color: currentColor; }

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

hr { border: 0; border-top: 1px solid var(--border); margin: var(--sp-7) 0; }

code, pre { font-family: var(--font-mono); font-size: 0.9em; }

small { font-size: var(--step-sm); }

/* Focus. michagency shipped `outline: none` on inputs with no replacement —
   a straight WCAG 2.4.7 failure. Never remove this block. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
/* Only suppress the default ring where :focus-visible is supported, so
   keyboard users always keep an indicator. */
:focus:not(:focus-visible) { outline: none; }

::selection { background: var(--accent); color: var(--accent-ink); }

/* Respect the OS-level reduced-motion setting. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}


/* ===== 20-layout.css ===== */
/* Page scaffolding: wrappers, header, footer, section rhythm, grids. */

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

.section { padding-block: var(--section-y); }
/* Consecutive sections share one gap rather than stacking two. */
.section + .section { padding-top: 0; }
/* A bordered section keeps its top padding even when it follows another
   section, or the rule sits directly on the heading. The compound selector is
   required: `.section + .section` is two classes and would otherwise win on
   specificity and zero the padding out. */
.section-bordered,
.section + .section.section-bordered {
  border-top: 1px solid var(--border);
  padding-top: var(--section-y);
}

/* --- skip link ---------------------------------------------------------- */
.skip-link {
  position: absolute;
  left: var(--sp-4);
  top: -100px;
  z-index: 100;
  padding: var(--sp-3) var(--sp-4);
  background: var(--accent);
  color: var(--accent-ink);
  border-radius: var(--radius);
  text-decoration: none;
  transition: top var(--dur) var(--ease);
}
.skip-link:focus { top: var(--sp-4); }

/* --- header ------------------------------------------------------------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border);
}
.site-header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-5);
  min-height: 4.25rem;
}

.brand {
  font-weight: 600;
  letter-spacing: -0.02em;
  font-size: 1.0625rem;
  text-decoration: none;
}

.nav-list {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  margin: 0;
  padding: 0;
  list-style: none;
}
.nav-list a {
  font-size: var(--step-sm);
  color: var(--text-muted);
  text-decoration: none;
  transition: color var(--dur) var(--ease);
}
.nav-list a:hover { color: var(--text); }
/* The CTA sits on an --accent fill, so it must take --accent-ink at rest, not
   only on hover — inheriting --text-muted here fails contrast on the button. */
.nav-list a.btn, .nav-list a.btn:hover { color: var(--accent-ink); }

/* Hamburger. Hidden on desktop; the checkbox-free JS toggle lives in site.js. */
.nav-toggle {
  display: none;
  width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: inherit;
  cursor: pointer;
}
.nav-toggle-bar,
.nav-toggle-bar::before,
.nav-toggle-bar::after {
  display: block;
  width: 1rem;
  height: 1.5px;
  margin-inline: auto;
  background: currentColor;
  transition: transform var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
.nav-toggle-bar::before, .nav-toggle-bar::after { content: ''; }
.nav-toggle-bar::before { transform: translateY(-5px); }
.nav-toggle-bar::after  { transform: translateY(3.5px); }

.nav-toggle[aria-expanded='true'] .nav-toggle-bar { background: transparent; }
.nav-toggle[aria-expanded='true'] .nav-toggle-bar::before { transform: translateY(0) rotate(45deg); }
.nav-toggle[aria-expanded='true'] .nav-toggle-bar::after  { transform: translateY(-1.5px) rotate(-45deg); }

@media (max-width: 760px) {
  .nav-toggle { display: block; }
  .nav-list {
    position: absolute;
    inset: 100% 0 auto;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: var(--sp-3) var(--gutter) var(--sp-5);
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    /* Collapsed by default on small screens. */
    display: none;
  }
  .nav-list.is-open { display: flex; }
  .nav-list li { padding-block: var(--sp-3); border-bottom: 1px solid var(--border); }
  .nav-list li:last-child { border-bottom: 0; padding-top: var(--sp-4); }
  .nav-list a { font-size: 1rem; }
  .site-nav { position: static; }
  .site-header-inner { position: relative; }
}

/* Body is a flex column (see 10-base.css), so the main region absorbs the
   leftover height and pins the footer to the bottom on short pages.
   `flex: 1 0 auto` rather than `1 1 auto`: main must be allowed to grow but
   never to shrink below its content. The last selector covers the `bare`
   layout, which has no <main>. */
main,
body > section:only-of-type {
  flex: 1 0 auto;
}

/* --- footer ------------------------------------------------------------- */
.site-footer {
  flex-shrink: 0;
  margin-top: var(--section-y);
  border-top: 1px solid var(--border);
  padding-block: var(--sp-7) var(--sp-5);
}
.site-footer-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: start;
  justify-content: space-between;
  gap: var(--sp-6);
}
.site-footer-brand p { margin: var(--sp-2) 0 0; font-size: var(--step-sm); }
.site-footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-5);
  font-size: var(--step-sm);
}
.site-footer-links a, .site-footer-links button {
  color: var(--text-muted);
  text-decoration: none;
  transition: color var(--dur) var(--ease);
}
.site-footer-links a:hover, .site-footer-links button:hover { color: var(--text); }
.site-footer-legal { margin-top: var(--sp-6); }

/* --- grids -------------------------------------------------------------- */
.grid { display: grid; gap: var(--sp-6); }
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }

/* Auto-fit grid: no breakpoint needed, cards reflow on their own. Prefer this
   over the fixed counts above wherever the design allows. */
.grid-auto { grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr)); }

@media (max-width: 900px) {
  .grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 620px) {
  .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; }
}

/* Split hero / feature row. */
.split {
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  gap: clamp(var(--sp-6), 4vw, var(--sp-8));
  align-items: center;
}
@media (max-width: 860px) { .split { grid-template-columns: 1fr; } }

/* --- language switcher ---------------------------------------------------
   Rendered as a plain list of links, not a <select>. A select needs JS to
   navigate and is invisible to crawlers; links are crawlable, which is the
   point of publishing translations at all. */
.lang-switch > ul {
  display: flex;
  gap: var(--sp-3);
  align-items: center;
  margin: 0;
  padding: 0 0 0 var(--sp-4);
  list-style: none;
  border-left: 1px solid var(--border);
}
.lang-switch a {
  font-family: var(--font-mono);
  font-size: var(--step-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-faint);
}
.lang-switch a:hover { color: var(--text); }
.lang-switch a[aria-current='true'] { color: var(--accent); }

@media (max-width: 760px) {
  .lang-switch > ul {
    flex-wrap: wrap;
    padding: var(--sp-2) 0 0;
    border-left: 0;
    border-top: 1px solid var(--border);
    gap: var(--sp-4);
  }
  .lang-switch a { font-size: var(--step-sm); }
}

/* Picklist switcher. Styled minimally rather than replaced with a custom
   dropdown: a native <select> gets keyboard behaviour, screen-reader support,
   and a usable mobile picker for free. */
.lang-switch select {
  appearance: none;
  padding: 0.35em 1.6em 0.35em 0.6em;
  background: transparent;
  color: var(--text-muted);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font: inherit;
  font-family: var(--font-mono);
  font-size: var(--step-xs);
  letter-spacing: 0.06em;
  cursor: pointer;
  /* Chevron drawn as a background image so no icon font or SVG file is needed. */
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 0.85em top 55%, right 0.55em top 55%;
  background-size: 4px 4px, 4px 4px;
  background-repeat: no-repeat;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.lang-switch select:hover { color: var(--text); border-color: var(--border-strong); }
/* The dropdown list itself is drawn by the OS, so give options explicit colours
   or they inherit a transparent background on some platforms. */
.lang-switch option { background: var(--surface); color: var(--text); }

.lang-links { display: flex; gap: var(--sp-3); list-style: none; margin: 0; padding: 0; }
.lang-links a { font-size: var(--step-xs); color: var(--text-faint); }

/* Suggest-mode bar. */
.lang-suggest {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-4);
  padding: var(--sp-3) var(--gutter);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  font-size: var(--step-sm);
}
.lang-suggest a { color: var(--accent); }
.lang-suggest button { color: var(--text-faint); font-size: 1.1rem; line-height: 1; }
.lang-suggest button:hover { color: var(--text); }


/* ===== 30-components.css ===== */
/* Reusable pieces: buttons, cards, forms, hero, prose, reveal animations. */

/* --- eyebrow / label ---------------------------------------------------- */
.eyebrow {
  display: inline-block;
  margin-bottom: var(--sp-3);
  font-family: var(--font-mono);
  font-size: var(--step-xs);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}

.lead { font-size: clamp(1.0625rem, 1rem + 0.4vw, 1.25rem); color: var(--text-muted); }
.muted { color: var(--text-muted); }
.faint { color: var(--text-faint); }

/* --- buttons ------------------------------------------------------------ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: 0.8em 1.5em;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--accent-ink);
  font: inherit;
  font-size: var(--step-sm);
  font-weight: 500;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  transition: transform var(--dur) var(--ease), opacity var(--dur) var(--ease),
              background-color var(--dur) var(--ease);
}
.btn:hover { opacity: 0.9; transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn[disabled] { opacity: 0.55; cursor: not-allowed; transform: none; }

.btn-outline {
  background: transparent;
  color: var(--text);
  border-color: var(--border-strong);
}
.btn-outline:hover { border-color: var(--text); opacity: 1; }

.btn-sm { padding: 0.6em 1.1em; font-size: var(--step-xs); }
.btn-lg { padding: 0.95em 1.9em; font-size: var(--step-body); }

/* A button that reads as a link — used for the cookie-preferences trigger,
   which must be a <button> for accessibility but should not look like one. */
.linklike {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

/* --- cards -------------------------------------------------------------- */
.card {
  padding: var(--sp-6);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.card:hover { border-color: var(--border-strong); }
.card h3 { margin-bottom: var(--sp-3); }
.card p:last-child { margin-bottom: 0; }

/* Whole-card link: the anchor covers the card without nesting interactive
   elements inside another anchor. */
.card-link { position: relative; text-decoration: none; display: block; }
.card-link:hover { transform: translateY(-2px); }

.card-num {
  font-family: var(--font-mono);
  font-size: var(--step-xs);
  color: var(--accent);
  letter-spacing: 0.1em;
}

/* --- hero --------------------------------------------------------------- */
.hero { padding-block: clamp(var(--sp-8), 4rem + 6vw, var(--sp-10)) var(--section-y); }
.hero h1 { font-size: var(--step-hero); max-width: 18ch; }
.hero .lead { max-width: 56ch; margin-top: var(--sp-5); }
.hero-actions { display: flex; flex-wrap: wrap; gap: var(--sp-4); margin-top: var(--sp-6); }

/* --- stats -------------------------------------------------------------- */
.stat-value {
  display: block;
  font-size: clamp(2rem, 1.5rem + 2vw, 3rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1;
}
.stat-label { font-size: var(--step-sm); color: var(--text-muted); }

/* --- forms -------------------------------------------------------------- */
.form { display: grid; gap: var(--sp-5); max-width: 34rem; }
.field { display: grid; gap: var(--sp-2); }
.field label { font-size: var(--step-sm); font-weight: 500; }

.field input, .field textarea, .field select {
  width: 100%;
  padding: 0.75em 0.9em;
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  font: inherit;
  font-size: var(--step-body);
  transition: border-color var(--dur) var(--ease);
}
.field textarea { min-height: 8rem; resize: vertical; }
.field input:hover, .field textarea:hover { border-color: var(--text-faint); }
/* NB: no `outline: none` here — the global :focus-visible ring in 10-base.css
   is the accessible focus indicator and must survive. */
.field input:focus, .field textarea:focus { border-color: var(--accent); }

.field-hint { font-size: var(--step-xs); color: var(--text-faint); }
.field-error { font-size: var(--step-xs); color: var(--error); }
.field input[aria-invalid='true'], .field textarea[aria-invalid='true'] { border-color: var(--error); }

/* Honeypot: off-screen rather than display:none, so bots that skip hidden
   fields still fill it. Never remove tabindex="-1" / aria-hidden on the input. */
.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form-status { font-size: var(--step-sm); min-height: 1.5em; }
.form-status[data-state='ok'] { color: var(--ok); }
.form-status[data-state='error'] { color: var(--error); }

/* --- prose (legal pages, articles) -------------------------------------- */
.prose { max-width: var(--wrap-narrow); }
.prose h2 { margin-top: var(--sp-7); }
.prose h3 { margin-top: var(--sp-6); }
.prose ul, .prose ol { padding-left: 1.25rem; }
.prose li { margin-bottom: var(--sp-2); }
.prose a { color: var(--accent); }
.prose table { width: 100%; border-collapse: collapse; margin-bottom: var(--sp-5); }
.prose th, .prose td {
  padding: var(--sp-3);
  border-bottom: 1px solid var(--border);
  text-align: left;
  font-size: var(--step-sm);
  vertical-align: top;
}
/* Wide tables scroll inside their own box rather than the page. */
.table-scroll { overflow-x: auto; }

/* --- reveal animations --------------------------------------------------
   .is-armed is set by JS only. Without JS — or with reduced motion — content
   is visible at rest and nothing here applies. Content must never depend on
   JS to become visible. */
.is-armed .reveal {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity var(--dur-slow) var(--ease), transform var(--dur-slow) var(--ease);
}
.is-armed .reveal.is-in { opacity: 1; transform: none; }


/* ===== 40-utilities.css ===== */
/* Single-purpose helpers. Keep this layer small — if a utility combination
   repeats three times, it belongs in 30-components.css as a named class. */

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

.text-center { text-align: center; }
.text-balance { text-wrap: balance; }
.mx-auto { margin-inline: auto; }

.mt-0 { margin-top: 0; }   .mb-0 { margin-bottom: 0; }
.mt-4 { margin-top: var(--sp-4); }  .mb-4 { margin-bottom: var(--sp-4); }
.mt-6 { margin-top: var(--sp-6); }  .mb-6 { margin-bottom: var(--sp-6); }
.mt-7 { margin-top: var(--sp-7); }  .mb-7 { margin-bottom: var(--sp-7); }

.stack > * + * { margin-top: var(--sp-4); }
.stack-lg > * + * { margin-top: var(--sp-6); }

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

.hide { display: none !important; }
@media (max-width: 760px) { .hide-sm { display: none !important; } }
@media (min-width: 761px) { .hide-lg { display: none !important; } }

.full-bleed { width: 100vw; margin-inline: calc(50% - 50vw); }

/* Constrain a media box to a ratio so images never cause layout shift. */
.ratio-16-9 { aspect-ratio: 16 / 9; object-fit: cover; width: 100%; }
.ratio-4-3  { aspect-ratio: 4 / 3;  object-fit: cover; width: 100%; }
.ratio-1-1  { aspect-ratio: 1 / 1;  object-fit: cover; width: 100%; }

@media print {
  .site-header, .site-footer, .skip-link, .nav-toggle { display: none !important; }
  body { background: #fff; color: #000; }
  a[href^='http']::after { content: ' (' attr(href) ')'; font-size: 0.85em; }
}


/* ===== 50-site.css ===== */
/* Caminobits-specific layer.
   Sits on top of the template layers so those stay generic and upgradeable.
   Anything here is unique to this site. */

/* Eyebrows and platform references use the Salesforce-family blue; teal stays
   the primary action colour. Two accents give hierarchy without a third hue. */
.eyebrow { color: var(--accent-2); }

.btn-outline { border-color: var(--accent-2); color: var(--accent-2); }
.btn-outline:hover { border-color: var(--accent); color: var(--accent); }

/* --- client wall --------------------------------------------------------
   Names rendered as type, greyscale, uniform weight. No logos are used: the
   engagements below were largely delivered through consultancies, so a logo
   wall would imply a direct client relationship and an endorsement that was
   never given. Text keeps every name without making that claim. */
.clients {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4) var(--sp-6);
  align-items: center;
  list-style: none;
  padding: 0;
  margin: var(--sp-6) 0 0;
}
.clients li {
  font-size: var(--step-sm);
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--text-faint);
  filter: grayscale(1);
  transition: color var(--dur) var(--ease);
}
.clients li:hover { color: var(--text-muted); }

/* Logo variant, for when licensed logo files exist. Greyscale at rest, colour
   on hover. Add <img class="client-logo"> inside the <li>. */
.client-logo {
  height: 26px;
  width: auto;
  filter: grayscale(1) brightness(1.6);
  opacity: 0.7;
  transition: opacity var(--dur) var(--ease), filter var(--dur) var(--ease);
}
.client-logo:hover { opacity: 1; filter: grayscale(0); }

/* --- capability matrix --------------------------------------------------- */
.matrix { display: grid; gap: var(--sp-5); }
.matrix-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--sp-4);
  align-items: baseline;
  padding-bottom: var(--sp-4);
  border-bottom: 1px solid var(--border);
}
.matrix-row:last-child { border-bottom: 0; }
.matrix-name { font-weight: 500; }
.matrix-detail { display: block; font-size: var(--step-sm); color: var(--text-muted); margin-top: 2px; }
.matrix-years {
  font-family: var(--font-mono);
  font-size: var(--step-xs);
  color: var(--accent-2);
  white-space: nowrap;
}

/* --- timeline ------------------------------------------------------------ */
.timeline { list-style: none; padding: 0; margin: var(--sp-6) 0 0; }
.timeline li {
  display: grid;
  grid-template-columns: 7.5rem 1fr;
  gap: var(--sp-5);
  padding-bottom: var(--sp-6);
  border-left: 1px solid var(--border);
  padding-left: var(--sp-5);
  margin-left: var(--sp-2);
}
.timeline li:last-child { padding-bottom: 0; }
.timeline-when {
  font-family: var(--font-mono);
  font-size: var(--step-xs);
  color: var(--accent-2);
  letter-spacing: 0.05em;
}
.timeline h3 { margin-bottom: var(--sp-2); font-size: 1.0625rem; }
.timeline p { margin: 0; font-size: var(--step-sm); color: var(--text-muted); }

@media (max-width: 620px) {
  .timeline li { grid-template-columns: 1fr; gap: var(--sp-2); }
  .matrix-row { grid-template-columns: 1fr; }
}

/* --- pull quote / emphasis block ----------------------------------------- */
.pull {
  border-left: 2px solid var(--accent);
  padding-left: var(--sp-5);
  font-size: clamp(1.125rem, 1rem + 0.7vw, 1.4rem);
  line-height: var(--leading-snug);
  color: var(--text);
  max-width: 46ch;
}

/* --- service and industry entries ---------------------------------------
   Long-form entries rather than cards: each carries three paragraphs, which a
   card grid would either truncate or make unreadable at narrow widths. */
.stack-xl > * + * { margin-top: var(--sp-8); }

.service, .industry {
  display: grid;
  grid-template-columns: minmax(14rem, 22rem) 1fr;
  gap: clamp(var(--sp-5), 3vw, var(--sp-7));
  padding-bottom: var(--sp-7);
  border-bottom: 1px solid var(--border);
}
/* Scoped to .industry only. Services became self contained cards, and this
   rule was stripping the bottom padding off the last card in every group. */
.stack-xl > .industry:last-child { border-bottom: 0; padding-bottom: 0; }

.service-head, .industry-head { position: sticky; top: 6rem; align-self: start; }
.service-head h3, .industry-head h3 { margin: var(--sp-2) 0 var(--sp-2); }

.service-duration, .industry-where {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: var(--step-xs);
  letter-spacing: 0.06em;
  color: var(--text-faint);
}

.service-body > p, .industry-body > p { margin-bottom: var(--sp-4); }
.service-body > p:last-child, .industry-body > p:last-child { margin-bottom: 0; }

/* The problem statement leads, so it carries the accent rule. */
.service-problem {
  border-left: 2px solid var(--accent-2);
  padding-left: var(--sp-4);
  color: var(--text);
}
/* The outcome closes the entry and is what a buyer scans for. */
.service-outcome {
  padding: var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: var(--step-sm);
  color: var(--text-muted);
}

@media (max-width: 860px) {
  .service, .industry { grid-template-columns: 1fr; gap: var(--sp-4); }
  .service-head, .industry-head { position: static; }
}

/* --- AI page ------------------------------------------------------------- */
.pipeline-item {
  padding: var(--sp-5);
  border-top: 2px solid var(--border-strong);
}
.pipeline-item h3 { font-size: 1rem; margin-bottom: var(--sp-2); }
.pipeline-item p { font-size: var(--step-sm); margin: 0; }

.devops-item { padding-bottom: var(--sp-4); border-bottom: 1px solid var(--border); }
.devops-item:last-child { border-bottom: 0; padding-bottom: 0; }
.devops-item h3 { font-size: 1rem; margin-bottom: var(--sp-2); }
.devops-item p { font-size: var(--step-sm); margin: 0; }

/* --- homepage hero visual ------------------------------------------------
   The generated background is dark and sparse, with its light concentrated
   upper right. Text sits on the left, so a left-weighted scrim keeps contrast
   well clear of the AA threshold without flattening the image.

   The image is decorative, so it is a background rather than an <img>: it
   carries no information a screen reader needs, and this keeps it out of the
   accessibility tree entirely. */
.hero-visual { position: relative; isolation: isolate; overflow: hidden; }

.hero-visual::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -2;
  background-image: url('/assets/img/hero-bg.svg?v=8938176f');
  background-size: cover;
  background-position: 60% center;
  opacity: 0.85;
}

/* Two scrims: one horizontal so the copy column stays dark, one vertical to
   fade the image out into the section below rather than cutting it off. */
.hero-visual::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(100deg, var(--bg) 22%, color-mix(in srgb, var(--bg) 70%, transparent) 52%, transparent 78%),
    linear-gradient(to bottom, transparent 40%, var(--bg) 96%);
}

.hero-visual .hero-inner { position: relative; }

@media (max-width: 860px) {
  /* Narrow screens have no free column, so the image drops back further and
     the scrim covers more of it. */
  .hero-visual::before { opacity: 0.5; background-position: 72% center; }
  .hero-visual::after {
    background:
      linear-gradient(180deg, color-mix(in srgb, var(--bg) 55%, transparent) 0%, var(--bg) 78%);
  }
}

@media (prefers-reduced-motion: no-preference) {
  .hero-visual::before { transition: opacity var(--dur-slow) var(--ease); }
}

/* Bottleneck entries: numbered visual weight is unnecessary here, the titles
   are the scan targets. A left rule ties them to the problem framing used on
   the services page. */
.bottleneck {
  padding-left: var(--sp-5);
  border-left: 2px solid var(--border-strong);
}
.bottleneck h3 { font-size: 1.0625rem; margin-bottom: var(--sp-2); }
.bottleneck p { font-size: var(--step-sm); margin: 0; }

/* --- effort chart --------------------------------------------------------
   Two stacked bars comparing composition, not magnitude. Deliberately no
   printed numbers on the bars: the table underneath carries the exact values,
   which keeps the marks clean and gives an accessible equivalent. */
.chart { margin: 0; }
.chart-title {
  font-family: var(--font-mono);
  font-size: var(--step-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: var(--sp-5);
}

.chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-5);
  list-style: none;
  padding: 0;
  margin: 0 0 var(--sp-6);
  font-size: var(--step-sm);
  color: var(--text-muted);
}
.chart-legend li { display: flex; align-items: center; gap: var(--sp-2); }

.swatch {
  display: inline-block;
  width: 10px; height: 10px;
  border-radius: 2px;
  flex: 0 0 auto;
}

.chart-row {
  display: grid;
  grid-template-columns: minmax(9rem, 12rem) 1fr;
  gap: var(--sp-4);
  align-items: center;
  margin-bottom: var(--sp-4);
}
.chart-label { font-size: var(--step-sm); color: var(--text-muted); }
/* Fixed height rather than aspect ratio: the bar is a proportion strip, so its
   height should not scale with viewport width. */
.chart-bar { width: 100%; height: 26px; display: block; }

.chart-table { width: 100%; border-collapse: collapse; font-size: var(--step-sm); }
.chart-table th, .chart-table td {
  padding: var(--sp-3);
  border-bottom: 1px solid var(--border);
  text-align: left;
}
.chart-table thead th { color: var(--text-faint); font-weight: 500; }
.chart-table tbody th { font-weight: 400; display: flex; align-items: center; gap: var(--sp-3); }
.chart-table td { color: var(--text-muted); font-family: var(--font-mono); }

/* The caption states the provenance. It is not decorative: without it the
   chart implies a measurement that was never taken. */
.chart-caption {
  margin-top: var(--sp-5);
  font-size: var(--step-xs);
  line-height: 1.6;
  color: var(--text-faint);
  max-width: 60ch;
  border-left: 2px solid var(--border-strong);
  padding-left: var(--sp-4);
}

@media (max-width: 620px) {
  .chart-row { grid-template-columns: 1fr; gap: var(--sp-2); }
}

/* Inner-page heroes carry the same image at lower intensity and a different
   crop, so the pages feel related without repeating one composition five
   times. The homepage keeps the full-strength version. */
.hero-visual-sub::before { opacity: 0.4; background-position: 30% 40%; }
.hero-visual-sub::after {
  background:
    linear-gradient(100deg, var(--bg) 30%, color-mix(in srgb, var(--bg) 78%, transparent) 60%, color-mix(in srgb, var(--bg) 45%, transparent) 85%),
    linear-gradient(to bottom, transparent 30%, var(--bg) 92%);
}
/* Vary the crop per page so consecutive navigation does not look like the
   image failed to change. */
body.page-ai .hero-visual-sub::before        { background-position: 76% 30%; }
body.page-expertise .hero-visual-sub::before { background-position: 12% 60%; transform: scaleX(-1); }
body.page-about .hero-visual-sub::before     { background-position: 55% 75%; }
body.page-contact .hero-visual-sub::before   { opacity: 0.3; background-position: 88% 50%; }

@media (max-width: 860px) {
  .hero-visual-sub::before { opacity: 0.22; }
}

/* --- skill bars ----------------------------------------------------------
   A ranked bar per capability. The value is printed at the end of every row
   rather than only on hover, so the chart is readable without interaction and
   the numbers are never carried by bar length alone. */
.skillbars { display: grid; gap: var(--sp-3); }
.skillbar {
  display: grid;
  grid-template-columns: minmax(10rem, 16rem) 1fr auto;
  gap: var(--sp-4);
  align-items: center;
}
.skillbar-label { font-size: var(--step-sm); }
.skillbar-track {
  height: 10px;
  background: var(--surface-2);
  border-radius: 5px;
  overflow: hidden;
}
.skillbar-fill { display: block; height: 100%; border-radius: 5px; }
.skillbar-value {
  font-family: var(--font-mono);
  font-size: var(--step-xs);
  color: var(--text-muted);
  white-space: nowrap;
}

/* --- experience timeline -------------------------------------------------
   Bars positioned absolutely inside a percentage track. The geometry is
   computed at build time from the actual years, so a bar cannot drift from the
   dates printed beside it. */
.gantt { display: grid; gap: var(--sp-3); }
.gantt-row, .gantt-axis {
  display: grid;
  grid-template-columns: minmax(11rem, 18rem) 1fr auto;
  gap: var(--sp-4);
  align-items: center;
}
.gantt-label { font-size: var(--step-sm); }
.gantt-track { position: relative; height: 18px; }
.gantt-track::before {
  content: '';
  position: absolute;
  inset: 50% 0 auto;
  height: 1px;
  background: var(--border);
}
.gantt-bar {
  position: absolute;
  top: 3px;
  height: 12px;
  border-radius: 6px;
  min-width: 6px;
}
.gantt-span {
  font-family: var(--font-mono);
  font-size: var(--step-xs);
  color: var(--text-faint);
  white-space: nowrap;
}
.gantt-axis { margin-top: var(--sp-2); }
.gantt-axis .gantt-track { height: auto; }
.gantt-axis .gantt-track::before { display: none; }
.gantt-tick {
  position: absolute;
  transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: var(--step-xs);
  color: var(--text-faint);
}

@media (max-width: 760px) {
  .skillbar, .gantt-row, .gantt-axis { grid-template-columns: 1fr auto; }
  .skillbar-track, .gantt-track { grid-column: 1 / -1; order: 3; }
  .gantt-axis .gantt-label, .gantt-axis .gantt-span { display: none; }
}

/* --- countries -----------------------------------------------------------
   The dot map sits behind at low opacity as texture. Its highlighted points
   are decorative and do not correspond to these countries, which is why the
   list carries the actual information and the image is dimmed well below the
   point where anyone would read position from it. */
.countries-section { position: relative; isolation: isolate; overflow: hidden; }
.countries-section::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -2;
  background: url('/assets/img/map-bg.svg?v=cf217c6d') center/cover no-repeat;
  opacity: 0.3;
}
.countries-section::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(180deg, var(--bg) 0%, color-mix(in srgb, var(--bg) 60%, transparent) 40%, var(--bg) 100%);
}

.countries {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
  gap: var(--sp-5);
  list-style: none;
  padding: 0;
  margin: 0;
}
.country {
  padding: var(--sp-4) 0 var(--sp-4) var(--sp-4);
  border-left: 2px solid var(--accent-2);
}
.country-name { display: block; font-weight: 500; }
.country-detail { display: block; font-size: var(--step-sm); color: var(--text-muted); margin-top: 2px; }

/* --- progressive disclosure ----------------------------------------------
   Services use native <details>. The problem statement is the summary, so the
   page scans as a list of problems a reader recognises, and the method opens
   on demand. The text stays in the DOM either way, so nothing is hidden from
   search, only from the initial read. */
details.service {
  display: block;
  grid-template-columns: none;
  padding: clamp(var(--sp-5), 2.4vw, var(--sp-7));
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
details.service:hover { border-color: var(--border-strong); }
details.service[open] { background: color-mix(in srgb, var(--surface) 70%, var(--bg)); }

details.service summary {
  cursor: pointer;
  list-style: none;
  display: block;
}
/* The default disclosure triangle is replaced by the chevron on .service-more. */
details.service summary::-webkit-details-marker { display: none; }
details.service summary::marker { content: ''; }

.service-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-4);
  margin-bottom: var(--sp-4);
}
.service-title { font-size: var(--step-h3); font-weight: 600; letter-spacing: -0.015em; }
.service-duration { margin-left: auto; }

details.service .service-problem {
  display: block;
  border-left: 2px solid var(--accent-2);
  padding-left: var(--sp-4);
  color: var(--text-muted);
  margin-bottom: var(--sp-5);
  max-width: 78ch;
}

.service-more {
  /* inline-flex leaves the baseline gap looking tighter than the measured
     padding, so the affordance carries its own top margin. */
  margin-top: var(--sp-2);
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-family: var(--font-mono);
  font-size: var(--step-xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
}
.service-more::after {
  content: '';
  width: 6px; height: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg) translate(-1px, -1px);
  transition: transform var(--dur) var(--ease);
}
details.service[open] .service-more::after { transform: rotate(-135deg) translate(-2px, -2px); }
details.service[open] .service-more { color: var(--text-faint); }

.service-body { padding-top: var(--sp-5); }
.service-body > p { margin-bottom: var(--sp-4); max-width: 72ch; }
.service-body > p:last-child { margin-bottom: 0; }

/* The panel slides open rather than appearing. Only applied where motion is
   welcome; otherwise <details> toggles instantly, which is correct. */
@media (prefers-reduced-motion: no-preference) {
  details.service[open] .service-body {
    animation: disclose var(--dur-slow) var(--ease);
  }
  @keyframes disclose {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: none; }
  }
}

/* Focus must be visible on the summary, which is the interactive element. */
details.service summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
  border-radius: var(--radius);
}

/* --- chart draw-in --------------------------------------------------------
   Bars start collapsed and grow to the width the build computed. `is-pending`
   is added by JS, so without it the bars are simply at their correct size. */
.skillbar-fill, .gantt-bar { transition: width var(--dur-slow) var(--ease), transform var(--dur-slow) var(--ease); }
.skillbar-fill.is-pending { width: 0 !important; }
.gantt-bar.is-pending { transform: scaleX(0); transform-origin: left center; }
.chart-bar rect { transition: transform var(--dur-slow) var(--ease); transform-origin: left center; }
.chart-bar rect.is-pending { transform: scaleX(0); }

/* --- interaction polish --------------------------------------------------- */
.card, .pipeline-item, .bottleneck, .country {
  transition: transform var(--dur) var(--ease), border-color var(--dur) var(--ease),
              background-color var(--dur) var(--ease);
}
.card:hover, .pipeline-item:hover, .bottleneck:hover { transform: translateY(-2px); }
.pipeline-item:hover, .bottleneck:hover { border-color: var(--accent); }
.country:hover { border-left-color: var(--accent); }

/* Client names lift out of greyscale one at a time on hover. */
.clients li { transition: color var(--dur) var(--ease), transform var(--dur) var(--ease); }
.clients li:hover { color: var(--text); transform: translateY(-1px); }

/* Buttons get a travelling arrow rather than a colour change alone. */
.btn { position: relative; }
a.btn-outline::after { content: ''; }

@media (prefers-reduced-motion: reduce) {
  .card:hover, .pipeline-item:hover, .bottleneck:hover, .clients li:hover { transform: none; }
  .skillbar-fill.is-pending, .gantt-bar.is-pending, .chart-bar rect.is-pending {
    width: revert; transform: none;
  }
}

/* --- about: practice model and founder ----------------------------------- */
.model-point { padding-left: var(--sp-4); border-left: 2px solid var(--accent-2); }
.model-point h3 { font-size: 1rem; margin-bottom: var(--sp-2); }
.model-point p { font-size: var(--step-sm); margin: 0; }

.founder {
  display: grid;
  grid-template-columns: minmax(12rem, 18rem) 1fr;
  gap: clamp(var(--sp-5), 3vw, var(--sp-7));
  margin-top: var(--sp-6);
}
/* The name stays alongside the text while it scrolls, so attribution is never
   lost partway down a long biography. */
.founder-id { position: sticky; top: 6rem; align-self: start; }
.founder-id h2 { margin-bottom: var(--sp-2); }
.founder-role {
  font-family: var(--font-mono);
  font-size: var(--step-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent-2);
  margin: 0;
}
.founder-body > p { margin-bottom: var(--sp-4); }

@media (max-width: 860px) {
  .founder { grid-template-columns: 1fr; }
  .founder-id { position: static; }
}

/* --- brand lockup ---------------------------------------------------------
   The mark stands in for the C. Its size is the one that was working; what
   needed correcting was where the wordmark sits against it.

   The two are centred on each other rather than sharing a baseline. Baseline
   alignment put "aminobits" low against a mark this size, because the mark is
   a solid geometric form with no baseline of its own to share. */
.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.16em;
  text-decoration: none;
  font-size: 1.375rem;
}
.brand-mark {
  height: 1.22em;
  width: auto;
  flex: 0 0 auto;
}
.brand-word {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: 0.005em;
  font-size: 1.1em;
  line-height: 1;
  /* Set slightly larger than the inherited size so the letters carry more
     weight against the mark. No vertical offset: flex centring puts it right. */
  position: relative;
  top: 0;
}
/* The live pulse sat where the mark now is, so it is dropped. */
.brand::before { display: none; }

.site-header-inner { min-height: 5rem; }

@media (max-width: 640px) {
  .brand { font-size: 1.125rem; }
  .site-header-inner { min-height: 4.25rem; }
}
}
}
}

/* The footer lockup reuses the header rules, just a step smaller: it is a
   sign-off rather than navigation. Sizing off .brand means the proportions
   between mark and wordmark carry over untouched. */
.site-footer .brand { font-size: 1.25rem; }


/* ===== 60-motion.css ===== */
/* Motion layer.
   Everything here is progressive enhancement built on current CSS: view
   transitions, scroll driven animation, and gradient text. Browsers without
   support render the site exactly as it was, and everything is disabled under
   prefers-reduced-motion.

   The brief was "modern", not "busy". The restraint is deliberate: the
   positioning is calm and technical, so the motion is slow, short in travel,
   and tied to scroll rather than firing on timers. */

/* --- cross document view transitions -------------------------------------
   Navigating between pages cross fades instead of flashing white. Chrome and
   Edge honour this; everything else ignores the at-rule. */
@view-transition { navigation: auto; }

::view-transition-old(root) { animation: vt-out 180ms cubic-bezier(.4,0,1,1) both; }
::view-transition-new(root) { animation: vt-in 260ms cubic-bezier(0,0,.2,1) both; }
@keyframes vt-out { to { opacity: 0; transform: translateY(-6px); } }
@keyframes vt-in  { from { opacity: 0; transform: translateY(8px); } }

/* The header persists across the transition rather than fading with the page,
   so navigation feels like one surface rather than a reload. */
.site-header { view-transition-name: site-header; }

/* --- scroll progress ------------------------------------------------------
   A thin accent line across the top, driven entirely by scroll position with
   no JS and no scroll listener. */
.scroll-progress {
  position: fixed;
  inset: 0 0 auto;
  height: 2px;
  z-index: 60;
  transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--accent-2), var(--accent));
  scale: 0 1;
}
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .scroll-progress {
      animation: progress linear;
      animation-timeline: scroll(root block);
    }
    @keyframes progress { to { scale: 1 1; } }
  }
}

/* --- scroll driven reveals ------------------------------------------------
   Where the browser supports it, reveals run off the element's own position in
   the viewport rather than a JS observer. Smoother, and it reverses correctly
   when scrolling back up. The IntersectionObserver path in site.js stays as
   the fallback. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .is-armed .reveal {
      opacity: 1;
      transform: none;
      animation: rise-in linear both;
      animation-timeline: view();
      animation-range: entry 8% cover 26%;
    }
    @keyframes rise-in {
      from { opacity: 0; transform: translateY(20px); }
      to   { opacity: 1; transform: none; }
    }
  }
}

/* --- hero parallax --------------------------------------------------------
   The background drifts slightly slower than the page. Scroll linked, so it
   tracks the scrollbar exactly instead of easing behind it. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .hero-visual::before {
      animation: drift linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
    }
    @keyframes drift {
      from { transform: translateY(-2%) scale(1.04); }
      to   { transform: translateY(6%) scale(1.04); }
    }
    /* The expertise crop is mirrored, so preserve that while drifting. */
    body.page-expertise .hero-visual-sub::before {
      animation-name: drift-flip;
    }
    @keyframes drift-flip {
      from { transform: translateY(-2%) scale(1.04) scaleX(-1); }
      to   { transform: translateY(6%) scale(1.04) scaleX(-1); }
    }
  }
}

/* --- headline accent ------------------------------------------------------
   A slow gradient sweep across the hero headline. Long period and low contrast
   so it reads as depth rather than as an animation demanding attention. */
/* Guarded: without background-clip support, `color: transparent` alone would
   erase the headline. The @supports test means unsupported browsers never see
   the transparent colour at all. */
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
@media (prefers-reduced-motion: no-preference) {
  .hero h1 {
    background: linear-gradient(
      100deg,
      var(--text) 0%, var(--text) 38%,
      color-mix(in srgb, var(--accent) 55%, var(--text)) 50%,
      var(--text) 62%, var(--text) 100%);
    background-size: 300% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation: sheen 9s ease-in-out infinite;
  }
  @keyframes sheen {
    0%, 100% { background-position: 120% 0; }
    50%      { background-position: -20% 0; }
  }
}
}

/* --- live mark ------------------------------------------------------------
   A slow pulse behind the brand mark in the header. Signals an active system
   without a spinner or a fake status light. */
.brand { position: relative; }
@media (prefers-reduced-motion: no-preference) {
  .brand::before {
    content: '';
    position: absolute;
    left: -14px;
    top: 50%;
    width: 6px;
    height: 6px;
    margin-top: -3px;
    border-radius: 50%;
    background: var(--accent);
    animation: pulse 3.2s ease-in-out infinite;
  }
  @keyframes pulse {
    0%, 100% { opacity: 0.25; transform: scale(0.8); }
    50%      { opacity: 1;    transform: scale(1); }
  }
}

/* --- card edge glow -------------------------------------------------------
   A gradient border that follows the pointer. Uses a masked pseudo element so
   the card keeps its own background and the glow sits on the edge only. */
.card, details.service, .pipeline-item, .bottleneck {
  position: relative;
}
@media (prefers-reduced-motion: no-preference) and (hover: hover) {
  .card::after, details.service::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    padding: 1px;
    background: linear-gradient(135deg,
      color-mix(in srgb, var(--accent) 60%, transparent),
      transparent 40%,
      color-mix(in srgb, var(--accent-2) 45%, transparent));
    /* Mask keeps only the 1px ring, so the fill stays untouched. */
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    mask-composite: exclude;
    opacity: 0;
    transition: opacity var(--dur) var(--ease);
    pointer-events: none;
  }
  .card:hover::after, details.service:hover::after { opacity: 1; }
}

/* --- grain ----------------------------------------------------------------
   A faint static noise layer over the whole page. It stops the large flat dark
   areas from banding on wide gradients and gives the surface some texture.
   Generated inline as an SVG turbulence filter, so it costs no request. */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0.035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* --- focus ring, kept above the grain ------------------------------------- */
.site-header, .skip-link, #cc-main { z-index: 50; }

@media (prefers-reduced-motion: reduce) {
  .scroll-progress { display: none; }
  .brand::before { animation: none; opacity: 0.6; }
  .hero h1 { animation: none; }
}

/* --- homepage brand ------------------------------------------------------
   Larger on the homepage, where the lockup introduces rather than wayfinds,
   shrinking to the standard size as the page scrolls. The whole lockup is
   sized in em off .brand, so one font-size moves mark, wordmark, and gap
   together and the measured proportions are preserved at every step. */
body.page-home .brand { font-size: 2.25rem; }
body.page-home .site-header-inner { min-height: 7rem; }

@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    body.page-home .brand,
    body.page-home .site-header-inner {
      animation: linear both;
      animation-timeline: scroll(root block);
      /* Settles over the first 220px, before the hero copy is being read. */
      animation-range: 0 220px;
    }
    body.page-home .brand { animation-name: brand-shrink; }
    body.page-home .site-header-inner { animation-name: brand-shrink-bar; }

    @keyframes brand-shrink     { to { font-size: 1.375rem; } }
    @keyframes brand-shrink-bar { to { min-height: 5rem; } }
  }
}

@media (max-width: 640px) {
  body.page-home .brand { font-size: 1.125rem; animation: none; }
  body.page-home .site-header-inner { min-height: 4.25rem; animation: none; }
}
