/* ==========================================================================
   Digiradix — design system.
   Hand-authored, no CDN and no runtime build step.

   Theme: "Midnight + Amber". Deep blue (--color-accent) is the brand and
   carries buttons, links and chips; amber (--color-warm) is the warm accent
   used sparingly for emphasis; teal (--color-teal) is reserved for
   success/affirmative marks. Ink is a blue-black so text sits in the same
   family as the primary rather than fighting it.

   The motion layer lives in motion.css, loaded after this file.
   ========================================================================== */

/* ---- Fonts -------------------------------------------------------------- */
/* Self-hosted, subset WOFF2 (§1.1 — no Google Fonts CDN). Each file is
   instanced down to only the axis range and glyphs this site actually uses
   (Latin + punctuation), then subset — ~95KB combined across all 3, versus
   ~520KB for the unsubset variable originals. Sourced from the fonts'
   official open-source (OFL) repositories; see assets/fonts/README.md for
   the exact pipeline (fontTools varLib.instancer + pyftsubset). */
@font-face { font-family:'Inter'; src:url('../fonts/Inter.woff2') format('woff2-variations'), url('../fonts/Inter.woff2') format('woff2'); font-weight:400 600; font-display:swap; font-style:normal; }
@font-face { font-family:'Sora'; src:url('../fonts/Sora.woff2') format('woff2-variations'), url('../fonts/Sora.woff2') format('woff2'); font-weight:600 700; font-display:swap; font-style:normal; }
@font-face { font-family:'JetBrains Mono'; src:url('../fonts/JetBrainsMono.woff2') format('woff2'); font-weight:400; font-display:swap; font-style:normal; }

/* ---- Tokens ------------------------------------------------------------- */
:root {
  /* ---- Palette: Midnight + Amber ---------------------------------------
     Deep blue carries the brand (buttons, links, chips); amber is the warm
     accent used sparingly for emphasis and highlights. Ink is a blue-black so
     text sits in the same family as the primary rather than fighting it.
     Every pairing below meets WCAG AA on its intended background. */

  /* Base */
  --color-bg:           #FFFFFF;
  --color-bg-alt:       #F8FAFC;
  --color-surface:      #F1F5F9;
  --color-ink:          #0B1220;   /* midnight, not pure black */
  --color-ink-muted:    #52627A;   /* 7.1:1 on white */
  --color-ink-subtle:   #64748B;   /* 4.8:1 on white — small print still passes AA */
  --color-line:         #E2E8F0;
  --color-line-strong:  #CBD5E1;

  /* Primary — deep blue */
  --color-accent:       #1D4ED8;
  --color-accent-hover: #1E40AF;
  --color-accent-tint:  #EFF6FF;
  --color-accent-on-dark: #93C5FD;  /* blue-300: the primary is too dark to read on midnight bands */

  /* Secondary — amber (warm accent).
     --color-warm is amber-700, dark enough to pass AA as text/icon colour;
     --color-warm-bright is the vivid amber, for fills and graphics ONLY
     (it fails contrast as text on white — never use it for copy). */
  --color-warm:        #B45309;
  --color-warm-hover:  #92400E;
  --color-warm-tint:   #FEF3C7;
  --color-warm-bright: #F59E0B;

  /* Tertiary — teal, used for success ticks and stat units */
  --color-teal:        #0D9488;
  --color-teal-tint:   #ECFDF5;

  /* Dark bands */
  --color-dark:         #0B1220;
  --color-dark-alt:     #131C2E;
  --color-on-dark:      #F8FAFC;
  --color-on-dark-muted:#94A3B8;
  --color-line-on-dark: #223049;

  /* Feedback */
  --color-success:      #0F766E;
  --color-warning:      #B45309;
  --color-error:        #DC2626;

  /* Type */
  --font-display: 'Sora', -apple-system, 'Segoe UI', sans-serif;
  --font-body:    'Inter', -apple-system, 'Segoe UI', sans-serif;
  --font-mono:    'JetBrains Mono', ui-monospace, 'Cascadia Code', monospace;

  --text-xs:   0.75rem;
  --text-sm:   0.875rem;
  --text-base: 1.0625rem;
  --text-lg:   1.1875rem;
  --text-xl:   clamp(1.375rem, 1.2rem + 0.8vw, 1.625rem);
  --text-2xl:  clamp(1.625rem, 1.4rem + 1.1vw, 2.125rem);
  --text-3xl:  clamp(2rem, 1.6rem + 2vw, 3rem);
  --text-4xl:  clamp(2.5rem, 1.9rem + 3.2vw, 4.5rem);
  --text-5xl:  clamp(3rem, 2rem + 5vw, 6rem);
  /* For hero headings that are a full sentence rather than a short phrase.
     Sits deliberately between 3xl and 4xl: nowhere near --text-4xl's 72px,
     which a 60–77 character headline in a half-width column cannot carry,
     but above --text-3xl at EVERY width so an H1 never renders smaller than
     the H2s beneath it. That last part is why the intercept (1.8rem) and
     slope (2.2vw) both exceed 3xl's rather than just the 52px cap exceeding
     its 48px — matching 3xl's slope with a lower intercept would invert the
     hierarchy in the middle of the range while looking fine at both ends. */
  --text-hero-long: clamp(2.125rem, 1.8rem + 2.2vw, 3.25rem);

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

  --container-wide:   1280px;
  --container:        1160px;
  --container-narrow:  880px;
  --container-prose:   720px;
  --gutter: clamp(1.25rem, 5vw, 2.5rem);

  --section-y:       clamp(4rem, 8vw, 8rem);
  --section-y-tight: clamp(3rem, 5vw, 5rem);
  --section-y-loose: clamp(5rem, 10vw, 10rem);

  /* Radius / borders / elevation */
  --radius-sm: 6px; --radius: 12px; --radius-lg: 18px; --radius-xl: 26px; --radius-full: 999px;
  --border:        1px solid var(--color-line);
  --border-strong: 1px solid var(--color-line-strong);
  --border-accent: 2px solid var(--color-accent);

  /* Layered elevation scale — soft, multi-stop, tinted toward the ink colour
     so shadows read as depth rather than grey haze. */
  /* Shadows are tinted with the midnight ink (11,18,32) rather than neutral
     grey, so depth reads as part of the palette instead of haze. */
  --shadow-xs: 0 1px 2px rgba(11,18,32,.05);
  --shadow-sm: 0 1px 2px rgba(11,18,32,.05), 0 2px 6px rgba(11,18,32,.05);
  --shadow-md: 0 2px 4px rgba(11,18,32,.04), 0 6px 16px rgba(11,18,32,.07);
  --shadow-lg: 0 4px 8px rgba(11,18,32,.04), 0 12px 32px rgba(11,18,32,.10), 0 2px 4px rgba(11,18,32,.05);
  --shadow-xl: 0 8px 16px rgba(11,18,32,.05), 0 24px 56px rgba(11,18,32,.12), 0 2px 6px rgba(11,18,32,.05);
  --shadow-header: var(--shadow-sm);
  --glow-accent: 0 8px 26px rgba(29,78,216,.34);
  --glow-warm:   0 8px 26px rgba(245,158,11,.36);
  --glow-teal:   0 8px 26px rgba(13,148,136,.30);
  --ring-accent: 0 0 0 4px rgba(29,78,216,.16);
  --ring-warm:   0 0 0 4px rgba(245,158,11,.20);

  /* Motion */
  --ease:      cubic-bezier(.4,0,.2,1);
  --ease-out:  cubic-bezier(.16,1,.3,1);
  --ease-spring: cubic-bezier(.34,1.56,.64,1);   /* slight overshoot */
  --ease-smooth: cubic-bezier(.65,0,.35,1);
  --dur-fast: 150ms;  --dur: 240ms;  --dur-slow: 420ms;  --dur-slower: 700ms;
  --dur-reveal: 900ms;

  --gradient-cta:    linear-gradient(105deg, #0B1220 0%, #1D4ED8 55%, #F59E0B 140%);
  --gradient-text:   linear-gradient(100deg, #1D4ED8 0%, #F59E0B 60%, #1D4ED8 100%);
  --gradient-sheen:  linear-gradient(100deg, transparent 20%, rgba(255,255,255,.55) 50%, transparent 80%);
  --gradient-surface: linear-gradient(180deg, #FFFFFF 0%, #F8FAFC 100%);
  --gradient-border: linear-gradient(135deg, rgba(29,78,216,.55), rgba(245,158,11,.55));
}

/* ---- Reset -------------------------------------------------------------- */
*,*::before,*::after { box-sizing:border-box; }
* { margin:0; }
/* overflow-x:clip on the root as well as body: the closed mobile-nav panel is
   position:fixed and parked off-screen at translateX(100%), and fixed elements
   are not clipped by body's overflow, so without this it widens the viewport
   on tablet widths. `clip` rather than `hidden` — hidden would turn the root
   into a scroll container and break the sticky header + smooth scrolling. */
html { -webkit-text-size-adjust:100%; scroll-behavior:smooth; overflow-x:clip; }
body {
  font-family:var(--font-body);
  font-size:var(--text-base);
  line-height:1.65;
  color:var(--color-ink);
  background:var(--color-bg);
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}
/* Guard against any animation that momentarily sits outside the viewport
   creating a horizontal scrollbar. `clip` (not `hidden`) is used deliberately:
   overflow:hidden on a scroll container would break the sticky header. */
body { overflow-x:clip; }
img,svg,video { display:block; max-width:100%; }
img { height:auto; }
button,input,textarea,select { font:inherit; color:inherit; }
button { cursor:pointer; background:none; border:none; }
a { color:inherit; text-decoration:none; }
ul,ol { list-style:none; padding:0; }
:where(h1,h2,h3,h4,h5,h6) { font-weight:600; }

/* ---- Focus (never removed) --------------------------------------------- */
:focus-visible {
  outline:2px solid var(--color-accent);
  outline-offset:2px;
  border-radius:2px;
}
.on-dark :focus-visible { outline-color:#fff; }

/* Skip link */
.skip-link {
  position:absolute; left:var(--space-4); top:-100px; z-index:100;
  background:var(--color-ink); color:#fff; padding:var(--space-3) var(--space-5);
  border-radius:var(--radius); font-weight:600; transition:top var(--dur) var(--ease);
}
.skip-link:focus { top:var(--space-4); }

/* ---- Layout ------------------------------------------------------------- */
.container       { width:100%; max-width:var(--container);       margin-inline:auto; padding-inline:var(--gutter); }
.container-wide  { max-width:var(--container-wide); }
.container-narrow{ max-width:var(--container-narrow); }
.container-prose { max-width:var(--container-prose); }

.section        { padding-block:var(--section-y); }
.section-tight  { padding-block:var(--section-y-tight); }
.section-loose  { padding-block:var(--section-y-loose); }

/* Bands are no longer flat fills: each gets a soft vertical wash plus a very
   faint dot grid, which stops large light areas reading as empty template. */
.bg-bg      { background:var(--color-bg); }
.bg-alt     { background:linear-gradient(180deg, #FFFFFF 0%, var(--color-bg-alt) 45%, #FFFFFF 100%); }
.bg-surface {
  position:relative;
  background:linear-gradient(180deg, #FFFFFF 0%, var(--color-surface) 40%, var(--color-surface) 60%, #FFFFFF 100%);
}
.bg-surface::before, .bg-alt::before {
  content:''; position:absolute; inset:0; pointer-events:none;
  background-image:radial-gradient(circle, rgba(10,10,10,.055) 1px, transparent 1px);
  background-size:22px 22px;
  -webkit-mask-image:radial-gradient(ellipse 80% 60% at 50% 50%, #000, transparent 75%);
  mask-image:radial-gradient(ellipse 80% 60% at 50% 50%, #000, transparent 75%);
  opacity:.5;
}
.bg-alt { position:relative; }
.bg-surface > *, .bg-alt > * { position:relative; z-index:1; }
.bg-dark    { background:var(--color-dark); color:var(--color-on-dark); }

.grid { display:grid; gap:var(--space-6); }
/* Single column on small screens: the 12-col track only exists once the
   .col-* span rules below are active, otherwise every child falls back to
   span 1 and gets sized by min-content, squashing its siblings. */
.grid-12 { grid-template-columns:1fr; }
@media (min-width:768px){
  .grid-12 { grid-template-columns:repeat(12,1fr); }
  .col-7 { grid-column:span 7; } .col-5 { grid-column:span 5; }
  .col-8 { grid-column:span 8; } .col-4 { grid-column:span 4; }
  .col-6 { grid-column:span 6; }
}

.cols { display:grid; gap:var(--space-6); grid-template-columns:1fr; }
@media (min-width:640px){ .cols-2 { grid-template-columns:repeat(2,1fr); } }
@media (min-width:768px){ .cols-3 { grid-template-columns:repeat(3,1fr); } }
@media (min-width:900px){ .cols-2-md { grid-template-columns:repeat(2,1fr); } }

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

/* ---- Typography --------------------------------------------------------- */
.display { font-family:var(--font-display); }
.mono    { font-family:var(--font-mono); }

.h1 { font-family:var(--font-display); font-size:var(--text-4xl); font-weight:600; letter-spacing:-0.03em;  line-height:1.05; }
.h2 { font-family:var(--font-display); font-size:var(--text-3xl); font-weight:600; letter-spacing:-0.025em; line-height:1.12; }
.h3 { font-family:var(--font-display); font-size:var(--text-2xl); font-weight:600; letter-spacing:-0.02em;  line-height:1.2; }
.h4 { font-family:var(--font-display); font-size:var(--text-xl); font-weight:600; letter-spacing:-0.015em; line-height:1.3; }

.lead  { font-size:var(--text-lg); line-height:1.6; color:var(--color-ink-muted); }
.small { font-size:var(--text-sm); line-height:1.55; }
.muted { color:var(--color-ink-muted); }
.subtle{ color:var(--color-ink-subtle); }
.measure { max-width:68ch; }
.prose-w { max-width:var(--container-prose); }

/* Eyebrow — the site's most repeated device. Violet on light, orange on dark. */
.eyebrow {
  display:inline-block;
  font-size:var(--text-sm); font-weight:600; letter-spacing:0.12em;
  text-transform:uppercase; line-height:1.2; color:var(--color-warm);
  margin-bottom:var(--space-4);
}
.on-dark .eyebrow { color:var(--color-warm-bright); }

/* Section heading block */
.section-head { max-width:var(--container-narrow); }
.section-head .h2 { margin-top:var(--space-2); }
.section-head .lead { margin-top:var(--space-4); }

/* Prose links */
.prose a, .link-underline {
  color:var(--color-ink);
  box-shadow:inset 0 -2px 0 var(--color-accent);
  transition:color var(--dur) var(--ease);
}
.prose a:hover, .link-underline:hover { color:var(--color-accent); }

/* Tertiary text link with arrow */
.link-arrow {
  display:inline-flex; align-items:center; gap:var(--space-2);
  color:var(--color-accent); font-weight:600;
  transition:gap var(--dur) var(--ease-out), color var(--dur) var(--ease);
}
.link-arrow svg { width:1em; height:1em; transition:transform var(--dur) var(--ease-out); }
.link-arrow:hover { color:var(--color-accent-hover); }
.link-arrow:hover svg { transform:translateX(3px); }
.on-dark .link-arrow { color:var(--color-accent-on-dark); }
.on-dark .link-arrow:hover { color:#fff; }

/* ---- Buttons ------------------------------------------------------------ */
/* Every button carries a diagonal sheen that sweeps across on hover (the
   ::before), plus a lift + glow. transform/opacity only, so it stays cheap. */
.btn {
  position:relative; isolation:isolate; overflow:hidden;
  display:inline-flex; align-items:center; justify-content:center; gap:var(--space-2);
  font-family:var(--font-body); font-size:1rem; font-weight:600; line-height:1;
  padding:0.9rem 1.85rem; border-radius:var(--radius-full);
  min-height:48px; text-align:center; letter-spacing:-.01em;
  transform:translateZ(0);
  transition:background var(--dur) var(--ease), color var(--dur) var(--ease),
             border-color var(--dur) var(--ease), transform var(--dur) var(--ease-spring),
             box-shadow var(--dur) var(--ease);
}
.btn::before {
  content:''; position:absolute; inset:0; z-index:-1;
  background:var(--gradient-sheen);
  transform:translateX(-120%); opacity:0;
  transition:transform var(--dur-slow) var(--ease-smooth), opacity var(--dur) var(--ease);
}
.btn:hover::before { transform:translateX(120%); opacity:1; }
.btn:active { transform:translateY(1px) scale(.985); }
.btn svg { width:1.15em; height:1.15em; transition:transform var(--dur) var(--ease-spring); }
.btn:hover svg { transform:translateX(3px); }

.btn-primary   { background:var(--color-accent); color:#fff; box-shadow:var(--shadow-sm); }
.btn-primary:hover { background:var(--color-accent-hover); transform:translateY(-2px); box-shadow:var(--glow-accent); }
.btn-secondary { background:var(--color-bg); color:var(--color-ink); border:1px solid var(--color-line-strong); box-shadow:var(--shadow-xs); }
.btn-secondary:hover { background:var(--color-bg); border-color:var(--color-ink); transform:translateY(-2px); box-shadow:var(--shadow-md); }
.btn-tertiary  { background:transparent; color:var(--color-accent); padding-inline:0; overflow:visible; }
.btn-tertiary::before { display:none; }
.btn-tertiary:hover { text-decoration:underline; text-underline-offset:4px; transform:none; }
.btn-on-dark   { background:#fff; color:var(--color-ink); box-shadow:var(--shadow-md); }
.btn-on-dark:hover { background:var(--color-accent); color:#fff; transform:translateY(-2px); box-shadow:var(--glow-accent); }
/* Outlined ghost for dark/gradient bands. Own variant rather than an inline
   override on .btn-secondary, which owns a solid light background. */
.btn-ghost-on-dark {
  background:rgba(255,255,255,.08); color:#fff;
  border:1px solid rgba(255,255,255,.55);
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
}
.btn-ghost-on-dark:hover { background:rgba(255,255,255,.18); border-color:#fff; transform:translateY(-2px); }
.btn-block { display:flex; width:100%; }

/* ---- Cards -------------------------------------------------------------- */
/* Cards get: a soft lift, a gradient hairline that fades in on hover
   (::after, masked so only the border shows), and a cursor-tracked spotlight
   (::before, driven by --mx/--my set in motion.js). */
.card {
  position:relative; isolation:isolate;
  background:var(--gradient-surface); border:var(--border); border-radius:var(--radius-lg);
  padding:var(--space-7);
  box-shadow:var(--shadow-xs);
  transition:border-color var(--dur) var(--ease), transform var(--dur-slow) var(--ease-spring),
             box-shadow var(--dur-slow) var(--ease);
}
.card::before {
  content:''; position:absolute; inset:0; z-index:-1; border-radius:inherit;
  background:radial-gradient(340px circle at var(--mx,50%) var(--my,0%),
             rgba(29,78,216,.09), rgba(245,158,11,.07) 40%, transparent 65%);
  opacity:0; transition:opacity var(--dur-slow) var(--ease);
}
.card::after {
  content:''; position:absolute; inset:0; z-index:-1; border-radius:inherit;
  padding:1px; background:var(--gradient-border);
  -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-slow) var(--ease);
}
.card:hover { transform:translateY(-6px); box-shadow:var(--shadow-lg); border-color:transparent; }
.card:hover::before, .card:hover::after { opacity:1; }
.card-top-accent { border-top:3px solid var(--color-accent); }
.card-flush { padding:0; overflow:hidden; }
.card-flush .img-ph { border-radius:0; border:none; border-bottom:var(--border); box-shadow:none; }
/* Real card artwork is full-bleed to the card edge; it only needs the divider
   the placeholder's border-bottom used to provide. */
.card-flush .img-media { border-bottom:var(--border); }

/* Card links lay out as columns so the "Learn more" row always sits on the
   baseline, keeping a row of cards visually aligned regardless of copy length. */
a.card { display:flex; flex-direction:column; }
.card-cta { margin-top:auto; padding-top:var(--space-5); }

/* ---- Booking embed ------------------------------------------------------
   The min-height sits on the embed itself, not the wrapper: it reserves space
   so nothing below jumps as the third-party scheduler paints, while the (much
   shorter) fallback card doesn't inherit a 640px hole beneath it.
   .cal-inline is the mount point for Cal.com's JS embed — it injects its own
   iframe inside, so the box only has to provide size and the visual frame. */
.booking-embed iframe,
.booking-embed .cal-inline {
  width:100%; min-height:640px; height:100%; border:0;
  border-radius:var(--radius-lg); background:var(--color-bg);
  box-shadow:var(--shadow-md);
}
/* Cal.com sizes its own iframe to the content; auto (not scroll) keeps a dead
   scrollbar off the page on desktop while still letting short viewports pan. */
.booking-embed .cal-inline { overflow:auto; }

/* ---- Book-a-call: scheduler above the fold ------------------------------
   This page has one job, so the calendar leads instead of sitting under a
   full-height hero. The top band drops to a fixed, small pad rather than
   --section-y (up to 8rem) so the widget starts within the first screen; the
   bottom keeps normal tight spacing so the next band isn't crowded. */
.book-top { padding-block:var(--space-6) var(--section-y-tight); }
.book-crumb { margin-bottom:var(--space-5); }
/* Narrower than .measure: the intro is a one-line signpost, and a short line
   keeps the calendar's top edge that much higher up the screen. */
.book-intro .measure { max-width:58ch; }
/* The h1 is set at .h3 to keep the calendar high, so the section headings
   below have to come down with it — a full-size .h2 further down the page
   would out-shout the page title and invert the visual hierarchy. */
.page-book .section-head .h2 { font-size:var(--text-2xl); letter-spacing:-0.02em; line-height:1.2; }

/* Laptop screens are wide but short. Below ~820px of viewport, trim the band's
   padding further so the month grid still clears the fold. Only the padding is
   worth touching: Cal.com sizes its own iframe, and the mount point grows to
   match it, so capping the box here would just add a nested scrollbar. */
@media (min-width:900px) and (max-height:820px) {
  .book-top { padding-block:var(--space-5) var(--space-8); }
}
.booking-fallback { max-width:640px; margin-inline:auto; }

/* ---- CTA tile -----------------------------------------------------------
   Fills the leftover cell in a 5-into-3 grid. Inherits .card for sizing and
   the cursor spotlight, then flips the whole palette to blue. Each override
   below undoes a .card default that assumes a light background. */
.cta-card {
  background:linear-gradient(150deg, var(--color-accent) 0%, #1B3FAF 55%, #16307F 100%);
  border-color:transparent; color:#fff;
  box-shadow:var(--shadow-md);
}
.cta-card h3, .cta-card p { color:#fff; }
.cta-card p { color:rgba(255,255,255,.82); }
/* Spotlight: white instead of the light-theme blue/amber wash. */
.cta-card::before {
  background:radial-gradient(340px circle at var(--mx,50%) var(--my,0%),
             rgba(255,255,255,.20), rgba(255,255,255,.06) 45%, transparent 68%);
}
/* Hairline: white instead of the blue→amber gradient border. */
.cta-card::after { background:linear-gradient(135deg, rgba(255,255,255,.55), rgba(255,255,255,.12)); }
.cta-card:hover { box-shadow:var(--glow-accent), var(--shadow-lg); }
/* Chip reads as translucent glass on the blue rather than a tinted panel. */
.cta-card .icon-chip {
  background:rgba(255,255,255,.14); color:#fff;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.28);
}
.cta-card .icon-chip::after { background:var(--color-warm-bright); }
/* `.cta-card.card` (0,3,0) rather than `.cta-card` (0,2,0): the generic
   `.card:hover .icon-chip` rule further down the file ties on specificity and
   would win on source order, repainting this chip blue instead of white. */
.cta-card.card:hover .icon-chip {
  background:#fff; color:var(--color-accent);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.5), 0 8px 22px rgba(0,0,0,.18);
}
.cta-card .link-arrow { color:#fff; }
.cta-card:hover .link-arrow { color:#fff; }
.cta-card .link-arrow svg { transition:transform var(--dur) var(--ease-out); }
.cta-card:hover .link-arrow svg { transform:translateX(5px); }

/* Icon chip — duotone: a soft blue-to-amber tinted panel with an inset
   hairline, so it reads as a crafted object rather than a flat colour swatch.
   On hover it flips to a solid brand-blue fill with a glow. */
.icon-chip {
  position:relative; isolation:isolate;
  display:inline-flex; align-items:center; justify-content:center;
  width:54px; height:54px; border-radius:var(--radius-lg);
  color:var(--color-accent);
  background:
    linear-gradient(140deg, var(--color-accent-tint) 0%, var(--color-warm-tint) 130%);
  box-shadow:inset 0 0 0 1px rgba(29,78,216,.14), var(--shadow-xs);
  /* ease-out, not ease-spring: a 56% overshoot applied to translate+rotate+
     scale at once made this small a chip visibly wobble on hover. */
  transition:transform var(--dur) var(--ease-out), color var(--dur) var(--ease),
             box-shadow var(--dur) var(--ease), background var(--dur) var(--ease);
}
/* The amber "spark": a small offset dot that gives the chip a second colour
   without tinting the glyph itself. */
.icon-chip::after {
  content:''; position:absolute; top:9px; right:9px; width:6px; height:6px;
  border-radius:50%; background:var(--color-warm-bright);
  opacity:.85; transition:transform var(--dur) var(--ease-out), opacity var(--dur) var(--ease);
}
.icon-chip svg { width:25px; height:25px; transition:transform var(--dur) var(--ease-out); }
.card:hover .icon-chip, a:hover > .icon-chip {
  transform:translateY(-2px) scale(1.04);
  background:linear-gradient(140deg, var(--color-accent) 0%, var(--color-accent-hover) 100%);
  color:#fff; box-shadow:inset 0 0 0 1px rgba(255,255,255,.18), var(--glow-accent);
}
.card:hover .icon-chip::after, a:hover > .icon-chip::after { transform:scale(1.35); opacity:1; }
.card:hover .icon-chip svg { transform:scale(1.06); }

/* ---- Pills / tags ------------------------------------------------------- */
.pill {
  display:inline-flex; align-items:center; gap:var(--space-1);
  background:var(--color-warm-tint); color:var(--color-warm);
  border-radius:var(--radius-sm); font-size:var(--text-sm); font-weight:600;
  padding:.25rem .625rem;
}
.pill-accent { background:var(--color-accent-tint); color:var(--color-warning); }
.pill-teal   { background:var(--color-teal-tint);  color:var(--color-success); }

/* Placeholder flag pill — used for fake/sample data slots */
.pill-placeholder {
  background:var(--color-surface); color:var(--color-ink-subtle);
  border:1px dashed var(--color-line-strong);
  font-family:var(--font-mono); font-size:var(--text-xs); text-transform:uppercase;
  letter-spacing:.06em;
}

/* ---- Numerals ------------------------------------------------------------
   Solid, not outlined. -webkit-text-stroke on a geometric face like Sora
   renders badly at large sizes (counters and joins break up), so these are
   filled and rely on weight + colour for emphasis instead. */
.numeral {
  font-family:var(--font-display); font-size:var(--text-3xl); font-weight:700;
  letter-spacing:-0.03em; line-height:1;
  color:var(--color-accent);
  font-variant-numeric:tabular-nums;
}

/* ---- Process timeline ---------------------------------------------------
   Four steps across on desktop, two on tablet, stacked on mobile, with a
   connector rule drawn between consecutive badges. The connector is a
   pseudo-element on each step, hidden wherever a row ends (see the
   breakpoint-specific :nth-child rules) so no line ever dangles. */
.process { display:grid; gap:var(--space-8) var(--space-6); grid-template-columns:1fr; }
.process-step { position:relative; padding-left:calc(56px + var(--space-5)); }

.step-badge {
  position:absolute; left:0; top:0;
  display:inline-flex; align-items:center; justify-content:center;
  width:56px; height:56px; border-radius:var(--radius-lg);
  font-family:var(--font-display); font-size:1.15rem; font-weight:700;
  letter-spacing:-.02em; font-variant-numeric:tabular-nums;
  color:#fff;
  background:linear-gradient(140deg, var(--color-accent) 0%, var(--color-accent-hover) 100%);
  box-shadow:var(--shadow-md), inset 0 0 0 1px rgba(255,255,255,.16);
  transition:transform var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease);
}
/* Amber spark, echoing the icon chips so the two devices feel related. */
.step-badge::after {
  content:''; position:absolute; top:8px; right:8px; width:6px; height:6px;
  border-radius:50%; background:var(--color-warm-bright); opacity:.9;
  transition:transform var(--dur) var(--ease-out);
}
.process-step:hover .step-badge { transform:translateY(-3px) scale(1.05); box-shadow:var(--glow-accent); }
.process-step:hover .step-badge::after { transform:scale(1.3); }

.step-title { margin-top:var(--space-1); }
.step-body  { margin-top:var(--space-3); }

/* Stacked: connector runs vertically down the badge column. */
.process-step::before {
  content:''; position:absolute; left:27px; top:64px; bottom:calc(var(--space-8) * -1);
  width:2px; background:var(--color-line); border-radius:2px;
}
.process-step:last-child::before { display:none; }

/* Two across: the copy sits BESIDE the badge, so the connector must stay
   vertical (a horizontal one would strike straight through the heading).
   It flows down each column, so it's hidden on the final row. */
@media (min-width:700px){
  .process { grid-template-columns:repeat(2,1fr); }
  .process-step:last-child::before { display:none; }
  .process-step:nth-last-child(-n+2)::before { display:none; }
}
/* Four across: the badge sits ABOVE the copy, which is the only arrangement
   where a horizontal connector between badges reads correctly. */
@media (min-width:1000px){
  .process { grid-template-columns:repeat(4,1fr); gap:var(--space-6); }
  .process-step { padding-left:0; padding-top:calc(56px + var(--space-5)); }
  .process-step::before {
    left:auto; top:27px; bottom:auto; width:auto; height:2px;
    inset-inline-start:64px; inset-inline-end:calc(var(--space-6) * -1);
    background:var(--color-line); border-radius:2px;
  }
  /* Re-show the second-to-last step's connector, which the 2-column rule above
     hides. This selector must match that rule's specificity (0,2,1) — a plain
     `.process-step::before { display:block }` is (0,1,1) and loses to it
     regardless of source order, which silently drops the 03 -> 04 line. */
  .process-step:nth-last-child(-n+2)::before { display:block; }
  .process-step:last-child::before { display:none; }
}

/* Stat numeral (solid) */
.stat-num {
  font-family:var(--font-display); font-size:var(--text-5xl); font-weight:600;
  letter-spacing:-0.04em; line-height:0.95;
}
.stat-unit { font-family:var(--font-mono); color:var(--color-teal); font-size:.4em; }

/* ---- Checklist ---------------------------------------------------------- */
.check-list { display:grid; gap:var(--space-3); }
.check-list li { display:flex; gap:var(--space-3); align-items:flex-start; }
/* Size/colour .tick on the class itself, not only inside .check-list: an SVG
   with a viewBox and no width/height stretches to fill its container, so a
   tick used outside a check list rendered as a huge black checkmark. */
.tick { flex:none; color:var(--color-teal); width:22px; height:22px; margin-top:2px; }
.check-list .tick { flex:none; color:var(--color-teal); width:22px; height:22px; margin-top:2px; }

/* ---- Feature grid --------------------------------------------------------
   Icon + title + body value props. No card chrome — separation comes from an
   accent rule that grows under each item on hover, which keeps this visually
   distinct from the service-card grid that usually sits above it. */
.feature-grid { --feature-gap:var(--space-8); gap:var(--feature-gap) var(--space-7); }
.feature {
  position:relative; padding-bottom:var(--space-5);
  transition:transform var(--dur-slow) var(--ease-spring);
}
.feature::after {
  content:''; position:absolute; left:0; bottom:0; height:2px; width:100%;
  border-radius:2px;
  background:linear-gradient(90deg, var(--color-accent), var(--color-warm-bright));
  transform:scaleX(0); transform-origin:left;
  transition:transform var(--dur-slow) var(--ease-out);
}
.feature:hover { transform:translateY(-4px); }
.feature:hover::after { transform:scaleX(1); }
.feature-title { margin-top:var(--space-5); }
.feature-body  { margin-top:var(--space-3); }
/* .icon-chip's hover state is otherwise scoped to cards and links. */
.feature:hover .icon-chip {
  transform:translateY(-2px) scale(1.04);
  background:linear-gradient(140deg, var(--color-accent) 0%, var(--color-accent-hover) 100%);
  color:#fff; box-shadow:inset 0 0 0 1px rgba(255,255,255,.18), var(--glow-accent);
}
.feature:hover .icon-chip::after { transform:scale(1.35); opacity:1; }

/* ---- Comparison table --------------------------------------------------- */
.table-wrap { overflow-x:auto; border:var(--border); border-radius:var(--radius-lg); }
.cmp {
  width:100%; border-collapse:collapse; min-width:720px;
  font-size:var(--text-base); background:var(--color-bg);
}
.cmp th, .cmp td { padding:var(--space-4) var(--space-5); text-align:left; vertical-align:middle; }
.cmp thead th {
  font-size:var(--text-sm); font-weight:600; text-transform:uppercase;
  letter-spacing:.08em; color:var(--color-ink-muted);
  border-bottom:var(--border-strong);
}
.cmp tbody td, .cmp tbody th { border-bottom:var(--border); }
.cmp tbody tr:last-child td, .cmp tbody tr:last-child th { border-bottom:none; }
.cmp tbody th { font-weight:600; color:var(--color-ink-muted); font-size:var(--text-sm); }
.cmp .col-rec {
  background:var(--color-accent-tint);
  border-left:2px solid var(--color-accent);
  border-right:2px solid var(--color-accent);
  font-weight:600; color:var(--color-ink);
}
.cmp thead .col-rec { border-top:2px solid var(--color-accent); color:var(--color-ink); }
.cmp tbody tr:last-child .col-rec { border-bottom:2px solid var(--color-accent); }
.rec-label {
  display:inline-block; background:var(--color-accent); color:#fff;
  font-size:var(--text-xs); font-weight:600; letter-spacing:.04em;
  padding:.15rem .5rem; border-radius:var(--radius-full); margin-bottom:var(--space-1);
}
.cmp .dash { color:var(--color-ink-subtle); }
.cmp .yes  { color:var(--color-teal); }

/* ---- Forms -------------------------------------------------------------- */
.field { display:block; }
.field + .field { margin-top:var(--space-5); }
/* ...but only when they are genuinely stacked. Two fields side by side in a
   grid row are adjacent siblings as well, so the rule above drops the second
   column lower than the first — a visibly crooked row. The grid's own gap
   already provides the spacing here. */
:is(.cols, .grid) > .field + .field { margin-top:0; }
.field label { display:block; font-size:var(--text-sm); font-weight:600; margin-bottom:var(--space-2); }
.field input, .field textarea, .field select {
  width:100%; font-size:var(--text-base);
  padding:.85rem 1.1rem; border:var(--border); border-radius:var(--radius);
  background:var(--color-bg); color:var(--color-ink);
  transition:border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
             transform var(--dur) var(--ease-out), background var(--dur) var(--ease);
}
/* Native select arrow is drawn by the OS, not the page — a boxed grey chevron
   that ignores the border radius, colour and type of every other control here.
   appearance:none drops it, then the same chevron-down path icon.php uses is
   painted as a background so the control matches the rest of the form. The
   fill is --color-ink-subtle #64748B, URL-encoded (# must be %23 inside a
   data URI). padding-right clears the arrow so a long option never runs under
   it. background-image needs its own declaration because the shared rule
   above sets the `background` shorthand, which would otherwise reset it. */
.field select {
  appearance:none; -webkit-appearance:none; -moz-appearance:none;
  padding-right:2.9rem;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2364748B' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat:no-repeat;
  background-position:right 1.05rem center;
  background-size:18px 18px;
}
/* Match the label/border tint the rest of the field gets on focus. */
.field select:focus { background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231D4ED8' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E"); }
/* IE/Edge legacy only: hides the second, built-in arrow. */
.field select::-ms-expand { display:none; }

.field input::placeholder, .field textarea::placeholder { color:var(--color-ink-subtle); }
.field input:hover, .field textarea:hover, .field select:hover { border-color:var(--color-line-strong); }
.field input:focus, .field textarea:focus, .field select:focus {
  outline:none; border-color:var(--color-accent);
  box-shadow:var(--ring-warm), var(--shadow-sm);
  transform:translateY(-1px);
}
.field label { transition:color var(--dur) var(--ease); }
.field:focus-within label { color:var(--color-accent); }
.field .error-msg {
  display:none; align-items:center; gap:var(--space-2);
  color:var(--color-error); font-size:var(--text-sm); margin-top:var(--space-2);
}
.field.has-error input, .field.has-error textarea { border-color:var(--color-error); }
.field.has-error .error-msg { display:flex; }
.field .error-msg svg { width:14px; height:14px; flex:none; }

/* Forms on dark bands */
.on-dark .field label { color:var(--color-on-dark); }
.on-dark .field input, .on-dark .field textarea {
  background:var(--color-dark-alt); border-color:var(--color-line-on-dark); color:var(--color-on-dark);
}
.on-dark .field input::placeholder { color:var(--color-on-dark-muted); }
.on-dark .field input:focus, .on-dark .field textarea:focus {
  border-color:var(--color-accent); box-shadow:0 0 0 3px rgba(96,165,250,.35);
}

/* ---- FAQ (native details/summary) -------------------------------------- */
.faq { border-top:var(--border); }
.faq details { border-bottom:var(--border); }
.faq summary {
  display:flex; justify-content:space-between; align-items:center; gap:var(--space-4);
  padding:var(--space-5) 0; cursor:pointer; list-style:none;
  font-family:var(--font-display); font-size:var(--text-lg); font-weight:600;
  letter-spacing:-0.01em;
}
.faq summary::-webkit-details-marker { display:none; }
.faq summary .marker {
  flex:none; width:24px; height:24px; position:relative;
  transition:transform var(--dur) var(--ease-out);
}
.faq summary .marker::before, .faq summary .marker::after {
  content:''; position:absolute; background:var(--color-accent);
  top:50%; left:50%; transform:translate(-50%,-50%);
}
.faq summary .marker::before { width:14px; height:2px; }
.faq summary .marker::after  { width:2px; height:14px; transition:transform var(--dur) var(--ease); }
.faq details[open] summary .marker::after { transform:translate(-50%,-50%) scaleY(0); }
.faq details[open] summary .marker::before { transform:translate(-50%,-50%); }
.faq .faq-body { padding:0 0 var(--space-5); color:var(--color-ink-muted); max-width:68ch; }

/* ---- Image placeholder -------------------------------------------------- */
/* Default placeholder is now a soft brand-tinted mesh rather than flat grey,
   so pages read as designed even before real photography lands. */
.img-ph {
  position:relative; display:flex; align-items:center; justify-content:center;
  background:
    radial-gradient(circle at 18% 22%, rgba(29,78,216,.16), transparent 52%),
    radial-gradient(circle at 82% 78%, rgba(245,158,11,.20), transparent 52%),
    radial-gradient(circle at 62% 18%, rgba(13,148,136,.10), transparent 46%),
    linear-gradient(150deg, #FFFFFF 0%, #F7F7F9 100%);
  border:var(--border); border-radius:var(--radius-lg);
  box-shadow:var(--shadow-md);
  overflow:hidden;
}
.img-ph::before {
  content:''; position:absolute; inset:0;
  background-image:
    repeating-linear-gradient(45deg, transparent 0 22px, rgba(10,10,10,.022) 22px 23px);
}
.img-ph .img-ph-label {
  position:relative; font-family:var(--font-mono); font-size:var(--text-xs);
  color:var(--color-ink-subtle); background:var(--color-bg);
  border:var(--border); border-radius:var(--radius-sm);
  padding:.35rem .6rem; text-align:center; max-width:90%; word-break:break-word;
}
.img-ph[data-template="gradient-mesh"]::before {
  background:radial-gradient(circle at 30% 30%, var(--color-accent-tint), transparent 55%),
             radial-gradient(circle at 70% 70%, var(--color-warm-tint), transparent 55%);
  opacity:1;
}
.img-ph[data-template="logo-placeholder"] { border-style:dashed; }
.img-ph[data-template="logo-placeholder"]::before { display:none; }

/* Real artwork carries its own frame, so this deliberately adds none. Hero art
   is drawn on pure white with its own margin and is meant to sit frameless in
   a white section (see PROMPTS-*.md); blog art is full-bleed inside
   .card-flush, which strips border and radius regardless. A border here would
   fight the first case and be discarded in the second.
   width:100% makes the image track its column; the intrinsic width/height
   attributes stay on the tag to reserve the box and prevent CLS. */
.img-media { width:100%; }

/* ---- Hero media ---------------------------------------------------------
   The hero illustrations are painted on opaque pure white, and motion.js puts
   an ambient aura behind the first section of every page — so the art lands as
   a white rectangle punched over a tinted backdrop, which is what makes it
   read as a picture pasted into the page. multiply maps its white onto that
   backdrop and keeps the ink, so the art sits *in* the hero instead of on it.

   The blend is contained by .hero-aura's isolation, so it never escapes the
   hero band. With JS off there is no aura, the backdrop is plain #FFFFFF, and
   multiply is a no-op — the art looks exactly as it does today.

   The media column deliberately carries no .reveal. That class sets
   will-change:opacity,transform, which creates a permanent stacking context
   and would isolate the blend forever, leaving the white plate. Dropping it
   also stops the page's LCP element fading up from opacity:0, which delays
   the paint it is measured by. */
.hero-media img { mix-blend-mode:multiply; }

/* Let the art run past its column toward the viewport edge so the hero stops
   reading as "text column, picture column". Capped at 96px so it stays a
   deliberate break-out rather than a runaway image on wide monitors, and only
   applied once .grid-12 is genuinely two columns — below 768px it stacks and
   any bleed would just overflow. .hero-band does the clipping. */
@media (min-width:768px){
  .hero-media {
    margin-right:calc(-1 * clamp(0px, 50vw - (var(--container) / 2) + var(--gutter), 96px));
  }
}

/* Marks the split hero on service, industry and sub-service pages. Two jobs:

   1. Clips the .hero-media bleed. .hero-aura sets the same overflow, but that
      arrives via JS — without it here a bleeding image would give the page a
      horizontal scrollbar.
   2. Scopes the long-headline type below. */
.hero-band { overflow:hidden; }

/* These pages' H1s are full sentences ("Website migration without downtime,
   lost data or lost rankings") sitting in a half-width column, not the short
   phrases .h1 was scaled for. At --text-4xl they ran to five or six lines and
   swamped the hero. Step the size down, and relax the tracking and leading —
   -0.03em and 1.05 are display settings that only read well across one or two
   big lines, and turn cramped over four. */
.hero-band .h1 {
  font-size:var(--text-hero-long);
  line-height:1.14;
  letter-spacing:-0.02em;
}

/* ---- Dark band ---------------------------------------------------------- */
.dark-band { background:var(--color-dark); color:var(--color-on-dark); }
/* Any dark context (.dark-band, .bg-dark, .on-dark) must re-map the muted
   text colours — the light-theme ink greys fail contrast on midnight. */
.dark-band .lead, .dark-band .muted,
.on-dark .lead, .on-dark .muted, .bg-dark .lead, .bg-dark .muted { color:var(--color-on-dark-muted); }
.on-dark .subtle, .bg-dark .subtle, .dark-band .subtle { color:#8FA0B6; }

/* ---- CTA gradient band -------------------------------------------------- */
/* Slowly drifting gradient + a soft light sweep across the band. */
.cta-gradient {
  position:relative; overflow:hidden; color:#fff;
  background:linear-gradient(105deg, #0B1220 0%, #1D4ED8 52%, #D97706 128%);
  background-size:200% 200%;
  animation:gradient-drift 14s var(--ease-smooth) infinite;
}
.cta-gradient::after {
  content:''; position:absolute; inset:-50%;
  background:radial-gradient(circle at 50% 50%, rgba(255,255,255,.16), transparent 55%);
  animation:orbit 18s linear infinite; pointer-events:none;
}
.cta-gradient > * { position:relative; z-index:1; }
.cta-gradient .lead { color:rgba(255,255,255,.9); }

/* ---- Trust / logo bar --------------------------------------------------- */
.trust-label { color:var(--color-ink-muted); font-size:var(--text-sm); font-weight:600; text-align:center; }
.logo-row { display:flex; flex-wrap:wrap; gap:var(--space-6); justify-content:center; align-items:center; }
/* Client marks are pre-rendered mono at 2x (280x96) by
   scripts/build-client-logos.php. object-fit:contain holds every one inside the
   same 140x48 slot whatever its proportions, so a long wordmark and a squarer
   badge sit on one optical line instead of each claiming its own height. */
.client-logo { width:140px; height:48px; object-fit:contain; }

/* ---- Header ------------------------------------------------------------- */
/* Frosted-glass sticky header. Height eases down once stuck, so the page
   feels like it "settles" as you scroll. */
.site-header {
  position:sticky; top:0; z-index:50;
  background:rgba(255,255,255,.72);
  -webkit-backdrop-filter:saturate(180%) blur(16px);
  backdrop-filter:saturate(180%) blur(16px);
  border-bottom:1px solid transparent;
  transition:box-shadow var(--dur-slow) var(--ease), border-color var(--dur-slow) var(--ease),
             background var(--dur-slow) var(--ease);
}
.site-header.is-stuck {
  background:rgba(255,255,255,.85);
  box-shadow:var(--shadow-md); border-bottom-color:var(--color-line);
}
.header-inner {
  display:flex; align-items:center; justify-content:space-between; gap:var(--space-6);
  height:80px; transition:height var(--dur-slow) var(--ease-out);
}
.site-header.is-stuck .header-inner { height:66px; }
/* The wordmark is artwork now (assets/img/digiradix-logo.svg), not text set in Sora —
   the square dot is drawn, not typed. Height drives it and width follows the
   img's own ratio, so the one number to change is the height. */
.brand {
  display:inline-flex; align-items:center;
  transition:transform var(--dur) var(--ease-spring);
}
.brand img { display:block; height:26px; width:auto; }
@media (max-width:480px){ .brand img { height:22px; } }
.brand:hover { transform:translateY(-1px); }

/* Scroll progress bar, width driven by motion.js. */
.scroll-progress {
  position:fixed; top:0; left:0; height:3px; z-index:70;
  width:var(--progress,0%); background:var(--gradient-cta);
  transition:width 90ms linear; pointer-events:none;
}

.nav-primary { display:none; }
@media (min-width:900px){ .nav-primary { display:flex; align-items:center; gap:var(--space-6); } }
.nav-link { font-weight:500; color:var(--color-ink); padding:var(--space-2) 0; position:relative; display:inline-flex; align-items:center; gap:.25rem; }
.nav-link::after {
  content:''; position:absolute; left:0; right:100%; bottom:-2px; height:2px;
  background:var(--color-accent); transition:right var(--dur) var(--ease-out);
}
.nav-link:hover::after, .nav-link[aria-current="page"]::after { right:0; }
.nav-item { position:relative; }
.nav-item > .nav-link svg { width:16px; height:16px; transition:transform var(--dur) var(--ease); }
.nav-item:hover > .nav-link svg, .nav-item:focus-within > .nav-link svg { transform:rotate(180deg); }

/* Services dropdown */
.dropdown {
  position:absolute; top:100%; left:50%; transform:translateX(-50%) translateY(6px);
  min-width:340px; background:var(--color-bg); border:var(--border);
  border-radius:var(--radius-lg); box-shadow:var(--shadow-header);
  padding:var(--space-3); opacity:0; visibility:hidden;
  transition:opacity var(--dur) var(--ease), transform var(--dur) var(--ease-out), visibility var(--dur);
}
.nav-item:hover .dropdown, .nav-item:focus-within .dropdown {
  opacity:1; visibility:visible; transform:translateX(-50%) translateY(4px);
}
.dropdown a { display:flex; gap:var(--space-3); align-items:flex-start; padding:var(--space-3); border-radius:var(--radius); transition:background var(--dur) var(--ease); }
.dropdown a:hover { background:var(--color-surface); }
.dropdown .dd-title { font-weight:600; }
.dropdown .dd-desc { font-size:var(--text-sm); color:var(--color-ink-muted); }

/* Mega-menu: services grouped, built sub-services listed beneath each. */
.dropdown-mega { min-width:0; width:min(920px, calc(100vw - 2rem)); display:grid; grid-template-columns:repeat(3,1fr); gap:var(--space-2) var(--space-6); max-height:min(72vh, 640px); overflow-y:auto; }
.dd-service { display:flex; flex-direction:column; }
.dd-subs { display:grid; gap:2px; margin:0 0 var(--space-2) calc(36px + var(--space-3) + var(--space-3)); }
.dropdown-mega .dd-subs a { display:block; padding:.35rem var(--space-3); font-size:var(--text-sm); color:var(--color-ink-muted); border-radius:var(--radius-sm); }
.dropdown-mega .dd-subs a:hover { color:var(--color-accent); background:var(--color-accent-tint); }
.dropdown-mega .dd-subs a.dd-see-all { color:var(--color-accent); font-weight:600; margin-top:var(--space-1); }
.dropdown-mega .dd-subs a.dd-see-all:hover { background:var(--color-accent-tint); }
.dd-all { grid-column:1 / -1; border-top:var(--border); margin-top:var(--space-1); padding-top:var(--space-4)!important; font-weight:600; color:var(--color-accent)!important; }
.dd-all:hover { background:transparent!important; text-decoration:underline; text-underline-offset:3px; }
@media (max-width:1150px){ .dropdown-mega { grid-template-columns:repeat(2,1fr); width:min(680px, calc(100vw - 2rem)); gap:var(--space-2) var(--space-5); } }
@media (max-width:750px){ .dropdown-mega { grid-template-columns:1fr; width:min(360px, calc(100vw - 2rem)); } }

/* Mobile: second-level sub-service links */
.mobile-nav-links .sub-2 { font-size:var(--text-base); color:var(--color-ink-subtle); padding:var(--space-2) 0 var(--space-2) var(--space-6); border-bottom:none; }

.header-cta { display:none; }
@media (min-width:900px){ .header-cta { display:inline-flex; } }

/* Hamburger */
.nav-toggle { display:inline-flex; align-items:center; justify-content:center; width:44px; height:44px; margin-right:calc(var(--space-2) * -1); }
@media (min-width:900px){ .nav-toggle { display:none; } }
.nav-toggle svg { width:26px; height:26px; }

/* Mobile panel */
.mobile-nav {
  position:fixed; inset:0; z-index:60; background:var(--color-bg);
  display:flex; flex-direction:column; padding:var(--gutter);
  /* The panel MUST scroll itself. It is position:fixed at inset:0, so it is
     exactly viewport-height, while its content is the whole nav — Services,
     every service, every sub-service, then Pricing/About/Blog and the CTA,
     which runs to roughly three screens on a phone. main.js also locks the
     page behind it with body.no-scroll, so without this nothing scrolls at
     all and everything below the fold is simply unreachable.
     overscroll-behavior:contain stops a scroll that reaches the end of the
     panel from chaining to the locked body underneath. */
  overflow-y:auto;
  -webkit-overflow-scrolling:touch;
  overscroll-behavior:contain;
  /* Clear the iOS home indicator so the last link is not under it. */
  padding-bottom:calc(var(--gutter) + env(safe-area-inset-bottom, 0px));
  transform:translateX(100%); transition:transform var(--dur-slow) var(--ease-out);
  visibility:hidden;
}
.mobile-nav.is-open { transform:translateX(0); visibility:visible; }
/* Sticks to the top of the now-scrolling panel: with a menu several screens
   tall, a close button that scrolls away strands the reader. Sticky positions
   against .mobile-nav (the scroll container), and the background stops links
   showing through as they pass underneath. */
.mobile-nav-head {
  display:flex; align-items:center; justify-content:space-between; height:72px;
  position:sticky; top:0; z-index:1; background:var(--color-bg);
}
.mobile-nav-links { display:flex; flex-direction:column; gap:var(--space-2); margin-top:var(--space-6); }
.mobile-nav-links a { font-family:var(--font-display); font-size:var(--text-2xl); font-weight:600; padding:var(--space-3) 0; border-bottom:var(--border); }
.mobile-nav-links .sub { font-size:var(--text-lg); color:var(--color-ink-muted); padding-left:var(--space-4); border-bottom:none; }
.mobile-nav .btn { margin-top:var(--space-6); }

/* ---- Footer ------------------------------------------------------------- */
.site-footer { background:var(--color-dark); color:var(--color-on-dark); }
.footer-grid { display:grid; gap:var(--space-8); grid-template-columns:1fr; }
@media (min-width:640px){ .footer-grid { grid-template-columns:1.5fr 1fr 1fr 1fr; } }
.footer-col h3 { font-size:var(--text-sm); text-transform:uppercase; letter-spacing:.1em; color:var(--color-on-dark-muted); font-weight:600; margin-bottom:var(--space-4); }
.footer-col ul { display:grid; gap:var(--space-3); }
.footer-col a { color:var(--color-on-dark); transition:color var(--dur) var(--ease); }
.footer-col a:hover { color:var(--color-accent-on-dark); }
/* The on-dark cut of the same artwork: the amber dot is in the file, because
   brand blue is near-invisible on the midnight footer. */
.footer-brand { display:inline-block; }
.footer-brand img { display:block; height:30px; width:auto; }
/* Social row. Scoped to .footer-col deliberately: `.footer-col ul` and
   `.footer-col a` above are more specific than a bare `.social-row`, so an
   unscoped rule would lose the cascade and inherit the stacked-list layout. */
.footer-col .social-row {
  display:flex; flex-wrap:wrap; gap:var(--space-3);
  list-style:none; padding:0;
}
.footer-col .social-link {
  display:grid; place-items:center; width:40px; height:40px;
  border-radius:var(--radius-full);
  border:1px solid var(--color-line-on-dark);
  color:var(--color-on-dark-muted);
  transition:color var(--dur) var(--ease), background var(--dur) var(--ease),
             border-color var(--dur) var(--ease), transform var(--dur) var(--ease-spring);
}
.footer-col .social-link svg { width:18px; height:18px; }
/* Inverts to the light chip on hover rather than tinting blue: these are brand
   glyphs at 18px, and --color-accent-on-dark is too low-contrast against the
   midnight ground to keep their finer shapes (TikTok's notch, the X counter)
   legible. */
.footer-col .social-link:hover,
.footer-col .social-link:focus-visible {
  color:var(--color-dark); background:var(--color-on-dark);
  border-color:var(--color-on-dark); transform:translateY(-2px);
}
/* Seven at 40px + 12px gaps need 352px, more than a 360px phone leaves after
   the container's padding, so the last one wrapped alone. 36 + 8 fits in 300. */
@media (max-width:480px){
  .footer-col .social-row  { gap:var(--space-2); }
  .footer-col .social-link { width:36px; height:36px; }
}

/* ---- Share this page ----------------------------------------------------
   Follow and Share are two icon rows side by side. They mean different things
   (where we publish vs. where you can send this page), so the share controls
   are deliberately NOT identical to the social ones: rounded squares, not
   circles. Same size, same hover, different silhouette — enough that the eye
   reads them as a different kind of control without a second colour or a
   second border weight. */
.footer-connect {
  display:flex; flex-wrap:wrap; gap:var(--space-8) var(--space-9);
  margin-top:var(--space-7);
}
.footer-connect > .footer-col { flex:0 1 auto; }

.footer-col .share-row {
  display:flex; flex-wrap:wrap; gap:var(--space-3);
  list-style:none; padding:0;
}
/* button resets: these are <button> and <a> in one row and must not diverge. */
.footer-col .share-link {
  display:grid; place-items:center; width:40px; height:40px;
  border-radius:var(--radius-sm);
  border:1px solid var(--color-line-on-dark);
  background:none; padding:0; font:inherit; cursor:pointer;
  color:var(--color-on-dark-muted);
  transition:color var(--dur) var(--ease), background var(--dur) var(--ease),
             border-color var(--dur) var(--ease), transform var(--dur) var(--ease-spring);
}
.footer-col .share-link svg { width:18px; height:18px; }
.footer-col .share-link:hover,
.footer-col .share-link:focus-visible {
  color:var(--color-dark); background:var(--color-on-dark);
  border-color:var(--color-on-dark); transform:translateY(-2px);
}
/* Confirmation of a copy, which is otherwise completely invisible: the
   clipboard changed and nothing on screen did. Held for 2s by main.js. */
.footer-col .share-link.is-copied {
  color:var(--color-dark); background:var(--color-accent-on-dark);
  border-color:var(--color-accent-on-dark);
}
.share-status {
  min-height:1.25em; margin-top:var(--space-3);
  font-size:var(--text-sm); color:var(--color-accent-on-dark);
}

@media (max-width:480px){
  .footer-connect { gap:var(--space-7); }
  .footer-col .share-row  { gap:var(--space-2); }
  .footer-col .share-link { width:36px; height:36px; }
}

.footer-bottom {
  border-top:1px solid var(--color-line-on-dark); margin-top:var(--space-9);
  padding-top:var(--space-6); display:flex; flex-wrap:wrap; gap:var(--space-4);
  justify-content:space-between; align-items:center;
  font-size:var(--text-sm); color:var(--color-on-dark-muted);
}
.footer-bottom nav { display:flex; flex-wrap:wrap; gap:var(--space-5); }
.footer-bottom a:hover { color:var(--color-accent-on-dark); }

/* ---- WhatsApp float ----------------------------------------------------- */
.wa-float {
  position:fixed; right:clamp(1rem,4vw,2rem); bottom:clamp(1rem,4vw,2rem); z-index:40;
  width:56px; height:56px; border-radius:var(--radius-full);
  background:var(--color-teal); color:#fff;
  display:flex; align-items:center; justify-content:center;
  box-shadow:0 8px 24px rgba(13,148,136,.38);
  opacity:0; visibility:hidden; transform:translateY(12px);
  transition:opacity var(--dur) var(--ease), transform var(--dur) var(--ease-out), visibility var(--dur);
}
.wa-float.is-visible { opacity:1; visibility:visible; transform:translateY(0); }
.wa-float svg { width:28px; height:28px; }

/* ---- Breadcrumb --------------------------------------------------------- */
.breadcrumb { font-size:var(--text-sm); color:var(--color-ink-muted); }
.breadcrumb ol { display:flex; flex-wrap:wrap; gap:var(--space-2); align-items:center; }
.breadcrumb li { display:flex; gap:var(--space-2); align-items:center; }
.breadcrumb a { transition:color var(--dur) var(--ease); }
.breadcrumb a:hover { color:var(--color-accent); }
.breadcrumb [aria-current="page"] { color:var(--color-ink); }
/* On dark bands the muted ink colour fails contrast — lift it. */
.on-dark .breadcrumb, .bg-dark .breadcrumb { color:var(--color-on-dark-muted); }
.on-dark .breadcrumb [aria-current="page"], .bg-dark .breadcrumb [aria-current="page"] { color:var(--color-on-dark); }
.on-dark .breadcrumb a:hover, .bg-dark .breadcrumb a:hover { color:var(--color-accent-on-dark); }

/* ---- Scroll reveal ------------------------------------------------------ */
/* Base reveal + directional variants. --d is a per-element stagger delay set
   by motion.js so grids cascade instead of all landing at once. */
.reveal {
  opacity:0; transform:translateY(28px);
  transition:opacity var(--dur-reveal) var(--ease-out),
             transform var(--dur-reveal) var(--ease-out),
             filter var(--dur-reveal) var(--ease-out);
  transition-delay:var(--d,0ms);
  will-change:opacity, transform;
}
.reveal.is-in { opacity:1; transform:none; filter:none; }
/* Horizontal variants only above the 12-col breakpoint. Below it the columns
   are stacked, so sideways motion is meaningless — and a pre-animation
   translateX pushes the element past the viewport and creates a horizontal
   scrollbar on phones. Small screens keep the default vertical rise. */
@media (min-width:768px){
  .reveal-left  { transform:translateX(-40px); }
  .reveal-right { transform:translateX(40px); }
}
.reveal-scale { transform:scale(.92); }
.reveal-blur  { filter:blur(12px); transform:translateY(20px); }
.reveal-flip  { transform:perspective(900px) rotateX(12deg) translateY(30px); transform-origin:top center; }

/* Word-by-word headline reveal (motion.js wraps each word). */
.split-word { display:inline-block; overflow:hidden; vertical-align:bottom; }
.split-word > span {
  display:inline-block;
  transform:translateY(110%) rotate(4deg); opacity:0;
  transition:transform var(--dur-reveal) var(--ease-out), opacity var(--dur-slow) var(--ease-out);
  transition-delay:var(--d,0ms);
}
.is-in .split-word > span, .split-word.is-in > span { transform:none; opacity:1; }

/* ---- Utilities ---------------------------------------------------------- */
.text-center { text-align:center; }
.mt-4{margin-top:var(--space-4);} .mt-5{margin-top:var(--space-5);} .mt-6{margin-top:var(--space-6);}
.mt-8{margin-top:var(--space-8);} .mt-9{margin-top:var(--space-9);}
.btn-row { display:flex; flex-wrap:wrap; gap:var(--space-4); }
.divider { height:1px; background:var(--color-line); border:none; }
.eyebrow-accent { color:var(--color-accent); }
.sr-only { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0; }
.no-scroll { overflow:hidden; }

/* ---- Form spam gate + submission notices ---------------------------------
   .hp-field is the honeypot from form_meta(). Moved off-screen rather than
   display:none, which the bots worth catching already skip. It must never be
   reachable: no height, no tab stop (set on the input), aria-hidden in markup. */
.hp-field {
  position:absolute; left:-9999px; top:auto;
  width:1px; height:1px; overflow:hidden;
}

.form-notice {
  display:flex; gap:var(--space-3); align-items:flex-start;
  margin-bottom:var(--space-5);
  padding:var(--space-4) var(--space-5);
  border:1px solid var(--color-warm); border-radius:var(--radius);
  background:var(--color-warm-tint);
}
.form-notice p { color:var(--color-ink); margin:0; }
.form-notice strong { display:block; }
.form-notice a { color:var(--color-accent); text-decoration:underline; }
.form-notice-icon { flex:none; color:var(--color-warm); display:flex; margin-top:2px; }
.form-notice-icon svg { width:20px; height:20px; }
/* Focused programmatically on load, so it must not draw a stray outline for
   mouse users while staying visible to keyboard users. */
.form-notice:focus { outline:none; }
.form-notice:focus-visible { outline:2px solid var(--color-accent); outline-offset:2px; }

/* On the two midnight bands (home, audit) the light tint is invisible. */
.form-notice-on-dark {
  background:rgba(245,158,11,.12); border-color:var(--color-warm-bright);
}
.form-notice-on-dark p { color:var(--color-on-dark); }
.form-notice-on-dark .form-notice-icon { color:var(--color-warm-bright); }
.form-notice-on-dark a { color:var(--color-accent-on-dark); }

/* ---- Legal documents -----------------------------------------------------
   Privacy / Terms / Refund. Long-form reading copy, so everything here keeps a
   left edge (see the note in the centred layer at the foot of this file).
   .legal-list needs its own bullets because `ul,ol { list-style:none }` at the
   top of this file strips them site-wide for nav and card lists. */
.legal-doc h2 { scroll-margin-top:96px; }
/* Prose links stay visibly underlined here, rather than sweeping in on hover
   like the rest of the site. These documents cross-reference each other and
   the contact address, and a reader who cannot see that "Terms of Service" is
   a link will not find it. Needs .legal-doc.prose (0,2,1) to beat motion.css's
   later `.prose a` (0,1,1), which sets the sweep's background-size to 0. */
.legal-doc.prose a {
  background-size:100% 2px;
  color:var(--color-accent);
}
.legal-doc.prose a:hover { color:var(--color-ink); }
/* The contents list is navigation, not prose, so it opts back out. Thirteen to
   nineteen underlined blue rows read as a wall of links; muted until hover is
   calmer and the numbering already marks them as a list to click. */
.legal-doc.prose .legal-toc a { background-size:0 2px; color:var(--color-ink-muted); }
.legal-doc.prose .legal-toc a:hover { background-size:100% 2px; color:var(--color-accent); }
/* Symmetric block margins: a list is a block in the flow, not a tail on the
   paragraph above it. With margin-top only, the paragraph that follows a list
   sat hard against the final bullet and read as part of it. */
.legal-list { display:grid; gap:var(--space-3); margin-block:var(--space-4); }
.legal-list li {
  position:relative; padding-inline-start:var(--space-5);
  color:var(--color-ink-muted); line-height:1.7;
}
.legal-list li::before {
  content:""; position:absolute; inset-inline-start:0; top:.65em;
  width:6px; height:6px; border-radius:var(--radius-full);
  background:var(--color-accent);
}
.legal-toc {
  border:1px solid var(--color-line); border-radius:var(--radius);
  padding:var(--space-5) var(--space-6); background:var(--color-surface);
}
.legal-toc ol { display:grid; gap:var(--space-2); margin-top:var(--space-3); counter-reset:toc; }
.legal-toc li { counter-increment:toc; }
.legal-toc li::before { content:counter(toc) ". "; color:var(--color-ink-subtle); }
.legal-toc a { color:var(--color-ink-muted); text-decoration:none; }
.legal-toc a:hover { color:var(--color-accent); text-decoration:underline; }

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

/* ==========================================================================
   CENTRED LAYOUT LAYER
   Must stay LAST in this file. Several of these rules override component
   defaults at identical specificity (e.g. .step-badge's `left:0`), so they
   win on source order alone — moving this block higher silently breaks
   badge centring and the hero alignment.

   The site is centre-aligned: section headings, hero blocks, cards and
   feature items centre their content.

   Deliberately NOT centred: long-form reading copy (blog bodies, legal
   pages, FAQ answers), form fields, and list items with leading icons.
   Centred paragraphs give the eye no consistent left edge to return to on
   each new line, which hurts readability at these line lengths — those are
   centred as a *block* (auto margins) with their text left-aligned.
   ========================================================================== */

.section-head.is-centered { margin-inline:auto; text-align:center; }
.section-head.is-centered .lead,
.section-head.is-centered .measure { margin-inline:auto; }

/* ---- Hero -----------------------------------------------------------------
   HOME ONLY gets the centred, single-column hero. Every other page keeps the
   original split layout: copy on the left, media (or form) on the right.
   The :is(main, main > article) form is needed because blog posts wrap their
   hero in an <article>, so a plain `main > section` selector misses them. */
.page-home :is(main, main > article) > section:first-of-type { text-align:center; }
/* Centres non-grid heroes, whose inner block is a max-width div. */
.page-home :is(main, main > article) > section:first-of-type .container > * { margin-inline:auto; }
.page-home :is(main, main > article) > section:first-of-type .grid-12 { justify-items:center; }
.page-home :is(main, main > article) > section:first-of-type .grid-12 > [class*="col-"] { grid-column:1 / -1; width:100%; }
.page-home :is(main, main > article) > section:first-of-type .lead,
.page-home :is(main, main > article) > section:first-of-type .measure { margin-inline:auto; }
.page-home :is(main, main > article) > section:first-of-type .btn-row { justify-content:center; }
.page-home :is(main, main > article) > section:first-of-type .breadcrumb ol { justify-content:center; }
/* Lists keep a left edge for scanning; the list itself is centred. */
.page-home :is(main, main > article) > section:first-of-type .check-list { text-align:left; width:fit-content; margin-inline:auto; }
/* Forms stay left-aligned — centred labels and inputs are hard to scan. */
.page-home :is(main, main > article) > section:first-of-type form { text-align:left; }
.page-home :is(main, main > article) > section:first-of-type form .btn-row { justify-content:flex-start; }

/* ---- Split (two-column) sections ----------------------------------------
   Columns in a .grid-12 are LEFT-aligned, headings included. A heading centred
   over a half-width column reads as a mistake next to the body copy beside it.

   No rules needed: left is the inherited default, and the only thing that
   centres a hero is the `.page-home` block above. An earlier version centred
   these columns and then needed a `body:not(.page-home)` block to undo it on
   inner pages — both were deleted rather than layering a third override.
   Full-width section headings (.section-head.is-centered) are still centred,
   as are .card and .feature contents; those are handled separately below. */

/* ---- Cards and feature items -------------------------------------------- */
.card { text-align:center; }
.card .icon-chip { margin-inline:auto; }
.card .check-list, .card .cmp { text-align:left; }
.card .btn-row { justify-content:center; }

/* A card whose icon sits BESIDE the copy rather than above it. The centred
   default above is written for stacked cards in a grid; in a horizontal row it
   centres the text against a left-aligned icon, which reads as a mistake. The
   chip also has to give up its auto inline margins — as a flex item those
   absorb the free space and push it away from the copy it belongs to. */
.card-row { display:flex; gap:var(--space-4); align-items:flex-start; text-align:left; }
.card-row > .icon-chip { flex:none; margin-inline:0; }

/* A card whose content is a LIST — a heading over rows of icon (or numeral)
   plus copy — rather than a standalone tile. Distinct from .card-row: there
   the card is itself the row, here it contains many. Centring these strands
   the heading over left-aligned rows, and the auto inline margins on
   .icon-chip absorb each row's free space and shove the chip away from the
   text it labels, so rows drift apart at different widths. Named for the
   .section-head.is-start convention already in this file. */
.card.is-start { text-align:left; }
.card.is-start .icon-chip { margin-inline:0; }

/* A form is never a centred tile, whatever it is wrapped in. Centred labels
   float away from the input they name and give the eye no common left edge to
   run down, which is the same reason the home hero's form already opts out.
   That rule is scoped to .page-home, so the contact and audit forms never got
   it; this covers any form built on .card. */
form.card { text-align:left; }
form.card .btn-row { justify-content:flex-start; }
.feature { text-align:center; }
.feature .icon-chip { margin-inline:auto; }
.feature::after { left:50%; transform:translateX(-50%) scaleX(0); transform-origin:center; }
.feature:hover::after { transform:translateX(-50%) scaleX(1); }

/* ---- Process timeline, 4-across only ------------------------------------ */
@media (min-width:1000px){
  .process-step { text-align:center; }
  .step-badge { left:50%; transform:translateX(-50%); }
  .process-step:hover .step-badge { transform:translateX(-50%) translateY(-3px) scale(1.05); }
  /* Connector spans from this badge's right edge to the next badge's left
     edge: start at centre+28px, end 28px short of the next column's centre. */
  .process-step::before {
    inset-inline-start:calc(50% + 34px);
    inset-inline-end:calc(-50% - (var(--space-6) / 2) + 34px);
  }
}
