/* ==========================================================================
   DAvIA Labs — styles
   Dark by default: the page is mostly code, and code is read on dark. Light
   is supported and every syntax token has a second value for it, because a
   palette tuned for #020a1c is unreadable on white.
   Navy and gold are taken from logo_draft.png; see the token notes below.
   ========================================================================== */

:root {
  color-scheme: dark;

  /* Black, gold and silver. The base is true #000000 — the page was navy
     until 2026-09-25 and the blue cast came from --muted, not from the
     background. Sections separate by elevation instead: bg, bg-soft, card each
     a step lighter, which is all the separation a black page needs.

     Both text colours are silver sampled from the logo's own wordmark faces
     (#f5f5f5 in the highlights, #dfddde mean) rather than invented, and both
     are neutral, so nothing on the page carries a hue except the gold. The
     code well returns to true black: on a black page a lighter code panel
     reads as a raised card, which is wrong for a cut-out. */
  --bg: #000000;
  --bg-soft: #0a0a0c;
  --card: #121214;
  --ink: #f2f1f2;
  --muted: #b5b3b8;

  /* Three weights of line, all gold-tinted rather than grey: on a black page
     a neutral rule reads as dirt, and the brief is gold and silver only. Each
     is the accent driven down into the black, not a separate hue. --rule
     separates sections, --border delineates components, and --border-strong is
     only ever used where an edge has to be found by eye. */
  --rule: #2b2418;
  --border: #3d3322;
  --border-strong: #6b5a3a;

  /* The logo gold, sampled from logo_draft.png (the chevron's brightest
     pass, #fbd465). It is the only chromatic thing in the interface, which is
     what makes the syntax colours read as content rather than decoration. */
  /* The sticky bar. In dark these cannot be derived from --bg: tinting black
     with black is invisible, so at rest the glass LIGHTENS instead, the way
     dark-mode system chrome does. Scrolled it has to go dark again, because
     the product figures pass underneath and one of them is a cream card. */
  --header-bg: rgba(255, 255, 255, 0.055);
  /* 0.58 is the floor, not a preference. Over the cream Alma Secret card the
     nav reads 4.98:1 at this value and 3.75:1 at 0.50. It only gets to be this
     transparent because --nav-link is silver here rather than the muted grey:
     at --muted the same 0.58 gives 2.70:1. */
  --header-bg-scrolled: rgba(0, 0, 0, 0.58);
  /* The gold of the supplied brain artwork, sampled from it. */
  --mark-gold: #fdd507;
  --consult: #38bdf8;
  --nav-link: var(--ink);
  --nav-link-hover: var(--accent);

  /* The wordmark's two metals, read row by row off the artwork's own letters
     rather than invented. --metal is the silver of D, the first A, V and I;
     --metal-au is the last A, which is that same silver for its top half and
     then turns gold — the transition sits at 48-68% of the cap height and the
     stops below are the sampled values at those rows.

     The artwork's metal actually varies ACROSS each stroke (a bevel: bright
     core, dark edges), not down the letter. That cannot be done to text, so
     these run vertically instead — at the 22px cap height of the lockup a
     bevel would be sub-pixel anyway, while a vertical ramp still reads. */
  --metal: linear-gradient(180deg, #b9b9bb 0%, #f4f4f5 10%, #ededee 30%, #d2d2d6 48%, #a9a9b0 60%, #e6e6ea 82%, #f8f8fa 100%);
  /* Same ramp read upwards. The first A is the V turned over, and a flipped
     element carries its background over with it, so this cancels that out and
     the metal still runs bright-at-top like the letters beside it. */
  --metal-up: linear-gradient(0deg, #b9b9bb 0%, #f4f4f5 10%, #ededee 30%, #d2d2d6 48%, #a9a9b0 60%, #e6e6ea 82%, #f8f8fa 100%);
  --metal-au: linear-gradient(180deg, #acacac 0%, #e7e7e7 6%, #ebeaea 27%, #dcdad6 41%, #e2d8ca 48%, #e7dbbd 55%, #f5e198 62%, #eda617 68%, #e6970e 75%, #dc8909 82%, #de9827 89%, #d3972d 100%);

  --accent: #fbd465;
  --accent-ink: #000000;
  --accent-soft: rgba(251, 212, 101, 0.12);



  /* Figure tokens. The product mocks are depictions of real UIs and keep each
     product's own palette — see the README — but the generic chart furniture
     (bars, baselines, values) belongs to this page and uses these. */
  --ink-strong: #ffffff;
  --chart-emphasis: var(--accent);
  --chart-context: #4a4a52;

  --ok: #7ee787;
  --no: #ff7b72;

  /* Wider than it was, because the container now carries its own ruled edges
     and interior padding — this keeps the text measure where it was. */
  --container: 1180px;
  --header-h: 92px;

  /* Square, everywhere. Rounded corners are the loudest tell that a page is
     marketing rather than instrumentation; both Swiss and Brutalist registers
     specify 0, and the whole page reads differently for this one value. */
  --radius: 0px;
  --radius-sm: 0px;
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* The light values live in both the media query and the explicit attribute,
   so the manual toggle wins in either direction. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    color-scheme: light;
    --bg: #ffffff;
    --bg-soft: #f7f7f5;
    --card: #f1f0ec;
    /* Pinned to #f8fafc: every light syntax token was measured against it. */
    --ink: #16161a;
    --muted: #4c4c54;
    --rule: #e8e3d8;
    --border: #d8d0c0;
    --border-strong: #9a8d76;

    /* The logo gold #fbd465 is 1.43:1 on white — unusable as text. This is
       the same hue driven down to a bronze: 6.04:1 on white, 5.43:1 on card
       and 4.81:1 on its own tint, so it clears AA on every surface the page
       puts it on and needs no second accent token. */
    /* Light already reads as glass: white over darker content. Unchanged. */
    --mark-gold: #b88700;
    --consult: #0369a1;
    /* The artwork's silver is a white-on-black metal: on white it disappears.
       Light gets the same polish inverted into graphite, and the last A ends
       in the bronze the rest of light mode already uses. */
    --metal: linear-gradient(180deg, #4b4b53 0%, #16161a 12%, #23232a 32%, #101014 50%, #45454e 62%, #1c1c22 84%, #16161a 100%);
    --metal-up: linear-gradient(0deg, #4b4b53 0%, #16161a 12%, #23232a 32%, #101014 50%, #45454e 62%, #1c1c22 84%, #16161a 100%);
    --metal-au: linear-gradient(180deg, #4b4b53 0%, #16161a 12%, #23232a 41%, #2b261c 48%, #3d3417 55%, #6b5405 62%, #8a5800 68%, #7d4f00 82%, #8a5800 100%);
    --nav-link: var(--muted);
    --nav-link-hover: var(--ink);
    --header-bg: color-mix(in srgb, var(--bg) 25%, transparent);
    --header-bg-scrolled: color-mix(in srgb, var(--bg) 74%, transparent);
    --accent: #8a5800;
    --accent-ink: #ffffff;
    --accent-soft: rgba(138, 88, 0, 0.10);


    --ink-strong: #000000;
    --chart-emphasis: var(--accent);
    --chart-context: #b4b0a8;

    --ok: #0f6e3d;
    --no: #b3261e;
  }
}

:root[data-theme="light"] {
  color-scheme: light;
  --bg: #ffffff;
  --bg-soft: #f7f7f5;
  --card: #f1f0ec;
  --ink: #16161a;
  --muted: #4c4c54;
  --rule: #e8e3d8;
  --border: #d8d0c0;
  --border-strong: #9a8d76;
  /* Light already reads as glass: white over darker content. Unchanged. */
  --mark-gold: #b88700;
  --consult: #0369a1;
  --metal: linear-gradient(180deg, #4b4b53 0%, #16161a 12%, #23232a 32%, #101014 50%, #45454e 62%, #1c1c22 84%, #16161a 100%);
  --metal-up: linear-gradient(0deg, #4b4b53 0%, #16161a 12%, #23232a 32%, #101014 50%, #45454e 62%, #1c1c22 84%, #16161a 100%);
  --metal-au: linear-gradient(180deg, #4b4b53 0%, #16161a 12%, #23232a 41%, #2b261c 48%, #3d3417 55%, #6b5405 62%, #8a5800 68%, #7d4f00 82%, #8a5800 100%);
  --nav-link: var(--muted);
  --nav-link-hover: var(--ink);
  --header-bg: color-mix(in srgb, var(--bg) 25%, transparent);
  --header-bg-scrolled: color-mix(in srgb, var(--bg) 74%, transparent);
  --accent: #8a5800;
  --accent-ink: #ffffff;
  --accent-soft: rgba(138, 88, 0, 0.10);
  --ink-strong: #000000;
  --chart-emphasis: var(--accent);
  --chart-context: #b4b0a8;
  --ok: #0f6e3d;
  --no: #b3261e;
}

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

/* Set by main.js for the single frame in which the theme attribute flips. See
   the note there: without it, a transitioned `color` keeps the outgoing
   theme's value and .btn-ghost ends up white on white in light mode. */
.theme-switch, .theme-switch *, .theme-switch *::before, .theme-switch *::after {
  transition: none !important;
}

html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

img, svg { max-width: 100%; }
.container { width: min(100% - 40px, var(--container)); margin-inline: auto; }

/* The container's ruled edges were removed on David's instruction. The inner
   padding stays: --container is 1180px to pay for it, so dropping the padding
   as well would widen every text measure on the page. */
/* The header is in this list now: without it the lockup sat 33px left of the
   hero copy, because every other container carries this padding and it did
   not. */
.section > .container,
.hero > .container,
.site-header > .container,
.site-footer > .container {
  padding-inline: clamp(16px, 2.6vw, 34px);
}
@media (max-width: 560px) {
  .section > .container,
  .hero > .container,
  .site-header > .container,
  .site-footer > .container { padding-inline: 0; }
}

a { color: inherit; text-decoration: none; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 0; }

.skip-link { position: absolute; left: -9999px; background: var(--accent); color: var(--accent-ink); padding: 10px 16px; z-index: 100; }
.skip-link:focus { left: 8px; top: 8px; }

h1, h2, h3 { line-height: 1.15; font-weight: 650; letter-spacing: -0.02em; }
h1 { font-size: clamp(2.1rem, 5vw, 3.4rem); letter-spacing: -0.03em; }
/* Keeps a hyphenated compound whole. Without it the headline broke as
   "data-" / "driven", which reads as a typo rather than a line break. */
.nw { white-space: nowrap; }
h2 { font-size: clamp(1.5rem, 3vw, 2.1rem); }
h3 { font-size: 1.02rem; }

.lead { font-size: clamp(1rem, 1.5vw, 1.14rem); color: var(--muted); max-width: 60ch; }

/* Figures never reflow as they change: the technical register depends on
   columns of numbers staying in column. */
.compare, .hero-meta, .stack-ix, .integration span {
  font-variant-numeric: tabular-nums;
}

/* The section label is the workhorse of this direction. There is no display
   typeface available — the CSP forbids an external font-src — so the technical
   register is carried entirely by the mono stack at small size, wide tracking
   and upper case, closed by a rule that runs out toward the margin. */
.eyebrow {
  display: flex;
  align-items: center;
  gap: 12px;
  font-family: var(--mono);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
.eyebrow .ix { color: var(--accent); font-weight: 600; }
/* Gold, fading out — it used --rule, the dimmest of the three line weights,
   and read as almost nothing on black. Same treatment as the divider in the
   logo itself. Decorative, so no contrast floor applies. */
.eyebrow::after {
  content: "";
  height: 1px;
  flex: 1;
  max-width: 150px;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--accent) 70%, transparent),
    color-mix(in srgb, var(--accent) 18%, transparent) 70%,
    transparent);
}
.contact-box .eyebrow { justify-content: center; }
.contact-box .eyebrow::after { display: none; }

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

.site-header {
  position: sticky; top: 0; z-index: 50;
  /* Two states, because one opacity cannot serve both. At rest the bar sits
     over the hero, which is effectively black (median 0, max 47), so it can be
     nearly clear — the nav still reads at 7.6:1. Once scrolling, the product
     figures pass underneath and one of them is a cream card (#faf6f0); the nav
     is --muted and drops to 3.5:1 at 65%, so the scrolled state has to hold
     around 74%. Measured, not guessed: if you lighten either value, re-check
     it against that cream card and not against the black. */
  background: var(--header-bg);
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  border-bottom: 1px solid transparent;
  transition: background-color 0.28s var(--ease), border-color 0.25s var(--ease);
}
.site-header.is-scrolled {
  background: var(--header-bg-scrolled);
}
.site-header.is-scrolled { border-bottom-color: var(--border); }
.header-inner { display: flex; align-items: center; gap: 14px; height: var(--header-h); }
/* The header runs wider than the page (1380 against 1180): six links, two
   buttons, the language switch and the theme toggle need the room, and the
   Spanish labels need more of it. */
.site-header > .container { width: min(100% - 40px, 1380px); }

/* Two rows now — wordmark over tagline — with the mark spanning both, which
   is the lockup from logo_draft.png. */
/* padding-right reserves the signature's overhang. It is absolutely
   positioned, so it contributes nothing to the grid on its own and ran under
   the theme toggle on narrow screens. It has to go HERE and not on .logo-text:
   that is the signature's positioning context, so padding there just widens
   `100%` and pushes the signature further right — which made it worse. */
.logo { display: inline-grid; grid-template-columns: auto auto; align-items: center; gap: 0 9px; flex: none; padding-right: 2rem; }
/* Full brain again, and sized by the text beside it rather than by a fixed
   number: spanning both rows with align-self:stretch makes its height exactly
   the wordmark plus the tagline, so it tracks them if either changes. */
/* Brain and inverted V together, cropped straight out of
   davia_metalico(transp).png — the band at y 125-825, tightened to its own
   alpha bounds. Nothing else was touched.

   2.75rem is the measured height of the wordmark plus the tagline beside it.
   Re-measure if either the wordmark size or the row gap changes; height:100%
   cannot do this, because the inline-grid has no definite height for the
   percentage to resolve against and the image falls back to its intrinsic
   size. */
.logo-mark { grid-row: span 2; align-self: center; height: 3.15rem; width: auto; aspect-ratio: 90.5 / 82.3; flex: none; }
/* The brain is drawn, redrawn flat from the supplied artwork: traced circuitry
   on the left — right-angle traces ending in solid nodes, after the
   earlier hand-drawn mark — a dot matrix on the right, the crossbar-less A below. One gold,
   the Start-a-build button's, so it follows the theme through --accent. The viewBox
   is cropped to the ink, as the artwork was cropped to its alpha, so the
   3.15rem above still measures the mark and not its padding. */
.logo-mark { color: var(--accent); }
/* position:relative so the signature can be hung off the wordmark's
   bottom-right corner rather than sitting next to it on the baseline. */
/* justify-self:start matters: as a grid item it stretches to the column, and
   the column is as wide as the tagline below it — so `left: 100%` on the
   signature was measuring the tagline's width, not the wordmark's, and threw
   it 60px clear of the last A. */
/* padding-top drops DAVIA within its row and the zero row-gap pulls the
   tagline up against it — together that is the "more compact" David asked for,
   without moving the mark, which is centred across both rows. */
.logo-text { position: relative; display: inline-block; justify-self: start; line-height: 1; padding-top: 0.22em; }

/* The tagline. Small, tracked wide and upper case — the logo's own treatment,
   and the only way to get that register with no display face available. */
.logo-sub {
  font-family: var(--mono);
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
@media (max-width: 460px) { .logo-sub { display: none; } }
/* Phones: the language switch moves from the header into the menu, so the
   bar keeps the full-size lockup, the theme button and the menu button. */
.primary-nav .lang-switch { display: none; }
@media (max-width: 480px) {
  .header-actions > .lang-switch { display: none; }
  .primary-nav .lang-switch { display: flex; align-self: flex-start; margin-top: 14px; }
  :root .primary-nav .lang-switch a:not(.btn) { padding: 10px 14px; border-bottom: 0; font-size: 0.72rem; }
}
/* The brain is supplied artwork now, not drawn. It keeps its own metallic
   gold in both themes — it is a logotype, so it does not recolour. */

/* The wordmark is the artwork's, glyph for glyph: D ∧ V I A, where the first A
   is written without its crossbar. It used to read DA∧IA — an operator wedged
   between two pairs — which spelled something the big logo in the hero does
   not, so the two marks disagreed on the company's own name.

   Weight 300, not the 200 it was: the artwork's stem is 17px against a 122px
   cap, a ratio of 0.139, and 200 rendered 0.095. That ratio is the one
   objective handle on "the same typography" when the display face itself
   cannot be embedded under the CSP. */
.logo-word {
  font-size: 1.95rem; font-weight: 300; letter-spacing: 0.2em;
  /* line-height:1 — inherited 1.65 left ~15px of empty line box under the
     baseline, and the tagline sits below that, so it read as detached from the
     wordmark rather than part of the lockup. DAVIA has no descenders, so
     nothing is clipped. */
  line-height: 1;
  color: var(--ink); display: inline-flex; align-items: baseline;
}

/* The first A, drawn as the artwork draws it: no crossbar. In that face the
   glyph is the V turned over — 147px wide against the V's 142 at the same cap
   height, same strokes, same apex — so flipping the font's own V is not an
   approximation of it, it is it, and it inherits the wordmark's weight,
   tracking and face for free. A drawn path would have to chase all three. */
.logo-caret {
  display: inline-block;
  /* Transforms do not move the layout box, so the flip happens about the line
     box's centre, not the glyph's. The glyph sits above that centre — cap top
     and baseline are not symmetric about it — so the mirror lands it low and
     the lift puts it back. Measured, not derived: it depends on the font's
     ascent and cap height, and the stack resolves differently per platform. */
  transform: scaleY(-1) translateY(0.03em);
  /* The artwork sets this pair tight — 5px against a 122px cap, where every
     other pair is 22 to 45 — because the two facing diagonals leave a wedge of
     white that reads as a gap twice its size. A font would do it with a kern
     pair; these are two flex items, so nothing kerns them. Not closed all the
     way to the artwork's 0.04, which at this cap height is under a pixel and
     would weld them together. */
  margin-right: -0.09em;
}

/* Silver and gold, clipped to the letters.

   Each run carries its own, and .logo-word deliberately carries none. Clipping
   on the parent instead put an X where the first A should be: the parent's
   clip is taken from its descendants' glyphs as laid out, and a transform is a
   paint-time effect, so the parent painted silver through the V the right way
   up while the child painted the same V flipped over it.

   Guarded because the fallback for an unsupported background-clip is
   transparent text — an invisible logo — and .logo-word's plain --ink is what
   should show then. The background is sized to the cap band rather than the
   line box, so the stops land at the fraction of the letter they were sampled
   from; DAVIA has no descenders, so nothing falls outside it. */
@supports (background-clip: text) or (-webkit-background-clip: text) {
  .logo-ag, .logo-caret, .logo-au {
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    background-repeat: no-repeat;
    background-size: 100% 0.76em;
    background-position: 0 0.105em;
  }
  .logo-ag { background-image: var(--metal); }
  .logo-caret { background-image: var(--metal-up); }
  .logo-au { background-image: var(--metal-au); }
}
/* Text kept for screen readers where the lockup draws the letters instead. */
.logo-alt { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }

/* "Labs" sits beside the wordmark as a chip rather than more letters — it is
   the division, not part of the name. */
/* "Labs" is drawn, not set: it is the sub-brand signed against a geometric
   wordmark, and the CSP allows no script typeface to be loaded. The word is an
   SVG path with a visually-hidden text twin beside it, so the logo link still
   announces "DAvIA Labs". A box around it made it read as a UI chip rather
   than as part of the mark. */
.logo-labs {
  /* Signed onto the mark: it sits below and right of the last A, overlapping
     its corner, the way a signature sits on a drawing. */
  position: absolute;
  left: calc(100% - 0.42rem);
  top: 0.92rem;
  /* width:max-content is load-bearing. Absolutely positioned with a `left`
     near 100%, the shrink-to-fit width collapses to the few px left in the
     containing block, and the global `svg { max-width: 100% }` then clamps the
     signature to that — it rendered as a 9px dash. */
  width: max-content;
  display: inline-flex;
  align-items: flex-end;
}
.logo-sig {
  /* Sized in rem, not em: em here resolves against the link's 16px, not the
     wordmark's 1.62rem, so the signature did not scale when the lockup grew. */
  width: 4.4rem;
  height: 1.78rem;
  max-width: none;
  overflow: visible;
  fill: none;
  stroke: var(--accent);
  stroke-width: 3.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.primary-nav { display: flex; align-items: center; gap: 22px; margin-left: auto; }
@media (max-width: 1320px) { .primary-nav { gap: 18px; } }
.primary-nav a:not(.btn) { white-space: nowrap; font-size: 0.88rem; color: var(--nav-link); transition: color 0.2s var(--ease); }
.primary-nav a:not(.btn):hover, .primary-nav a.is-active { color: var(--nav-link-hover); }

.header-actions { display: flex; align-items: center; gap: 8px; }
/* Language switch, after dvfconsulting.cl's: two chips in one frame, the
   current language in the accent. The chip stays small; an overlay pads the
   tap target to 44px without widening the bar. */
.lang-switch { display: flex; border: 1px solid var(--border-strong); overflow: hidden; }
.lang-switch a { position: relative; padding: 8px 10px; font-family: var(--mono); font-size: 0.72rem; font-weight: 700; letter-spacing: 0.06em; line-height: 1; color: var(--muted); text-decoration: none; transition: color 0.16s var(--ease), background-color 0.16s var(--ease); }
.lang-switch a + a { border-left: 1px solid var(--border-strong); }
.lang-switch a::after { content: ""; position: absolute; left: 0; right: 0; top: 50%; height: 44px; transform: translateY(-50%); }
.lang-switch a:hover { color: var(--ink); background: color-mix(in srgb, var(--accent) 8%, transparent); }
.lang-switch a.is-current { color: var(--accent-ink); background: var(--accent); }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-size: 0.9rem; font-weight: 600; padding: 12px 20px; min-height: 44px;
  border-radius: var(--radius-sm); border: 1px solid transparent; cursor: pointer;
  transition: background 0.2s var(--ease), border-color 0.2s var(--ease), color 0.2s var(--ease);
}
.btn svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; }
.btn-primary { background: var(--accent); color: var(--accent-ink); }
.btn-primary:hover { background: color-mix(in srgb, var(--accent) 84%, #ffffff); }
/* The consulting way in, beside the build button in the header: outlined in
   DVF's sky blue, so it reads as the other practice rather than a second
   version of our own call to action. */
.nav-consult { border-color: var(--consult); color: var(--consult); background: transparent; }
.nav-consult:hover { background: color-mix(in srgb, var(--consult) 14%, transparent); }
/* The hero's two calls to action: build, or see what has been built. Equal
   width so neither reads as the afterthought; the colour says which leads. */
.btn-wide { min-width: 240px; padding-inline: 32px; font-size: 0.95rem; }
.btn-ghost { border-color: var(--border-strong); color: var(--ink); background: transparent; }
.btn-ghost:hover { border-color: var(--accent); color: var(--accent); }
.nav-cta, .nav-consult { padding: 9px 16px; min-height: 38px; white-space: nowrap; }

.icon-btn {
  width: 38px; height: 38px; display: grid; place-items: center;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: transparent; color: var(--muted); cursor: pointer;
}
.icon-btn:hover { color: var(--accent); border-color: var(--accent); }
.icon-btn svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
.theme-toggle .icon-moon { display: none; }
:root[data-theme="light"] .theme-toggle .icon-sun,
:root:not([data-theme="dark"]) .theme-toggle .icon-sun { display: none; }
:root[data-theme="light"] .theme-toggle .icon-moon,
:root:not([data-theme="dark"]) .theme-toggle .icon-moon { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle .icon-sun { display: block; }
  :root:not([data-theme="light"]) .theme-toggle .icon-moon { display: none; }
}
:root[data-theme="dark"] .theme-toggle .icon-sun { display: block; }
:root[data-theme="dark"] .theme-toggle .icon-moon { display: none; }

.nav-toggle { display: none; }
.burger { position: relative; width: 16px; height: 11px; display: block; }
.burger span { position: absolute; left: 0; width: 100%; height: 2px; background: currentColor; border-radius: 0; transition: transform 0.25s var(--ease), opacity 0.2s var(--ease), top 0.25s var(--ease); }
.burger span:nth-child(1) { top: 0; }
.burger span:nth-child(2) { top: 4.5px; }
.burger span:nth-child(3) { top: 9px; }
.nav-toggle[aria-expanded="true"] .burger span:nth-child(1) { top: 4.5px; transform: rotate(45deg); }
.nav-toggle[aria-expanded="true"] .burger span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] .burger span:nth-child(3) { top: 4.5px; transform: rotate(-45deg); }

/* ---------- Sections ---------- */

.section { padding: clamp(56px, 8vw, 96px) 0; }
.section-alt { background: var(--bg-soft); border-block: 1px solid var(--border); }
.section-head { max-width: 62ch; margin-bottom: clamp(28px, 4vw, 44px); }
.section-head h2 { margin: 10px 0 12px; }
.section-head p { color: var(--muted); }

/* ---------- Hero ---------- */

/* The video is the background, so the hero owns a stacking context and the
   copy sits above it. isolation:isolate keeps the negative z-indexes below the
   text but still inside the section, rather than punching behind the page. */
.hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding: clamp(72px, 10vw, 132px) 0 clamp(56px, 8vw, 92px);
  border-bottom: 1px solid var(--rule);
}
.hero-video {
  position: absolute; inset: 0; z-index: -2;
  width: 100%; height: 100%; object-fit: cover;
  pointer-events: none;
}

/* Load-bearing, not decoration. The animation's brightest pixels are the gold
   lockup, and white copy over raw gold does not clear AA. This scrim is what
   buys the contrast back: a flat base that guarantees the floor, plus a
   left-weighted gradient so the side the text actually sits on is darkest.
   Measured against the animation's brightest frame — see the README. */
.hero-scrim {
  position: absolute; inset: 0; z-index: -1; pointer-events: none;
  /* Measured, not chosen. Composited against the animation's brightest frame,
     these give ink 10.69:1 and muted 5.54:1 — and muted (.lead, .hero-meta) is
     the binding constraint, not ink. Lightening the flat layer to 0.45 drops
     muted to 3.58:1 and fails, so there is roughly one step of headroom here:
     if you lighten it, re-measure. */
  /* Re-measured after the field was brightened. A flat scrim strong enough for
     the copy also hid the animation, which was the complaint — so the cover is
     placed where the text is instead of spread evenly: heavy across the copy
     column, then a sharp falloff so the field reads at full strength in the gap
     and behind the lockup. Copy zone lands at ink 9.06:1 and muted 4.70:1;
     muted (.lead, .hero-meta) is the binding constraint, as always. */
  background:
    linear-gradient(90deg, color-mix(in srgb, var(--bg) 88%, transparent) 0%,
                    color-mix(in srgb, var(--bg) 84%, transparent) 50%,
                    color-mix(in srgb, var(--bg) 18%, transparent) 66%,
                    color-mix(in srgb, var(--bg) 4%, transparent) 100%),
    color-mix(in srgb, var(--bg) 28%, transparent);
}
@media (max-width: 860px) {
  /* Copy spans the full width here, so the gradient cannot be directional and
     the flat layer has to carry it alone: 0.82 gives muted 5.37:1. */
  /* Single column here, so the gradient cannot be directional: 0.78 flat gives
     muted 5.20:1, where 0.66 gave 3.53:1 and failed. */
  .hero-scrim { background: color-mix(in srgb, var(--bg) 78%, transparent); }
}

/* Copy left, brand lockup right — the arrangement David asked for. Below
   980px the lockup drops under the copy rather than shrinking into it. */
/* Two columns, two rows, and the eyebrow lives OUTSIDE this grid. The logo
   shares row 1 with the h1 and the lead, which is what lets its height be
   driven by that block: top on the h1's cap line, bottom on the lead's last
   line. Sizing it by width could only ever fix one end. The CTAs and the meta
   list sit in row 2, under the copy only. */
.hero-inner {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
  gap: 28px clamp(24px, 4vw, 56px);
  align-items: start;
}
.hero-head { grid-column: 1; grid-row: 1; }
.hero-rest { grid-column: 1; grid-row: 2; }
/* position:relative with an absolutely positioned image. That is the part that
   matters: an in-flow image contributes its own natural height to the row, so
   the row grows to fit the logo and pushes the CTAs down. Out of flow, the row
   is sized by the h1 and lead alone, and the logo is sized BY it. */
.hero-logo { grid-column: 2; grid-row: 1; align-self: stretch; position: relative; min-height: 0; }


/* The hero logo is the artwork David supplied, used as-is rather than redrawn.
   It is a logotype, so WCAG's contrast rules do not apply to the text inside
   it — but it still carries a real alt, since it is the only place the full
   brand line appears. width/height are set so the box is reserved before the
   image lands and the hero does not shift. */
.hero-logo { display: flex; justify-content: center; align-items: flex-start; }
.hero-logo img {
  /* The offset is the h1's own top margin plus its half-leading — the gap
     between the row's top and the cap line — written against the h1's clamp so
     it tracks the headline as it scales. A percentage margin cannot do this:
     percentages resolve against the container's width, not the image's. */
  --cap-offset: calc(16px + 0.15 * clamp(2.1rem, 5vw, 3.4rem));
  position: absolute;
  top: var(--cap-offset);
  left: 50%;
  transform: translateX(-50%);
  /* 0.8: the logo sits at 80% of the height it would take to meet the lead's
     last line, still anchored at the cap line. */
  height: calc(0.8 * (100% - var(--cap-offset)));
  width: auto;
  max-width: 80%;
  object-fit: contain;
  object-position: center top;
}
/* Narrow: single column, logo above the copy, width-driven again — there is no
   block beside it to take a height from and no cap line to meet. This block
   must stay AFTER the rule above: same specificity, so source order decides,
   and placed before it the desktop rule won at every width and the logo
   disappeared on phones. */
@media (max-width: 980px) {
  .hero-inner { grid-template-columns: 1fr; }
  .hero-logo { grid-column: 1; grid-row: 1; align-self: center; position: static; margin: 0 0 14px; }
  .hero-head { grid-column: 1; grid-row: 2; }
  .hero-rest { grid-column: 1; grid-row: 3; }
  .hero-logo img {
    position: static;
    transform: none;
    height: auto;
    width: 80%;
    max-width: calc(0.8 * clamp(220px, 46vw, 340px));
  }
}


.hero h1 { margin: 16px 0 18px; max-width: 17ch; }
/* Zero inside the row — the space before the CTAs is the grid's row-gap now.
   As a margin it sat inside the row the logo is measured against, so the logo
   overshot the lead's last line by exactly that 28px. */
.hero .lead { margin-bottom: 0; }
.hero-actions { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }

.hero-meta { display: flex; flex-wrap: wrap; gap: 8px 22px; margin-top: 30px; font-family: var(--mono); font-size: 0.78rem; color: var(--muted); }
.hero-meta span { display: inline-flex; align-items: center; gap: 7px; }
.hero-meta svg { width: 14px; height: 14px; fill: none; stroke: var(--accent); stroke-width: 2; }

/* ---------- Products ---------- */

/* A manifest, not a set of cards. Three products of equal weight laid out as
   varied bento tiles would be arbitrary — the varied spans would encode a
   difference in importance that does not exist. Indexed ruled rows say the
   same thing the code blocks do: this is a parts list, and the parts are real. */
.stack-list { display: grid; gap: 1px; background: var(--border); border: 1px solid var(--border); }
/* The last track is a fixed width, not `auto`: each row is its own grid, so an
   `auto` track sizes to that row's own content and the column rules land at a
   different x on every row — which reads as a wobble, not a manifest. */
.stack-row { background: var(--bg); display: grid; grid-template-columns: 54px minmax(150px, 190px) 1fr 176px; align-items: stretch; }
.stack-row > * { display: flex; align-items: center; padding: 15px 18px; }
/* No vertical rules between cells: the columns are held by alignment alone. */

.stack-ix { font-family: var(--mono); font-size: 0.72rem; color: var(--accent); justify-content: center; }
.stack-name { font-family: var(--mono); font-weight: 600; color: var(--ink); font-size: 0.92rem; }
.stack-desc { color: var(--muted); font-size: 0.9rem; line-height: 1.55; }
/* The classification is a column of the manifest, not a pill floating in one.
   A chip here would be the only rounded, filled object left on the page. */
.stack-tag {
  font-family: var(--mono); font-size: 0.68rem; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--accent); white-space: nowrap;
}

@media (max-width: 760px) {
  .stack-row { grid-template-columns: 54px 1fr; }
  .stack-ix { grid-row: span 3; align-items: flex-start; padding-top: 17px; }
  .stack-row > .stack-desc, .stack-row > .stack-tag { padding-top: 0; }
  .stack-name { padding-bottom: 6px; }
}


/* ---------- Process ---------- */

/* The connector is drawn on top of an ordered list rather than being an SVG
   diagram: the steps stay real text, announce as a list of five, and survive
   with styling off. The rule sits behind the nodes and stops short of both
   ends so it never runs into the page's own ruled edges. */
.process {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 0;
  position: relative;
  counter-reset: step;
}
.process::before {
  content: "";
  position: absolute;
  /* Node centres, not column edges: the first node sits at the container edge
     (centre 7px), the last is inset by its column's 18px padding, so the rule
     has to stop 26px short of the final column's start. */
  left: 7px; right: calc(20% - 26px); top: 7px;
  height: 1px;
  background: var(--border);
}
.process-step { position: relative; padding: 30px 18px 0 0; counter-increment: step; }
.process-step + .process-step { padding-left: 18px; }

/* The node. Sits on the connector, and carries the step number as generated
   content — decorative, because the list already conveys the order. */
.process-step::before {
  content: "";
  position: absolute; top: 0; left: 0;
  width: 15px; height: 15px;
  background: var(--bg);
  border: 2px solid var(--accent);
  border-radius: 50%;
}
.process-step + .process-step::before { left: 18px; }

.process-step h3 {
  font-family: var(--mono);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 9px;
}
.process-step p { color: var(--muted); font-size: 0.9rem; line-height: 1.6; }

@media (max-width: 980px) {
  .process { grid-template-columns: 1fr; }
  .process::before { left: 7px; right: auto; top: 10px; bottom: 10px; width: 1px; height: auto; }
  .process-step { padding: 0 0 26px 30px; }
  .process-step + .process-step { padding-left: 30px; }
  .process-step::before, .process-step + .process-step::before { left: 0; top: 3px; }
}

/* Staggered, like every other group on the page. */
.js-reveal .process-step.reveal.is-visible:nth-child(2) { transition-delay: 60ms; }
.js-reveal .process-step.reveal.is-visible:nth-child(3) { transition-delay: 120ms; }
.js-reveal .process-step.reveal.is-visible:nth-child(4) { transition-delay: 180ms; }
.js-reveal .process-step.reveal.is-visible:nth-child(5) { transition-delay: 240ms; }

/* Four secondary greys in the mocks were lifted to clear AA. They are
   depictions of real product UIs, but they are still real text rendered on a
   real page, and WCAG does not care that the source screen was worse:
   #64748b -> #728299 and #475569 -> #748296 on Waze Cargo (measured against
   its lightest surface, the row fill #0f1628, not the app background), and
   #8a8578 -> #746f62, #a09a8c -> #756f61 on Alma Secret's cream card. Each now
   sits at 4.6:1 or better. */
/* ---------- Product cards ---------- */

/* The manifest became cards so each product could carry its figure. The rows
   were denser and read better as a parts list, but a parts list cannot show
   anything — and what these three products look like is the argument. */
.product-cards { display: grid; gap: 1px; background: var(--border); border: 1px solid var(--border); }
.product-card { background: var(--bg); padding: clamp(20px, 3vw, 30px); }

.product-head { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; }
.product-ix { font-family: var(--mono); font-size: 0.72rem; color: var(--accent); }
.product-name { font-family: var(--mono); font-size: 1.02rem; font-weight: 600; color: var(--ink); }
.product-tag {
  font-family: var(--mono); font-size: 0.66rem; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--accent); margin-left: auto;
}
.product-desc { color: var(--muted); font-size: 0.93rem; max-width: 68ch; margin-top: 10px; }

/* Four key figures, then the stack. The stack reuses the home page's
   Technology glyphs, one per tool, named on hover and on keyboard focus;
   the name is also in the markup as text for screen readers. */
.product-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1px; margin-top: 20px; background: var(--border); border: 1px solid var(--border); }
.product-stats div { background: var(--bg-soft); padding: 16px 18px; display: flex; flex-direction: column-reverse; gap: 6px; }
.product-stats dd { margin: 0; font-size: 1.5rem; font-weight: 700; letter-spacing: -0.02em; color: var(--ink-strong); }
/* Sentence-case labels: they carry a full thought, and a line of tracked
   capitals is hard to read past a few words. */
.product-stats dt { font-size: 0.82rem; line-height: 1.45; color: var(--muted); }
.product-stats div { justify-content: flex-end; }
.product-stats.is-three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 760px) { .product-stats, .product-stats.is-three { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 480px) { .product-stats, .product-stats.is-three { grid-template-columns: 1fr; } }

.product-stack { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px; margin-top: 18px; }
.product-stack-label { font-family: var(--mono); font-size: 0.66rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.product-stack ul { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.tech { position: relative; display: grid; place-items: center; width: 38px; height: 38px; border: 1px solid var(--border); color: var(--accent); cursor: default; transition: border-color 0.2s var(--ease), background 0.2s var(--ease); }
.tech svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.tech:hover, .tech:focus-visible { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); outline: none; }
.tech-name { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
.tech::after {
  content: attr(data-tip); position: absolute; bottom: calc(100% + 8px); left: 50%;
  transform: translate(-50%, 4px); white-space: nowrap; pointer-events: none;
  font-family: var(--mono); font-size: 0.7rem; letter-spacing: 0.04em;
  background: var(--ink); color: var(--bg); padding: 5px 8px;
  opacity: 0; transition: opacity 0.15s var(--ease), transform 0.15s var(--ease); z-index: 2;
}
.tech:hover::after, .tech:focus-visible::after { opacity: 1; transform: translate(-50%, 0); }
@media (prefers-reduced-motion: reduce) { .tech::after { transition: none; } }

.product-figs { margin-top: 22px; display: grid; gap: 18px; }
/* A pair: the first screen earns the width, the second does not. */
.product-figs.is-pair { grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr); align-items: start; }
@media (max-width: 860px) { .product-figs.is-pair { grid-template-columns: 1fr; } }
/* Waze Cargo carries four screens: two by two, one column on phones. */
.product-figs.is-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; }
@media (max-width: 860px) { .product-figs.is-grid { grid-template-columns: 1fr; } }

/* The mocks are product UIs in their own palettes, so they are framed as
   screens rather than bled into the page — otherwise the page looks like it
   has four accent colours instead of one. */
.project-figure {
  margin: 0; padding: 14px; border: 1px solid var(--rule); background: var(--bg-soft);
}

.project-figure { margin: 0; }
.project-figures { display: grid; gap: 34px; }
.chart-title {
  font-weight: 700;
  color: var(--ink-strong);
  font-size: 0.98rem;
}
.chart-sub { font-size: 0.82rem; color: var(--muted); margin-bottom: 14px; }
.chart { width: 100%; height: auto; display: block; overflow: visible; }

/* Real screens, not drawings. The mocks these replaced were hand-built because
   there was nothing to show; there is now, and a photograph of the delivered
   thing argues better than a careful reconstruction of it.

   width/height are on the element so the box is reserved before the file
   arrives — every one of these is lazy and below the fold, and without the
   intrinsic ratio the card jumps when they land. */
.project-figure .shot { display: block; width: 100%; height: auto; }
/* Screenshots open full screen (main.js). The dialog is always dark, like
   the screens it shows, whatever the page theme. */
.shot.is-zoomable { cursor: zoom-in; transition: opacity 0.2s var(--ease); }
.shot.is-zoomable:hover { opacity: 0.88; }
.shot.is-zoomable:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.lightbox {
  width: 100vw; height: 100vh; max-width: none; max-height: none; margin: 0; padding: 56px 72px 28px;
  border: 0; background: rgba(4, 4, 6, 0.96); color: #e8e6e1;
}
.lightbox[open] { display: grid; place-items: center; }
.lightbox::backdrop { background: rgba(0, 0, 0, 0.6); }
.lightbox-figure { margin: 0; display: grid; justify-items: center; gap: 16px; max-width: 100%; }
.lightbox-img { display: block; max-width: 100%; max-height: calc(100vh - 230px); width: auto; height: auto; box-shadow: 0 30px 80px rgba(0, 0, 0, 0.6); }
.lightbox-caption { display: grid; gap: 4px; max-width: 78ch; text-align: center; }
.lightbox-title { font-size: 1.05rem; color: #fff; }
.lightbox-sub { font-size: 0.82rem; color: #a8a49b; }
.lightbox-note { font-size: 0.9rem; line-height: 1.55; color: #cfccc5; margin-top: 4px; }
.lightbox-btn {
  position: fixed; display: grid; place-items: center; width: 44px; height: 44px;
  border: 1px solid rgba(251, 212, 101, 0.4); background: rgba(0, 0, 0, 0.5); color: #fbd465; cursor: pointer;
}
.lightbox-btn:hover, .lightbox-btn:focus-visible { border-color: #fbd465; outline: none; }
.lightbox-btn svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.lightbox-close { top: 14px; right: 14px; }
.lightbox-prev { left: 14px; top: 50%; transform: translateY(-50%); }
.lightbox-next { right: 14px; top: 50%; transform: translateY(-50%); }
.lightbox.is-single .lightbox-prev, .lightbox.is-single .lightbox-next { display: none; }
@media (max-width: 700px) {
  .lightbox { padding: 60px 12px 20px; }
  .lightbox-prev, .lightbox-next { top: auto; bottom: 14px; transform: none; }
  .lightbox-img { max-height: calc(100vh - 300px); }
}

/* The walkthrough video: landscape on wide screens, the vertical cut on
   phones, where it is sized like the phone it was made for. */
.demo-video { display: block; width: 100%; height: auto; background: #000; }
.demo-video.is-mobile { display: none; }
@media (max-width: 700px) {
  .demo-video.is-desktop { display: none; }
  .demo-video.is-mobile { display: block; max-width: 360px; margin-inline: auto; }
}
.chart-baseline { stroke: var(--border); stroke-width: 1; }
.chart-bar-emphasis { fill: var(--chart-emphasis); }
.chart-bar-context { fill: var(--chart-context); }
.chart-cat {
  font-family: var(--font);
  font-size: 13px;
  font-weight: 600;
  fill: var(--ink);
}
.chart-value {
  font-family: var(--font);
  font-size: 13px;
  font-weight: 700;
  fill: var(--muted);
}
.chart-value-emphasis { fill: var(--ink-strong); }
.chart-note {
  font-size: 0.78rem;
  color: var(--muted);
  margin-top: 14px;
  line-height: 1.5;
}
.as-mock text { font-family: var(--font); }
.as-card { fill: #faf6f0; stroke: #e3ddd2; stroke-width: 1; }
.as-eyebrow {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.12em;
  fill: #2f4a3a;
}
.as-label { font-size: 12.5px; font-weight: 600; fill: #3a3a34; }
.as-track { fill: #e8e0d3; }
.as-bar { fill: #2f4a3a; }
.as-score { font-size: 12.5px; font-weight: 700; fill: #2f4a3a; }
.as-score-max { font-size: 10px; font-weight: 600; fill: #756f61; }
.as-rule { fill: #e3ddd2; }
.as-rule-accent { fill: #b69968; }
.as-section {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.12em;
  fill: #746f62;
}
.as-dot { fill: #b69968; }
.as-product { font-size: 12.5px; font-weight: 600; fill: #2a2a26; }
.as-reason { font-size: 11px; fill: #746f62; }
.wc-mock text { font-family: var(--font); }
.wc-app { fill: #06090f; stroke: #172033; stroke-width: 1; }
.wc-rule { stroke: #172033; stroke-width: 1; }
.wc-card { fill: #0c1220; stroke: #172033; stroke-width: 1; }
.wc-row { fill: #0f1628; }
.wc-logo { fill: #0891b2; }
.wc-logo-wave { fill: none; stroke: #e0f7fb; stroke-width: 1.3; stroke-linecap: round; }
.wc-brand { font-size: 11.5px; font-weight: 800; fill: #e2e8f0; letter-spacing: -0.02em; }
.wc-brand-accent { fill: #06b6d4; }
.wc-sub { font-size: 7.5px; fill: #728299; }
.wc-key { font-size: 7.5px; fill: #728299; }
.wc-key-fill { fill: #06b6d4; }
.wc-key-ring { fill: none; stroke: #38bdf8; stroke-width: 1.4; }
.wc-pill { fill: none; stroke: #172033; stroke-width: 1; }
.wc-pill-on { fill: rgba(6, 182, 212, 0.12); stroke: #06b6d4; }
.wc-pill-label { font-size: 7.5px; font-weight: 600; fill: #728299; letter-spacing: 0.06em; }
.wc-pill-label-on { fill: #06b6d4; font-weight: 700; }
.wc-field { font-size: 7.5px; font-weight: 600; fill: #728299; }
.wc-chip { font-size: 8px; font-weight: 700; fill: #e2e8f0; }
.wc-land { fill: #131d33; stroke: #2a3a58; stroke-width: 1; stroke-linejoin: round; }
.wc-port { transition: r 0.14s var(--ease), fill-opacity 0.14s var(--ease); }
.wc-port:hover { fill-opacity: 1; }
.wc-callout { fill: #1e293b; stroke: #334155; stroke-width: 1; }
.wc-callout-tie { stroke: #334155; stroke-width: 1; }
.wc-callout-port { font-size: 7.5px; font-weight: 700; fill: #e2e8f0; }
.wc-callout-val { font-size: 7px; font-weight: 600; fill: #ef4444; }
.wc-legend { fill: rgba(12, 18, 32, 0.92); stroke: #172033; stroke-width: 1; }
.wc-legend-label { font-size: 6.8px; fill: #728299; }
.wc-legend-map { font-size: 7px; }
.wc-port-name { font-size: 13px; font-weight: 800; fill: #e2e8f0; letter-spacing: -0.02em; }
.wc-port-sub { font-size: 8px; fill: #728299; }
.wc-risk-label { font-size: 6.8px; font-weight: 700; fill: #728299; letter-spacing: 0.14em; }
.wc-risk { font-size: 14px; font-weight: 800; fill: #ef4444; letter-spacing: -0.02em; }
.wc-card-title { font-size: 8.5px; font-weight: 700; fill: #e2e8f0; }
.wc-card-sub { font-size: 7.5px; fill: #728299; }
.wc-grid { stroke: #172033; stroke-width: 1; stroke-dasharray: 2 3; }
.wc-axis { font-size: 7px; fill: #748296; }
.wc-line-raw {
  fill: none;
  stroke: #38bdf8;
  stroke-width: 1.3;
  stroke-dasharray: 3 2.5;
  opacity: 0.75;
}
.wc-line-adj { fill: none; stroke: #06b6d4; stroke-width: 1.8; stroke-linejoin: round; }
.wc-pt { fill: #06b6d4; }
.wc-gap { fill: #06b6d4; opacity: 0.12; }
.wc-row-label { font-size: 8px; fill: #cbd5e1; }
.wc-row-value { font-size: 8px; font-weight: 700; fill: #06b6d4; }
.wc-alert { fill: rgba(245, 158, 11, 0.08); stroke: rgba(245, 158, 11, 0.45); stroke-width: 1; }
.wc-alert-text { font-size: 8px; fill: #cbd5e1; }
.wc-alert-lead { font-weight: 700; fill: #f59e0b; }
.wc-foot { font-size: 7.5px; fill: #748296; }
.lcd {
  width: 100%;
  height: auto;
  display: block;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
}

/* ---------- Comparison table ---------- */

/* Wide tables overflow on phones, so the scroll lives on a wrapper rather
   than the page body. */
.table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: var(--radius); }

/* Denser than a marketing table and ruled in both directions: 36px rows read
   as an instrument panel, 56px rows read as a pricing page. */
.compare { width: 100%; border-collapse: collapse; min-width: 620px; font-size: 0.88rem; }
.compare th, .compare td { padding: 10px 16px; text-align: left; border-bottom: 1px solid var(--rule); vertical-align: top; }
.compare thead th {
  background: var(--card); font-family: var(--mono);
  font-size: 0.68rem; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--muted); font-weight: 600; padding-block: 12px;
  border-bottom: 1px solid var(--border);
}
.compare tbody th { font-weight: 500; color: var(--ink); }
.compare tbody tr:last-child th, .compare tbody tr:last-child td { border-bottom: 0; }

/* The studio's own column is tinted, but every cell also states its answer in
   words — colour alone would carry the comparison for nobody using it. */
.compare .col-us { background: var(--accent-soft); color: var(--ink); }
.compare thead .col-us { color: var(--accent); }

.mark { display: inline-flex; align-items: center; gap: 7px; }
.mark svg { width: 15px; height: 15px; flex: none; fill: none; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.mark-yes svg { stroke: var(--ok); }
.mark-no svg { stroke: var(--no); }

/* ---------- Integrations ---------- */

/* The rules are drawn by the group headings, not by a 1px row-gap over a
   tinted container as they were when this was twelve tiles in two full rows.
   With the list grouped, most groups do not divide evenly by the column count,
   and an empty cell is not a gap — the container's own background shows
   through it, so the tinted-background trick painted a solid block of --rule
   wherever a group's last row ran short. */
.integrations { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 0; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); }
@media (max-width: 900px) { .integrations { grid-template-columns: repeat(5, minmax(0, 1fr)); } }
@media (max-width: 560px) { .integrations { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

/* Tighter cells and a smaller glyph: this is a compatibility matrix, and a
   matrix is read by scanning, not by admiring each tile. */
.integration { background: var(--bg); display: grid; place-items: center; gap: 8px; padding: 16px 8px; text-align: center; transition: background 0.2s var(--ease); }
.integration:hover { background: var(--bg-soft); }
.integration svg { width: 22px; height: 22px; fill: none; stroke: var(--muted); stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; transition: stroke 0.2s var(--ease); }
.integration:hover svg { stroke: var(--accent); }
.integration span { font-family: var(--mono); font-size: 0.68rem; letter-spacing: 0.04em; color: var(--muted); }

/* Forty-seven entries do not read as a list, they read as wallpaper. Scanning
   an unsorted block that size for "do they do Kafka" is slower than reading a
   paragraph, so the grid is sectioned and each heading spans the full row. */
.integration-group {
  grid-column: 1 / -1;
  border-top: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: 0.64rem;
  font-weight: 500;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--accent);
  padding: 15px 14px 3px;
}
.integration-group:first-child { border-top: 0; }

/* ---------- Portfolio callout ---------- */

/* The frame the docs callout used to have, with three project screens fanned
   beside the copy. The screens share one box (a fixed 16:9, cover-cropped
   from the top) so screenshots of different heights stack as equal cards. */
.work-cta {
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: clamp(26px, 4vw, 44px);
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 420px); gap: clamp(28px, 4vw, 56px); align-items: center;
  background: var(--card);
  overflow: hidden;
}
.work-copy h2 { margin: 10px 0 12px; text-wrap: balance; }
.work-copy p { color: var(--muted); max-width: 54ch; margin-bottom: 22px; }
.work-stack { position: relative; display: block; aspect-ratio: 16 / 11; }
.work-stack img {
  position: absolute; width: 74%; height: auto; aspect-ratio: 16 / 9;
  object-fit: cover; object-position: top;
  border: 1px solid var(--border-strong);
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.45);
  transition: transform 0.35s var(--ease);
}
.work-stack img:nth-child(1) { left: 0;   top: 0;   transform: rotate(-5deg); }
.work-stack img:nth-child(2) { left: 13%; top: 16%; transform: rotate(1.5deg); }
.work-stack img:nth-child(3) { left: 26%; top: 34%; transform: rotate(5deg); }
.work-cta:hover .work-stack img:nth-child(1) { transform: rotate(-8deg) translate(-10px, -6px); }
.work-cta:hover .work-stack img:nth-child(3) { transform: rotate(8deg) translate(10px, 6px); }
@media (prefers-reduced-motion: reduce) { .work-stack img { transition: none; } }
@media (max-width: 860px) {
  .work-cta { grid-template-columns: 1fr; }
  .work-stack { width: min(100%, 420px); justify-self: center; }
}

/* ---------- DVF Consulting hand-off ---------- */

/* The card is DVF's own brand in both themes, not ours: its navy, its sky
   blue, its promise line and its hero loop, with the inverse (light) logo.
   The loop's chart sits in its right half, so the video is anchored right
   and a scrim from the left keeps the copy readable over it. */
.advice { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 480px); gap: clamp(28px, 5vw, 64px); align-items: center; }
.advice-copy h2 { margin: 10px 0 12px; text-wrap: balance; }
.advice-copy p { color: var(--muted); max-width: 56ch; margin-bottom: 22px; }
.advice-card {
  position: relative; display: block; overflow: hidden; isolation: isolate;
  background: #020617; border: 1px solid #1e293b; border-top: 3px solid #0369a1;
  color: #94a3b8; text-decoration: none;
  transition: border-color 0.25s var(--ease), transform 0.25s var(--ease), box-shadow 0.25s var(--ease);
}
.advice-card:hover { border-color: #0369a1; transform: translateY(-3px); box-shadow: 0 22px 50px rgba(3, 105, 161, 0.18); }
.advice-video {
  position: absolute; inset: 0; z-index: -2; width: 100%; height: 100%;
  object-fit: cover; object-position: 78% center; opacity: 0.9;
}
.advice-card::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(100deg, rgba(2, 6, 23, 0.94) 0%, rgba(2, 6, 23, 0.78) 45%, rgba(2, 6, 23, 0.2) 100%);
}
.advice-body { display: grid; justify-items: start; gap: 12px; padding: clamp(20px, 2.8vw, 28px); }
/* Logo and address share the top row, which is most of the height saved. */
.advice-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; width: 100%; margin-bottom: 4px; }
/* The logo was set at 300px and read as stretched beside the promise; at
   210 it is a signature, and the promise is the headline. */
.advice-logo { width: 180px; max-width: 60%; height: auto; }
.advice-promise { display: grid; font-size: clamp(1.4rem, 2.4vw, 1.75rem); font-weight: 700; letter-spacing: -0.02em; line-height: 1.08; color: #f8fafc; }
.advice-promise-b { color: #38bdf8; text-shadow: 0 0 30px rgba(56, 189, 248, 0.3); }
.advice-sub { max-width: 34ch; font-size: 0.92rem; line-height: 1.5; color: #cbd5e1; }
.advice-steps { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 2px; font-family: var(--mono); font-size: 0.66rem; letter-spacing: 0.12em; text-transform: uppercase; }
.advice-steps span { border: 1px solid #1e3a5f; background: rgba(2, 6, 23, 0.6); padding: 4px 9px; color: #94a3b8; }
/* The arrows between the steps: it is a sequence, not three tags. */
.advice-steps i { width: 14px; height: 1px; background: #38bdf8; position: relative; }
.advice-steps i::after { content: ""; position: absolute; right: 0; top: -3px; border: 3.5px solid transparent; border-left: 5px solid #38bdf8; border-right: 0; }
.advice-url { display: inline-flex; align-items: center; gap: 6px; color: #f8c877; font-weight: 600; font-size: 0.9rem; white-space: nowrap; }
.advice-url svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; transition: transform 0.25s var(--ease); }
.advice-card:hover .advice-url svg { transform: translateX(4px); }
@media (prefers-reduced-motion: reduce) { .advice-card, .advice-url svg { transition: none; } }
@media (max-width: 860px) { .advice { grid-template-columns: 1fr; } }
@media (max-width: 420px) { .advice-logo { width: 148px; } }

/* ---------- Privacy page and cookie notice ---------- */

.legal { max-width: 68ch; }
.legal h2 { font-size: 1.15rem; margin: 30px 0 10px; }
.legal h2:first-child { margin-top: 0; }
.legal p, .legal li { color: var(--muted); line-height: 1.7; }
.legal ul { margin: 10px 0 10px 1.1em; }
.legal strong { color: var(--ink); font-family: var(--mono); font-weight: 600; }
.legal-date { margin-top: 30px; font-size: 0.85rem; }

.footer-legal { margin-left: 14px; font: inherit; color: var(--muted); text-decoration: underline; text-underline-offset: 3px; }
.footer-legal:hover { color: var(--accent); }
.link-btn { background: none; border: 0; padding: 0; cursor: pointer; }

/* Built by consent.js only when analytics is configured. Accept and Reject
   are the same size, side by side: refusing is as easy as accepting. */
.consent {
  position: fixed; left: 16px; right: 16px; bottom: 16px; z-index: 60;
  max-width: 560px; margin-left: auto;
  display: grid; gap: 14px; padding: 18px 20px;
  background: var(--card); border: 1px solid var(--border-strong);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45);
}
.consent[hidden] { display: none; }
.consent-text { font-size: 0.88rem; line-height: 1.6; color: var(--muted); }
.consent-text a { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }
.consent-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }

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

.contact-box { text-align: center; display: grid; justify-items: center; gap: 16px; }
.contact-box p { color: var(--muted); max-width: 52ch; }
.contact-actions { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; }
.btn-whatsapp svg { width: 18px; height: 18px; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
/* Email opens a choice: Gmail in a new tab, or mailto: for whatever mail app
   the device has. We can't tell which one a visitor uses, so we ask. */
.email-menu { position: relative; }
.email-menu > summary { list-style: none; }
.email-menu > summary::-webkit-details-marker { display: none; }
.email-menu-list {
  position: absolute; top: calc(100% + 6px); left: 50%; transform: translateX(-50%); z-index: 20;
  min-width: 100%; display: grid; background: var(--card); border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); overflow: hidden; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
}
.email-menu-list a { padding: 12px 18px; min-height: 44px; display: flex; align-items: center; justify-content: center; white-space: nowrap; font-size: 0.9rem; color: var(--ink); text-decoration: none; }
.email-menu-list a + a { border-top: 1px solid var(--border); }
.email-menu-list a:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); }

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

.site-footer { border-top: 1px solid var(--border); padding: 34px 0 28px; }
.footer-top { display: flex; flex-wrap: wrap; gap: 22px; justify-content: space-between; align-items: center; }
.footer-nav { display: flex; flex-wrap: wrap; gap: 20px; }
.footer-nav a { font-size: 0.84rem; color: var(--muted); }
.footer-nav a:hover { color: var(--accent); }
.footer-bottom { margin-top: 24px; padding-top: 18px; border-top: 1px solid var(--border); display: flex; flex-wrap: wrap; gap: 14px 24px; justify-content: space-between; align-items: center; }
/* Gold, the mark's and the button's: the icons are the brand's sign-off. */
.footer-social { display: flex; gap: 10px; }
.footer-social a, .footer-social span { display: grid; place-items: center; width: 38px; height: 38px; border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent); color: var(--accent); transition: background 0.2s var(--ease), border-color 0.2s var(--ease); }
/* Placeholders for accounts that do not exist yet: same gold, no link. */
.footer-social .is-pending { cursor: default; }
.footer-social a:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); }
.footer-social svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.footer-bottom p { font-size: 0.8rem; color: var(--muted); }

/* ---------- Reveal ---------- */

.js-reveal .reveal { opacity: 0; transform: translateY(10px); }
.js-reveal .reveal.is-visible { opacity: 1; transform: none; transition: opacity 0.4s var(--ease), transform 0.4s var(--ease); }

/* Stagger, declared after the base rule because the `transition` shorthand
   above resets transition-delay to 0 wherever it applies. Kept to 60-70ms a
   step: enough to read as sequence, not enough to feel like a queue. */
.js-reveal .stack-row.reveal.is-visible:nth-child(2) { transition-delay: 60ms; }
.js-reveal .stack-row.reveal.is-visible:nth-child(3) { transition-delay: 120ms; }

@media (prefers-reduced-motion: reduce) {
  .js-reveal .reveal { opacity: 1; transform: none; transition: none; }
  .js-reveal .reveal.is-visible { transition: none; transition-delay: 0s; }
}

/* ---------- Responsive ---------- */

/* The menu collapses to the burger where the row stops fitting, 1220px, in
   both languages: the Spanish menu uses short labels (Proceso, Nosotros,
   Empecemos) to fit the same width. main.js closes an open menu past
   the same width; if they disagree, the menu is stranded open. */
@media (max-width: 1219px) {
  .nav-toggle { display: grid; }
  .primary-nav {
    position: fixed; inset: var(--header-h) 0 auto 0;
    flex-direction: column; align-items: stretch; gap: 0;
    background: var(--bg); border-bottom: 1px solid var(--border);
    padding: 8px 20px 18px; display: none;
  }
  .primary-nav.is-open { display: flex; }
  .primary-nav a:not(.btn) { padding: 13px 0; border-bottom: 1px solid var(--border); }
  .nav-consult { margin-top: 12px; }
  .nav-cta { margin-top: 8px; }
  /* With the nav out of the row, nothing else pushes these to the right. */
  .header-actions { margin-left: auto; }
}
