/* Talari Technologies — corporate site.

   Design language: restrained, near-black, precise. Surfaces are separated by
   hairline borders and elevation rather than coloured glow; colour appears
   rarely and means something when it does. Type does most of the work.

   The palette has five colours but only two jobs. Electric Blue is the
   *material* — the washes, the lit edges, the chrome — and is saturated
   enough to also be a real button fill, carrying white at 4.67:1. Cyan is the
   *action*, filling precisely one button per screen at 10.47:1 on the ground.
   Deep Navy is structure and the ink on that cyan; the two accents, Purple
   and Blue, appear in the headline gradient and in running text.

   Because the blue works as a fill, the chrome can carry it directly and
   links get a lighter tint of it rather than defaulting to the action colour —
   cyan links would put a 10:1 shout on every inline reference.

   Contrast is not eyeballed. Every foreground/background pair below was
   measured against the computed background it actually renders on, and the
   ramp is checked against the *lightest* surface it ever lands on rather than
   the page ground, which is the easy case.

   100% self-contained: fonts ship with the page (~70 KB, variable latin
   subsets) and there are no external requests. The only script handles scroll
   reveals, the nav's scrolled state, the pointer spotlight and the FAQ
   collapse. That matters here — the audience is largely on Ethiopian mobile
   networks. */

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/inter-var.woff2") format("woff2");
}

/* The wordmark only — subset to the six letters of "Talari", which is why a
   third family costs 844 bytes rather than 18 KB.

   Michroma ships a single 400 weight and no variable axis, so it is declared
   at 400 and the wordmark asks for 400. Asking for 700 would make the browser
   synthesise a bold, and a faux bold on letterforms this wide smears the
   terminals and thickens the counters unevenly. */
@font-face {
  font-family: "Michroma";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/michroma-wordmark.woff2") format("woff2");
}

@font-face {
  font-family: "Instrument Sans";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("fonts/instrument-sans-var.woff2") format("woff2");
}

:root {
  /* Identity: Electric Blue #1769FF, with Cyan #11CFE0 second and Deep Navy
     #142A8A third, plus two accents — Purple #7A18FF and Blue #5C86FF. All
     five come from the supplied mark, which runs navy through blue to cyan
     around its ring and purple through blue up its stem.

     The roles are inherited from the palette this replaces, because the
     architecture was sound even though the hues were not. One colour is the
     chrome, one is the action, one reads as the brand in running text, and
     the deepest is structure rather than ink:

       --blue       chrome fills and the mark. 4.27:1 on the ground, which is
                    fine for an object with a lit edge and a shadow, and white
                    sits on it at 4.67:1.
       --cyan       the action colour, in the role Lemon Chiffon used to hold:
                    a bright fill carrying dark ink, 10.47:1 against the
                    ground so a call to action cannot be missed.
       --blue-soft  anything that must READ as the brand on a dark ground —
                    kickers, links, small labels. 5.99 / 5.48 / 4.84 across
                    the three surfaces.
       --navy       deep structure and the ink on cyan. At 1.62:1 on the
                    ground it is never text on the page itself.

     --blue-hi goes DEEPER on hover rather than lighter, which is the opposite
     of the usual move. Above L 0.58 white drops under 4.5:1 on this hue, so a
     brighter hover would quietly break the label it carries; the lift comes
     from a stronger glow instead.

     The surfaces and the text ramp keep the exact OKLCH lightness steps of
     the palette they replace and are re-hued to 261.6 degrees, the mark's
     own blue. Nothing in the layout shifts; only the temperature does. */
  /* Tints, glows and hairlines are built from these triplets rather than from
     copies of the hex, so a token change cannot orphan an rgba() behind it. */
  --blue-rgb: 23, 105, 255;          /* the glow behind blue chrome          */
  --blue-soft-rgb: 92, 134, 255;     /* matches --blue-soft exactly          */
  --cyan-rgb: 17, 207, 224;          /* matches --cyan exactly               */

  --blue: #1769FF;              /* Electric Blue — chrome fills and the mark */
  --blue-hi: #0A5EF2;           /* hover — deeper, so white gains contrast   */
  --navy: #142A8A;              /* Deep Navy — structure, and ink on cyan    */
  --blue-soft: #5C86FF;         /* 5.99 ground, 5.48 surface, 4.84 card      */

  --cyan: #11CFE0;              /* Cyan — the action colour                  */
  --cyan-hi: #54DDED;           /* hover — brighter, not a different hue     */
  --cyan-dim: rgba(var(--cyan-rgb), 0.16);
  --on-cyan: #142A8A;           /* 6.38:1 on the cyan fill — the deep navy   */

  --purple: #7A18FF;            /* Accent Purple — fills and washes          */
  --purple-soft: #A15BFF;       /* the same accent where it has to read as
                                   text: 5.18:1 on the ground               */

  /* The plate behind the mark, on site chrome only. A cool white rather than
     the cream it replaces: the mark was drawn on white, and a warm plate
     under a blue-and-cyan mark fights it. */
  --plate-hi: #FFFFFF;
  --plate: #F4F8FF;
  --plate-lo: #E4ECFA;

  /* AirPass is unlisted; these are scoped to that one page. Green still reads
     as a separate signal against a blue brand. */
  --airpass: #1A7343;
  --airpass-soft: #4AD07F;
  --airpass-fill: #1A7343;
  --airpass-fill-hi: #218A52;

  /* Surfaces: navy-black base, two elevation steps above it, at the same
     lightness as the violet-black they replace. The brand blue is
     deliberately NOT the card fill — at this saturation a panel in it would
     compete with every button on the page. */
  --bg: #02081B;
  --bg-rgb: 2, 8, 27;   /* the ground, for the bar and the focus spacer */
  --surface: #061430;
  --surface-hi: #0D2043;

  /* Hairlines carry the cyan tint rather than plain white — on a navy ground
     a neutral-grey rule reads as a seam from a different design. */
  --hairline: rgba(var(--cyan-rgb), 0.13);
  --hairline-hi: rgba(var(--cyan-rgb), 0.24);

  /* Text ramp, re-hued and held at the old lightness. Measured against the
     lightest surface any of it lands on (#0D2043) as well as the ground:
     17.90 / 9.34 / 5.86 on the ground, 14.45 / 7.54 / 4.73 on that band. */
  --ink: #EEF3FB;
  --muted: #A6B2C9;
  --dim: #7E8CA4;

  --ok: #4AD07F;
  --radius: 18px;
  --radius-sm: 12px;
  --pill: 980px;
  --display: "Instrument Sans", "Inter", system-ui, sans-serif;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);      /* expo-out: settles, not stops */
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  /* The overlay curve: leaves immediately, arrives slowly, never overshoots.
     Everything that opens, expands or appears over content uses this one, so
     the whole platform moves with a single hand. */
  --spring: cubic-bezier(0.32, 0.72, 0, 1);

  /* Elevation. Two stacked shadows — a tight contact shadow plus a wide
     ambient one — read as a real object; a single blur reads as a sticker.
     The inset highlight is the specular top edge of a raised surface, and is
     what separates "dark card" from "lit material". */
  --edge: inset 0 1px 0 rgba(255, 255, 255, 0.07);
  --edge-hi: inset 0 1px 0 rgba(255, 255, 255, 0.11);
  --lift: 0 1px 2px rgba(0, 0, 0, 0.5), 0 8px 24px -8px rgba(0, 0, 0, 0.6);
  --lift-hi: 0 1px 2px rgba(0, 0, 0, 0.5), 0 16px 40px -12px rgba(0, 0, 0, 0.7);

  /* The card surface, defined once. Every panel on the site is this material,
     so a change here is a change everywhere rather than eleven near-misses. */
  --panel: linear-gradient(180deg, rgba(var(--cyan-rgb), 0.07), rgba(var(--cyan-rgb), 0.02));

  /* Pointer position within the hovered card, written by app.js. The fallback
     parks the highlight off-card, so nothing shows before the first move and
     nothing shows at all on touch devices, which never fire it. */
  --mx: -400px;
  --my: -400px;
}

* { box-sizing: border-box; margin: 0; }

/* Fixed grain over the whole page: ~2.5% opacity, no interaction cost. */
body::before { display:none; }

/* Keep real content above the grain. */
body > * { position: relative; z-index: 2; }

html {
  scroll-behavior: smooth;
  overflow-x: clip;
  /* The nav is sticky, so an anchor jump (/services/#angol from the footer,
     say) would otherwise park the target heading underneath it. This is the
     bar's height plus a little air. */
  scroll-padding-top: 96px;
}

body {
  font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-feature-settings: "cv11" 1, "ss01" 1;
  background: var(--bg);
  /* Pushed below the bar and thinned out: at -10% with the hero glow stacked
     on top of it, the wash bloomed directly behind the nav and put a bright
     band across the top of every page. */
  background-image: radial-gradient(110% 55% at 50% 4%, rgba(var(--blue-rgb), 0.10), transparent 62%);
  background-attachment: scroll;
  color: var(--ink);
  line-height: 1.6;
  overflow-x: clip;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

a { color: var(--blue-soft); text-decoration: none; }

/* Selection in the action colour, so highlighting text looks like part of the
   design rather than the operating system's idea of blue. */
::selection { background: var(--cyan); color: var(--on-cyan); }

/* The scrollbar is a visible surface on a dark page; left alone it is a bright
   grey stripe down the right edge of an otherwise near-black site. */
* { scrollbar-color: #223659 transparent; scrollbar-width: thin; }
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: #223659;
  border: 3px solid var(--bg);
  border-radius: var(--pill);
}
::-webkit-scrollbar-thumb:hover { background: #304975; }

code {
  font-family: ui-monospace, "SF Mono", "Cascadia Code", Consolas, monospace;
  font-size: 0.88em;
  background: rgba(255, 255, 255, 0.07);
  color: var(--ink);
  padding: 2px 8px;
  border-radius: 6px;
}

b { font-weight: 600; }

.skip {
  position: absolute;
  left: -9999px;
  top: 8px;
  z-index: 60;
  background: var(--cyan);
  color: var(--on-cyan);
  padding: 10px 18px;
  border-radius: var(--pill);
  font-weight: 600;
  box-shadow: var(--lift-hi);
}

.skip:focus { left: 12px; }

/* The focus ring is the action colour with a dark spacer ring behind it, so it
   stays legible over a card, over the cyan button and over the CTA band
   alike — one ring that does not need a variant per surface. */
:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 3px;
  border-radius: 8px;
  box-shadow: 0 0 0 5px rgba(var(--bg-rgb), 0.9);
}

/* ------------------------------------------------------- scroll reveals */

/* Short travel, long ease. Movement should be noticed only in aggregate.

   The hidden state is scoped to html.rv, a class set by a one-line inline
   script in the head of every page. That is deliberate: if the class is
   absent — scripting off, the script blocked, app.js failing to parse —
   nothing is ever hidden, so the page cannot end up with invisible content
   waiting for a reveal that will not come. Being in the head means it lands
   before first paint, so there is no flash of visible content being hidden
   again.

   Only opacity and transform, so the whole thing runs on the compositor and
   costs no layout. That is what makes it affordable on the connections and
   devices this site is built for. */
html.rv .reveal {
  opacity: 0;
  transform: translateY(14px) scale(0.995);
  transition: opacity 0.9s var(--spring), transform 0.9s var(--spring);
  transition-delay: var(--d, 0s);
}

html.rv .reveal.in { opacity: 1; transform: none; }

/* Cards in a grid arrive in reading order rather than as one block. The step
   is small on purpose: past about 70ms per card the last one feels late, and
   the delay is capped at four because a row that keeps counting reads as lag,
   not choreography. */
.svc-grid > .reveal:nth-child(2),
.grid > .reveal:nth-child(2),
.stats > .reveal:nth-child(2),
.products > .reveal:nth-child(2) { --d: 0.06s; }

.svc-grid > .reveal:nth-child(3),
.grid > .reveal:nth-child(3),
.stats > .reveal:nth-child(3),
.products > .reveal:nth-child(3) { --d: 0.12s; }

.svc-grid > .reveal:nth-child(n + 4),
.grid > .reveal:nth-child(n + 4),
.stats > .reveal:nth-child(n + 4),
.products > .reveal:nth-child(n + 4) { --d: 0.18s; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  html.rv .reveal { opacity: 1; transform: none; transition: none; }
  .product:hover, .tile:hover, .svc:hover, .stat:hover { transform: none; }
  .faq .answer { transition: none; }
  .nav { transition: none; }
  .strip-track { animation: none !important; }
  .pulse::after { animation: none !important; }
  .spot::after { display: none; }
  .hero > * { animation: none !important; clip-path: none !important; }
  .nav .brand-mark, .nav .brand-sub { transition: none; }
}

/* ----------------------------------------------------------------- nav */

.nav {
  display: flex;
  align-items: center;
  gap: 28px;
  padding: 12px clamp(16px, 5vw, 56px);
  position: sticky;
  top: 0;
  z-index: 20;
  background: rgba(var(--bg-rgb), 0);
  /* Identity filter at rest, not saturate(180%). The bar is meant to be
     transparent until it is over content, but a backdrop-filter applies
     whatever its value within the element's own box — so saturating at rest
     stamped a hard-edged, more saturated rectangle across the top of every
     page. It was faint under the old violet wash and obvious under this blue
     one. Kept as an identity rather than removed so the transition to the
     frosted state still animates. */
  backdrop-filter: saturate(100%) blur(0px);
  -webkit-backdrop-filter: saturate(100%) blur(0px);
  border-bottom: 1px solid transparent;
  transition: background 0.5s var(--spring), backdrop-filter 0.5s var(--spring),
              border-color 0.5s var(--spring), box-shadow 0.5s var(--spring);
}

/* app.js sets this past the first scroll. The bar earns its material only
   once there is content beneath it to separate from. */
.nav.stuck {
  background: rgba(var(--bg-rgb), 0.66);
  backdrop-filter: saturate(180%) blur(28px);
  -webkit-backdrop-filter: saturate(180%) blur(28px);
  border-bottom-color: var(--hairline);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.03), 0 12px 32px -18px rgba(0, 0, 0, 0.9);
  /* The bar tightens as it takes on material — a few pixels, on the same
     curve as the frosting, so the two read as one gesture rather than a
     colour change and a resize that happen to coincide. */
  padding-top: 8px;
  padding-bottom: 8px;
}

.nav { transition-property: background, backdrop-filter, border-color, box-shadow, padding; }

/* The mark shrinks with the bar. transform rather than height, so it costs
   nothing to animate and the layout underneath never reflows. */
.nav .brand-mark {
  transition: transform 0.5s var(--spring);
  transform-origin: left center;
}

.nav.stuck .brand-mark { transform: scale(0.88); }

/* The descender line under the wordmark is the first thing worth losing when
   space tightens; the name itself never moves. */
.nav .brand-sub {
  transition: opacity 0.4s var(--ease), max-height 0.5s var(--spring);
  max-height: 1.4em;
  overflow: hidden;
}

.nav.stuck .brand-sub { opacity: 0; max-height: 0; }

/* The blur's lower edge is a hard line where a real frosted panel would fade.
   A short scrim under the bar softens the cut without tinting the content. */
.nav::after {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 100%;
  height: 28px;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(180deg, rgba(var(--bg-rgb), 0.5), transparent);
  transition: opacity 0.5s var(--spring);
}

.nav.stuck::after { opacity: 1; }

.brand {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  min-height: 44px;
  color: var(--ink);
  transition: opacity 0.25s var(--ease);
}

.brand:hover { opacity: 0.82; }

/* The mark sits on the bar directly, with no plate behind it.

   The plate was inherited from the lion, which needed one: that mark was a
   single deep violet at 1.97:1 on the ground and dissolved without a light
   surface under it. This one does not need the help. Measured at nav size
   against the bar, the bare mark holds a median 3.76:1 across its pixels
   against 4.64:1 on the plate, and the share that falls below 1.5:1 — the
   part that genuinely disappears — only moves from 7.5% to 10.8%.

   The plate was also working against itself: it lifts the Deep Navy at the
   lower left of the ring, but flattens the cyan at the top, which is the
   brightest thing in the mark. Roughly a wash, in exchange for a white
   rectangle in the corner of every page.

   The box stays, so the bar keeps its rhythm and the scale-on-scroll still
   has something to transform. */
.brand-mark {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Height is authoritative and the width follows, so revising the artwork to a
   different aspect can never stretch the mark — the width/height attributes on
   the tag exist to reserve space, not to fix the shape. */
.brand-mark img { display: block; width: auto; }
.nav .brand-mark img { height: 30px; }
.footer .brand-mark img { height: 26px; }

.brand-word {
  /* Michroma carries only the wordmark; --display is the fallback for the
     nested .brand-sub and anything the subset does not cover. */
  font-family: "Michroma", var(--display);
  font-weight: 400;              /* the only weight it has — see the face */
  /* 16px. Michroma has a small apparent x-height for its width, so at the 17px
     that suited a monospace it read smaller than it measured; below 15 it goes
     thin against the nav links. */
  font-size: 16px;
  letter-spacing: 0;
  line-height: 1.05;
  display: flex;
  flex-direction: column;
}

.brand-sub {
  font-family: "Inter", system-ui, sans-serif;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--dim);
}

.nav-links {
  display: flex;
  gap: 26px;
  margin-left: auto;
  font-size: 14px;
  font-weight: 500;
}

/* Fitts: the hit area is grown with padding and pulled back with an equal
   negative margin, so the target becomes 44px tall while the bar's layout and
   spacing stay exactly as designed. Measured at 24px before this — legal under
   WCAG 2.5.8's 24px floor, but half the comfortable size, and the cost of
   missing is a wasted tap on a phone. */
.nav-links a {
  position: relative;
  color: var(--muted);
  padding: 10px 6px 12px;
  margin: -10px -6px -12px;
  /* The bar drops to 13.5px type below 900px, which left these at 43.59px.
     A floor is more robust than tuning padding per breakpoint. */
  min-height: 44px;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  transition: color 0.2s var(--ease);
}

.nav-links a:hover { color: var(--ink); }
.nav-links a[aria-current="page"] { color: var(--ink); }

/* Which page you are on is worth more than a shade of grey. A short rule in
   the action colour marks it — it grows from the centre on hover too, so the
   indicator and the hover state are visibly the same object. */
.nav-links a::after {
  content: "";
  position: absolute;
  left: 6px; right: 6px; bottom: 10px;
  height: 1.5px;
  border-radius: var(--pill);
  background: var(--cyan);
  transform: scaleX(0);
  transform-origin: center;
  transition: transform 0.4s var(--spring), opacity 0.3s var(--ease);
  opacity: 0.55;
}

.nav-links a:hover::after { transform: scaleX(1); }
.nav-links a[aria-current="page"]::after { transform: scaleX(1); opacity: 1; }

/* The bar's button is permanent furniture; the one in the page is the actual
   ask. If both were cyan there would be two loudest things on screen and
   neither would win, so the chrome takes the brand blue and the content keeps
   the action colour. Same markup — the difference is positional. */
.nav .btn-primary {
  background: var(--blue);
  color: #fff;
  padding: 9px 18px;
  font-size: 14px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.24), 0 1px 2px rgba(0, 0, 0, 0.45);
}

.nav .btn-primary:hover {
  background: var(--blue-hi);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3), 0 2px 4px rgba(0, 0, 0, 0.45),
              0 8px 22px -8px rgba(var(--blue-rgb), 0.75);
}

/* ------------------------------------------------------------- buttons */

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 11px 22px;
  border-radius: var(--pill);
  font-weight: 590;
  font-size: 15px;
  letter-spacing: -0.01em;
  white-space: nowrap;
  isolation: isolate;
  transition: background 0.25s var(--ease), border-color 0.25s var(--ease),
              transform 0.4s var(--spring), color 0.25s var(--ease),
              box-shadow 0.25s var(--ease);
}

/* Press is instant and release is sprung — the asymmetry is what makes a
   button feel physical rather than merely animated. */
.btn:active { transform: scale(0.965); transition-duration: 0.06s; }

/* Lemon Chiffon is the action colour. It appears on the one thing the page
   wants you to press and nowhere else, which is why it can be this loud.
   6.38:1 with the Deep Navy text it carries. */
.btn-primary {
  background: var(--cyan);
  color: var(--on-cyan);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.7),
    inset 0 -1px 0 rgba(var(--blue-rgb), 0.18),
    0 1px 2px rgba(0, 0, 0, 0.45),
    0 8px 22px -8px rgba(var(--cyan-rgb), 0.35);
}

.btn-primary:hover {
  background: var(--cyan-hi);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.8),
    inset 0 -1px 0 rgba(var(--blue-rgb), 0.18),
    0 2px 4px rgba(0, 0, 0, 0.45),
    0 14px 34px -10px rgba(var(--cyan-rgb), 0.5);
}

/* Kept for places that need a filled button without claiming to be *the*
   action — the blue reads as a sibling of the cyan, not a competitor. */
.btn-brand {
  background: var(--blue);
  color: #fff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22), 0 1px 2px rgba(0, 0, 0, 0.45),
              0 6px 18px -6px rgba(var(--blue-rgb), 0.65);
}
.btn-brand:hover { background: var(--blue-hi); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.26), 0 2px 4px rgba(0, 0, 0, 0.45), 0 10px 26px -6px rgba(var(--blue-rgb), 0.85); }

.btn-airpass {
  background: var(--airpass-fill);
  color: #fff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.45),
              0 6px 18px -6px rgba(26, 115, 67, 0.6);
}
.btn-airpass:hover { background: var(--airpass-fill-hi); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.24), 0 2px 4px rgba(0, 0, 0, 0.45), 0 10px 26px -6px rgba(26, 115, 67, 0.75); }

.btn-ghost {
  border: 1px solid var(--hairline-hi);
  color: var(--ink);
  background: rgba(255, 255, 255, 0.02);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

.btn-ghost:hover { background: rgba(255, 255, 255, 0.08); border-color: var(--hairline-hi); }

/* The CTA band's primary action. Same colour as .btn-primary — the action
   colour does not change because the background did. */
.btn-invert {
  background: var(--cyan);
  color: var(--on-cyan);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.7),
    0 1px 2px rgba(0, 0, 0, 0.5),
    0 10px 30px -10px rgba(var(--cyan-rgb), 0.45);
}

.btn-invert:hover {
  background: var(--cyan-hi);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.8),
    0 2px 4px rgba(0, 0, 0, 0.5),
    0 16px 40px -12px rgba(var(--cyan-rgb), 0.6);
}

.btn-outline { border: 1px solid rgba(255, 255, 255, 0.32); color: #fff; }
.btn-outline:hover { background: rgba(255, 255, 255, 0.12); border-color: rgba(255, 255, 255, 0.45); }

/* 11px of padding put the standard button at 40px. A button is the single
   most-aimed-at thing on the page; it gets the full 44. */
.btn { min-height: 44px; }
.btn-lg { padding: 14px 28px; font-size: 16px; min-height: 50px; }

/* The arrow leads the cursor by a few pixels on hover. Tiny, but it is the
   difference between a link that responds and a link that merely recolours. */
.arrow { display: inline-block; transition: transform 0.4s var(--spring); }
.btn:hover .arrow { transform: translateX(4px); }

/* ---------------------------------------------------------------- hero */

.hero {
  position: relative;
  max-width: 900px;
  margin: 0 auto;
  padding: clamp(72px, 13vh, 148px) 20px 76px;
  text-align: center;
}

/* Washes sit behind the type. They were painting over it before — a blurred
   layer at 50% opacity above a headline is a legibility cost for no gain. */

/* Two blooms drifting against each other on different periods, so the light
   never repeats a position exactly. It is slow enough (26s and 34s) to read
   as a lit room rather than an animation — you notice it on the second look,
   which is the point. */

/* The hero bloom is gone. It was a 420x320 ellipse carried entirely by
   filter: blur(130px) — but a 130px blur is the most expensive thing on the
   page to composite, so the fast path below turned the filter off and left the
   ellipse behind: a hard-edged disc floating over the headline on every page.
   A wash that only works blurred does not survive a performance budget, and
   the soft radial on body already does this job at no cost. */



.badge {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: 13px;
  font-weight: 500;
  color: var(--muted);
  border: 1px solid var(--hairline);
  background: rgba(255, 255, 255, 0.04);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  padding: 6px 15px;
  border-radius: var(--pill);
  margin-bottom: 28px;
  box-shadow: var(--edge);
}

.pulse {
  position: relative;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--ok);
}

.pulse::after {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: 50%;
  border: 1.5px solid var(--ok);
  animation: pulse 2.4s ease-out infinite;
}

@keyframes pulse {
  from { transform: scale(0.5); opacity: 1; }
  to { transform: scale(1.6); opacity: 0; }
}

.hero h1 {
  font-family: var(--display);
  font-size: clamp(44px, 8vw, 86px);
  line-height: 1.03;
  letter-spacing: -0.027em;
  font-weight: 700;
  /* Balanced wrapping is the difference between a headline and a headline
     with one word stranded on the last line. Ignored where unsupported. */
  text-wrap: balance;
}

/* A single hue shift, not a spectrum, and it runs the same way the mark does:
   purple at the stem, cyan at the far edge of the ring. The stop sits late so
   the purple reads as the body of the word and the cyan as its lit edge.
   Both ends clear the 3:1 bar for display type — 5.18:1 and 10.47:1. */
.grad {
  background: linear-gradient(102deg, var(--purple-soft) 6%, var(--cyan) 78%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  /* Clipping a gradient to glyphs drops the antialiasing hint browsers apply
     to solid text; without this the edges thin out and the word looks lighter
     than the line above it. */
  -webkit-font-smoothing: antialiased;
}

.lede {
  max-width: 620px;
  margin: 22px auto 0;
  font-size: clamp(17px, 2.2vw, 20px);
  line-height: 1.55;
  color: var(--muted);
  text-wrap: pretty;
}

.hero-actions {
  display: flex;
  gap: 12px;
  justify-content: center;
  flex-wrap: wrap;
  margin-top: 34px;
}

.hero-note { margin-top: 20px; font-size: 13px; color: var(--dim); }

/* --------------------------------------------------------------- strip */

.strip {
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
  overflow: hidden;
  padding: 14px 0;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);
}

.strip-track {
  display: flex;
  gap: 30px;
  width: max-content;
  color: var(--dim);
  font-weight: 500;
  font-size: 13.5px;
  letter-spacing: 0.01em;
  animation: strip 38s linear infinite;
}

.strip-track span:nth-child(even) { color: rgba(var(--cyan-rgb), 0.30); }

@keyframes strip { to { transform: translateX(-50%); } }

/* Moving text you might want to read should stop when you look at it. */
.strip:hover .strip-track { animation-play-state: paused; }

/* ------------------------------------------------------------- sections */

.section {
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(80px, 12vh, 144px) 20px;
}

/* The kicker is a label, and a label reads faster with a mark beside it than
   with more letter-spacing. The dash is the only place the two brand greens meet
   at this size, which quietly restates the palette on every section. */
.kicker {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  color: var(--blue-soft);
  font-weight: 600;
  font-size: 12.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  margin-bottom: 16px;
}

.kicker::before {
  content: "";
  width: 22px;
  height: 1.5px;
  border-radius: var(--pill);
  background: linear-gradient(90deg, transparent, var(--cyan));
  flex: none;
}

/* On the sub-pages the kicker sits in a left-aligned column. */
.page-hero .kicker,
.prose .kicker { justify-content: flex-start; }

.section h2 {
  font-family: var(--display);
  text-align: center;
  font-size: clamp(32px, 5vw, 56px);
  line-height: 1.08;
  letter-spacing: -0.022em;
  font-weight: 600;
  text-wrap: balance;
}

.section-lede {
  text-align: center;
  color: var(--muted);
  font-size: 17px;
  max-width: 620px;
  margin: 18px auto 0;
  text-wrap: pretty;
}

/* ------------------------------------------------------ panel material */

/* Every raised surface on the site — service cards, tiles, stats, the fact
   lists — is one material. Defining it in a single place is what keeps eleven
   panels from drifting into eleven near-identical greys. */
.product, .tile, .stat, .svc, .facts {
  position: relative;
  isolation: isolate;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--edge), var(--lift);
}

/* The spotlight. app.js writes the pointer's position into --mx/--my on the
   card under the cursor; these two layers read it.

   ::after is the light falling on the face of the card. ::before is the same
   light caught on its top edge — a gradient masked down to the 1px border
   ring, so the rim brightens where the cursor is and fades away from it.
   Together they read as a real surface being lit from a point. Neither layer
   exists for touch users: no pointer move, no --mx, highlight stays parked
   off-card. */
.product::after, .tile::after, .stat::after, .svc::after, .facts::after,
.product::before, .tile::before, .stat::before, .svc::before, .facts::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.45s var(--ease);
}

.product::after, .tile::after, .stat::after, .svc::after, .facts::after {
  background: radial-gradient(320px circle at var(--mx) var(--my),
              rgba(var(--cyan-rgb), 0.06), transparent 60%);
}

.product::before, .tile::before, .stat::before, .svc::before, .facts::before {
  padding: 1px;
  background: radial-gradient(260px circle at var(--mx) var(--my),
              rgba(var(--cyan-rgb), 0.45), transparent 58%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
}

.product:hover::after, .tile:hover::after, .stat:hover::after,
.svc:hover::after, .facts:hover::after,
.product:hover::before, .tile:hover::before, .stat:hover::before,
.svc:hover::before, .facts:hover::before { opacity: 1; }

/* A card that lifts on hover should give when pressed, or the lift reads as
   decoration rather than as the surface responding to you. */
.svc:active, .tile:active, .product:active {
  transform: translateY(-1px) scale(0.994);
  transition-duration: 0.09s;
}

/* Coarse pointers get no spotlight at all — a highlight frozen wherever the
   last tap landed is worse than none. */
@media (hover: none) {
  .product::after, .tile::after, .stat::after, .svc::after, .facts::after,
  .product::before, .tile::before, .stat::before, .svc::before, .facts::before {
    display: none;
  }
}

/* ------------------------------------------------------------- products */

.products {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 22px;
  margin-top: 60px;
  align-items: start;
}

.product {
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: 34px;
  transition: background 0.45s var(--ease), border-color 0.45s var(--ease),
              box-shadow 0.45s var(--ease), transform 0.45s var(--spring);
}

.product:hover {
  border-color: var(--hairline-hi);
  box-shadow: var(--edge-hi), var(--lift-hi);
  transform: translateY(-2px);
}
.product.soon { background: rgba(255, 255, 255, 0.02); }
.product.soon:hover { background: var(--surface); border-color: rgba(74, 208, 127, 0.3); }

.product-head { margin-bottom: 22px; }

.product-logo {
  display: inline-flex;
  width: fit-content;
  max-width: 100%;
  border-radius: 14px;
  overflow: hidden;
  margin-bottom: 16px;
  line-height: 0;
  box-shadow: var(--edge), 0 4px 16px -4px rgba(0, 0, 0, 0.6);
}

.product-logo img { height: 64px; width: auto; max-width: 100%; display: block; }

/* AirPass ships a transparent symbol with no wordmark, so it is composed with
   live text — the name stays selectable and crisp at any size. */
.ap-lockup { display: flex; align-items: center; gap: 15px; margin-bottom: 16px; }
.ap-lockup img { height: 44px; width: auto; display: block; flex: none; }

.ap-word {
  font-family: var(--display);
  font-size: 26px;
  font-weight: 700;
  letter-spacing: -0.012em;
  line-height: 1.05;
  color: var(--ink);
  display: flex;
  flex-direction: column;
  margin: 0;
}

.ap-word em {
  font-family: "Inter", system-ui, sans-serif;
  font-style: normal;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--airpass-soft);
  margin-top: 5px;
}

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

.product-tag { font-size: 13px; color: var(--dim); font-weight: 500; }
.product-tag .live { color: var(--ok); font-weight: 600; }
.product-tag .soon-dot { color: var(--airpass-soft); font-weight: 600; }

.product-lede { color: var(--muted); font-size: 15.5px; }

/* Wraps rather than scrolling: a chip cut off mid-word reads as a bug. */
.flow {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin: 22px 0 4px;
  padding: 14px 16px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  background: rgba(0, 0, 0, 0.35);
}

.flow code { background: rgba(255, 255, 255, 0.07); color: var(--ink); white-space: nowrap; }
.flow code.hl { background: rgba(var(--cyan-rgb), 0.14); color: var(--cyan); font-weight: 600; }
.flow i { font-style: normal; color: var(--dim); flex: none; }

.feature-list {
  list-style: none;
  padding: 0;
  margin: 22px 0 28px;
  color: var(--muted);
  font-size: 14.5px;
}

.feature-list li { padding: 7px 0 7px 28px; position: relative; }

/* A drawn tick rather than a text glyph — consistent weight with the icons. */
.feature-list li::before {
  content: "";
  position: absolute;
  left: 3px; top: 13px;
  width: 13px; height: 7px;
  border-left: 1.8px solid var(--ok);
  border-bottom: 1.8px solid var(--ok);
  transform: rotate(-45deg);
}

.soon-panel {
  margin: 22px 0 28px;
  padding: 20px 22px;
  border: 1px solid rgba(74, 208, 127, 0.22);
  border-radius: var(--radius-sm);
  background: rgba(74, 208, 127, 0.05);
  color: var(--muted);
  font-size: 14px;
}

.soon-panel b { color: var(--airpass-soft); }

.product .btn { margin-top: auto; align-self: flex-start; }

/* --------------------------------------------------------- thesis stats */

.section.dark {
  max-width: none;
  background:
    radial-gradient(90% 60% at 50% 0%, rgba(var(--blue-rgb), 0.10), transparent 70%),
    linear-gradient(180deg, var(--navy), var(--bg));
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
  box-shadow: var(--edge);

  /* The band opens on Deep Navy rather than on the brand blue. Electric Blue
     is too light to be a ground: pure --ink measures only 4.19:1 on it and
     cyan 2.45:1, so nothing in the palette could sit on it. Navy is the
     structure colour and carries the whole ramp — cyan 6.38:1, ink 10.91:1.

     Inside the band the secondary text still comes off the cyan side, because
     --blue-soft is 3.65:1 here. Scoped as token overrides so every descendant
     follows without a rule of its own; the band fades to the page ground at
     the bottom, where these same values only gain contrast. */
  --blue-soft: var(--cyan);      /* 6.38:1 at the top of the band */
  --dim: #C2CFE6;                /* 7.73:1 */
}

.dark-inner { max-width: 1120px; margin: 0 auto; }

.stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
  margin-top: 56px;
}

.stat {
  min-width: 0;
  padding: 32px;
  transition: border-color 0.45s var(--ease), box-shadow 0.45s var(--ease),
              transform 0.45s var(--spring);
}

.stat:hover {
  border-color: var(--hairline-hi);
  box-shadow: var(--edge-hi), var(--lift-hi);
  transform: translateY(-2px);
}

.stat-num {
  font-family: var(--display);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1, "ss01" 1;
  font-size: clamp(34px, 4vw, 50px);
  font-weight: 700;
  letter-spacing: -0.022em;
  line-height: 1.1;
  color: var(--ink);
}

.stat h3 {
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--blue-soft);
  margin: 10px 0 12px;
}

.stat p { color: var(--muted); font-size: 14.5px; }

.latency { margin-top: 22px; display: grid; gap: 10px; }

.lat-row {
  display: grid;
  grid-template-columns: 78px 1fr 46px;
  align-items: center;
  gap: 10px;
  font-size: 12.5px;
  color: var(--dim);
}

.lat-row i {
  display: block;
  height: 6px;
  width: var(--w);
  border-radius: var(--pill);
  background: var(--cyan);
}

.lat-row i.slow { background: #1B2B48; }
.lat-row em { font-style: normal; text-align: right; }

/* -------------------------------------------------------------- company */

.company {
  display: grid;
  grid-template-columns: 1.55fr 1fr;
  gap: 48px;
  margin-top: 52px;
  align-items: start;
}

.company-text { min-width: 0; }
.company-text p { color: var(--muted); font-size: 16px; }
.company-text p + p { margin-top: 18px; }

.facts { padding: 8px 26px; }

/* Proximity: these rows pushed the label to one edge and its value to the
   other, measuring up to 204px apart — far enough that the eye has to track
   back along the row to pair them, and the hairline does the associating
   instead of the spacing. A max-content column puts each value directly after
   its own label, so the pair reads as one object. */
.facts > div {
  display: grid;
  grid-template-columns: max-content 1fr;
  align-items: baseline;
  column-gap: 18px;
  padding: 14px 0;
  border-bottom: 1px solid var(--hairline);
}

.facts > div:last-child { border-bottom: 0; }

.facts dt {
  color: var(--dim);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  flex: none;
}

/* Left-aligned now that it sits beside its label rather than opposite it —
   right-alignment was what created the gap in the first place. */
.facts dd { margin: 0; font-size: 14.5px; font-weight: 500; text-align: left; }

/* --------------------------------------------------- sub-page furniture */

.page-hero {
  position: relative;
  max-width: 900px;
  margin: 0 auto;
  padding: clamp(44px, 8vh, 84px) 20px clamp(36px, 6vh, 60px);
}

.page-hero h1 {
  font-family: var(--display);
  font-size: clamp(36px, 5.8vw, 62px);
  line-height: 1.05;
  letter-spacing: -0.025em;
  font-weight: 700;
  text-wrap: balance;
}

.page-hero .lede { margin-left: 0; margin-right: 0; max-width: 660px; text-align: left; }

.crumbs {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-size: 13px;
  color: var(--dim);
  margin-bottom: 20px;
}

/* Same trick on the breadcrumb, which measured 21px tall. */
.crumbs a {
  color: var(--muted);
  padding: 12px 5px;
  margin: -12px -5px;
  transition: color 0.2s var(--ease);
}
.crumbs a:hover { color: var(--ink); }
.crumbs [aria-current="page"] { color: var(--cyan); }
/* Decorative (aria-hidden), but still held to the text ramp so it reads as a
   separator rather than a rendering artifact — and one step below the links,
   which keeps the hierarchy readable. */
.crumbs .sep { color: var(--dim); }

.prose { max-width: 700px; margin: 0 auto; }
.prose h2 {
  font-family: var(--display);
  text-align: left;
  font-size: clamp(22px, 2.6vw, 27px);
  letter-spacing: -0.015em;
  font-weight: 600;
  margin: 44px 0 12px;
  text-wrap: balance;
}
.prose h3 { font-family: var(--display); font-size: 17px; font-weight: 600; margin: 28px 0 8px; }
.prose p, .prose li { color: var(--muted); font-size: 16px; text-wrap: pretty; }
.prose p + p { margin-top: 15px; }
.prose ul, .prose ol { margin: 12px 0 0; padding-left: 22px; }
.prose li { padding: 5px 0; }
.prose a { text-decoration: underline; text-underline-offset: 3px; }
.prose .updated { font-size: 13.5px; color: var(--dim); }

/* ---------------------------------------------------------------- tiles */

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(272px, 1fr));
  gap: 20px;
  margin-top: 52px;
}

/* An auto-fit grid picks three columns at desktop widths, which leaves a set
   of four with one card alone on the second row — it reads as an afterthought
   rather than the fourth of four. Sets of exactly four go two by two, the
   same shape as the service grid. Sets of three or six are already whole. */
.grid:has(> :nth-child(4):last-child) { grid-template-columns: repeat(2, 1fr); }

@media (max-width: 860px) {
  .grid:has(> :nth-child(4):last-child) { grid-template-columns: 1fr; }
}

.tile {
  min-width: 0;
  padding: 30px;
  transition: border-color 0.45s var(--ease), box-shadow 0.45s var(--ease),
              transform 0.45s var(--spring);
}

.tile:hover {
  border-color: var(--hairline-hi);
  box-shadow: var(--edge-hi), var(--lift-hi);
  transform: translateY(-2px);
}

/* Icons are drawn on a 24px grid with a single stroke weight, so they read as
   one family rather than as clip art. */
.tile-icon {
  display: grid;
  place-items: center;
  width: 42px; height: 42px;
  margin-bottom: 18px;
  border-radius: var(--radius-sm);
  /* The fill and the numeral on it are the same hue, so the chip was competing
     with its own content — the numerals measured 4.46:1 against a 0.26 fill.
     The fill comes down and the ring goes up: the chip still reads as an
     object, and the numeral clears AA. */
  background: linear-gradient(158deg, rgba(var(--blue-soft-rgb), 0.17), rgba(var(--blue-soft-rgb), 0.05));
  color: var(--blue-soft);
  box-shadow: var(--edge), inset 0 0 0 1px rgba(var(--blue-soft-rgb), 0.22);
}

.tile-icon svg { width: 21px; height: 21px; display: block; }
.tile-icon.green {
  background: linear-gradient(158deg, rgba(74, 208, 127, 0.22), rgba(74, 208, 127, 0.06));
  color: var(--airpass-soft);
  box-shadow: var(--edge), inset 0 0 0 1px rgba(74, 208, 127, 0.14);
}
.tile-icon.gold {
  background: linear-gradient(158deg, rgba(var(--cyan-rgb), 0.22), rgba(var(--cyan-rgb), 0.06));
  color: var(--cyan);
  box-shadow: var(--edge), inset 0 0 0 1px rgba(var(--cyan-rgb), 0.16);
}

.tile h3 {
  font-family: var(--display);
  font-size: 17.5px;
  font-weight: 600;
  letter-spacing: -0.007em;
  margin-bottom: 9px;
}

.tile p { color: var(--muted); font-size: 14.5px; }

.tile-num {
  font-variant-numeric: tabular-nums;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--dim);
  letter-spacing: 0.12em;
  display: block;
  margin-bottom: 12px;
}

/* ------------------------------------------------------------- services */

/* The service grid is the site's spine now: one card per offering, each
   stating plainly whether it is live or still being built. */
/* Two by two. An auto-fit grid strands the fourth card on its own row at
   common desktop widths, which reads as an afterthought rather than a set. */
.svc-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20px;
  margin-top: 56px;
}

@media (max-width: 860px) {
  .svc-grid { grid-template-columns: 1fr; }
}

.svc {
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: 32px;
  transition: border-color 0.45s var(--ease), box-shadow 0.45s var(--ease),
              transform 0.45s var(--spring);
}

.svc:hover {
  border-color: var(--hairline-hi);
  box-shadow: var(--edge-hi), var(--lift-hi);
  transform: translateY(-2px);
}

.svc-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 22px;
}

.svc h3 {
  font-family: var(--display);
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.009em;
  margin-bottom: 10px;
}

.svc p { color: var(--muted); font-size: 14.5px; text-wrap: pretty; }
.svc p + p { margin-top: 12px; }
/* Guarantees a gap above the divider even when the copy fills the card. */
.svc > p:last-of-type { margin-bottom: 8px; }

/* The icon plate grows very slightly with the card and catches a touch more
   light. Scale alone would read as a jump; pairing it with the lift makes it
   part of the same gesture. */
.svc .tile-icon {
  margin-bottom: 0;
  transition: transform 0.5s var(--spring), background 0.45s var(--ease),
              box-shadow 0.45s var(--ease);
}

.svc:hover .tile-icon {
  transform: scale(1.06);
  box-shadow: var(--edge-hi), 0 6px 18px -8px rgba(var(--cyan-rgb), 0.6);
}

/* Status is a fact, not decoration — it reads at a glance and never
   overstates. The dot carries the colour so the label itself can stay in the
   text ramp and remain readable at 10.5px. */
.status {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: none;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 5px 12px 5px 10px;
  border-radius: var(--pill);
  border: 1px solid var(--hairline);
  background: rgba(0, 0, 0, 0.28);
  color: var(--muted);
  white-space: nowrap;
}

.status::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.5;
  flex: none;
}

.status.live { border-color: rgba(74, 208, 127, 0.32); }
.status.live::before { background: var(--ok); opacity: 1; box-shadow: 0 0 0 3px rgba(74, 208, 127, 0.16); }

.status.soon { border-color: rgba(var(--cyan-rgb), 0.26); }
.status.soon::before { background: var(--cyan); opacity: 1; box-shadow: 0 0 0 3px rgba(var(--cyan-rgb), 0.13); }

/* A hairline above the link separates the card's claim from its call to
   action, and gives every card the same baseline regardless of copy length. */
.svc-link {
  margin-top: auto;
  min-height: 44px;
  padding-top: 20px;
  border-top: 1px solid var(--hairline);
  font-size: 14px;
  font-weight: 550;
  display: flex;
  align-items: center;
  gap: 6px;
  /* Stretch rather than shrink-to-fit: the rule then lines up across the row
     of cards, and the whole bottom strip becomes the hit target. */
  align-self: stretch;
  color: var(--cyan);
  transition: color 0.2s var(--ease), border-color 0.45s var(--ease);
}

.svc:hover .svc-link { border-color: var(--hairline-hi); }
.svc-link .arrow { transition: transform 0.4s var(--spring); }
.svc-link:hover .arrow { transform: translateX(4px); }

/* ------------------------------------------------------------------ faq */

.faq { max-width: 760px; margin: 48px auto 0; }

.faq details {
  border-bottom: 1px solid var(--hairline);
  padding: 20px 2px;
}

.faq details:first-child { border-top: 1px solid var(--hairline); }

.faq summary {
  user-select: none;
  font-weight: 550;
  font-size: 16px;
  letter-spacing: -0.01em;
  cursor: pointer;
  list-style: none;
  position: relative;
  /* Fitts: the summary is the whole control for a disclosure, and it measured
     26px. The vertical padding is absorbed by the row's existing 20px, so the
     accordion looks identical and the target reaches 44px. */
  padding: 9px 34px 9px 0;
  margin: -9px 0;
  /* padding alone landed on 43.98px — a pixel short after rounding */
  min-height: 44px;
  box-sizing: border-box;
  transition: color 0.2s var(--ease);
}

.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { color: var(--blue-soft); }

/* Drawn chevron, rotating to point down when open. */
.faq summary::after {
  content: "";
  position: absolute;
  right: 6px; top: 6px;
  width: 8px; height: 8px;
  border-right: 1.7px solid var(--dim);
  border-bottom: 1.7px solid var(--dim);
  transform: rotate(-45deg);
  transition: transform 0.45s var(--spring);
}

.faq details[open] summary::after { transform: rotate(45deg); }
.faq details[open] summary { color: var(--ink); }

/* A 0fr → 1fr grid row is the only way to transition to intrinsic height
   without measuring in JS. The inner element must be able to shrink, hence
   min-height: 0 and overflow hidden. */
.faq .answer {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  transition: grid-template-rows 0.5s var(--spring),
              opacity 0.3s var(--spring),
              transform 0.5s var(--spring);
  transform: translateY(-4px);
}

.faq .answer-in { min-height: 0; overflow: hidden; }

.faq details[open] .answer,
.faq details.closing .answer { opacity: 1; transform: none; }
.faq details[open] .answer { grid-template-rows: 1fr; }

/* Mid-close: JS holds the element open while this plays out. */
.faq details.closing .answer { grid-template-rows: 0fr; opacity: 0; transform: translateY(-4px); }

.faq details p { margin-top: 14px; color: var(--muted); font-size: 15px; max-width: 64ch; }
.faq details p + p { margin-top: 10px; }
.faq details p:first-child { margin-top: 14px; }

/* ------------------------------------------------------------------ cta */

.cta {
  position: relative;
  text-align: center;
  padding: clamp(88px, 14vh, 148px) 20px;
  background:
    radial-gradient(80% 120% at 50% 120%, rgba(var(--blue-rgb), 0.45), transparent 65%),
    linear-gradient(180deg, #0A1E44, #040B1A);
  border-top: 1px solid var(--hairline);
  box-shadow: var(--edge-hi);
  overflow: hidden;
  isolation: isolate;
}

/* The band's top edge is where the page changes material. A hairline that
   brightens toward the centre reads as light catching a seam; a flat 1px rule
   reads as a table border. */
.cta::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(var(--cyan-rgb), 0.34), transparent);
}

/* A single bloom low in the band, so the gradient has a source. It breathes
   on the same slow clock as the hero, which is what keeps the two ends of the
   page feeling like one room. */


.cta h2 {
  font-family: var(--display);
  font-size: clamp(32px, 5vw, 54px);
  line-height: 1.08;
  letter-spacing: -0.022em;
  font-weight: 600;
  color: var(--ink);
  margin-bottom: 14px;
  position: relative;
  text-wrap: balance;
}

.cta p {
  color: var(--muted);
  font-size: 17px;
  max-width: 540px;
  margin: 0 auto 32px;
  position: relative;
}

.cta-actions {
  display: flex;
  gap: 12px;
  justify-content: center;
  flex-wrap: wrap;
  position: relative;
}

/* -------------------------------------------------------------- footer */

.footer {
  position: relative;
  border-top: 1px solid var(--hairline);
  padding: 64px 20px 44px;
}

/* Same lit seam as the CTA band, at a lower level — the footer is a quieter
   boundary than the call to action, and the light says so. */
.footer::before {
  content: "";
  position: absolute;
  inset: -1px 0 auto;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(var(--cyan-rgb), 0.28), transparent);
}

.footer-inner {
  max-width: 1120px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1.6fr repeat(3, 1fr);
  gap: 32px;
}

.footer-brand { display: inline-flex; align-items: center; gap: 11px; }
.footer-tag { color: var(--dim); font-size: 14px; margin-top: 16px; max-width: 300px; }
.footer-mail {
  display: inline-block;
  margin-top: 6px;
  font-size: 14px;
  /* was a 17px line of text and it is the site's only contact address */
  padding: 13px 4px;
  margin-left: -4px;
}

.footer-col h4 {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--dim);
  margin-bottom: 16px;
}

.footer-col a {
  display: block;
  width: fit-content;
  color: var(--muted);
  font-size: 14px;
  /* 5px of padding gave a 27px target. 11px reaches 44px — but the negative
     margin that kept the old rhythm made consecutive links overlap by 17px,
     so a tap in the seam hit whichever happened to be on top. The column is
     simply taller now; an unambiguous target is worth the pixels. */
  padding: 11px 0;
  transition: color 0.2s var(--ease), transform 0.35s var(--spring);
}

.footer-col a:hover { color: var(--ink); transform: translateX(3px); }

.footer-legal {
  max-width: 1120px;
  margin: 40px auto 0;
  padding-top: 26px;
  border-top: 1px solid var(--hairline);
  color: var(--dim);
  font-size: 12.5px;
}

/* -------------------------------------------------- page transitions */

/* This is an eight-page site with no framework, so every internal link is a
   full document load — the hard white-to-dark flash between pages is the one
   thing that most gives away "static site" rather than "product". A
   cross-document view transition removes it: the outgoing page is captured,
   the incoming one cross-fades in, and the browser does it natively with no
   script and no router.

   Where it is unsupported the whole at-rule is ignored and navigation is
   exactly what it was. */
@view-transition { navigation: auto; }

/* The chrome does not belong in the cross-fade. Naming the bar and the footer
   gives each its own transition group, and pinning those groups to no
   animation holds them perfectly still while the content beneath changes —
   which is what makes the navigation read as one continuous surface rather
   than two pages swapped. */
.nav { view-transition-name: site-nav; }
.footer { view-transition-name: site-footer; }

::view-transition-group(site-nav),
::view-transition-group(site-footer) { animation: none; }

::view-transition-old(root) {
  animation: vt-out 0.28s var(--ease-in-out) both;
}

::view-transition-new(root) {
  animation: vt-in 0.42s var(--spring) both;
}

@keyframes vt-out { to { opacity: 0; transform: translateY(-6px); } }
@keyframes vt-in { from { opacity: 0; transform: translateY(10px); } }

@media (prefers-reduced-motion: reduce) {
  /* Still suppress the flash, but with no movement at all. */
  ::view-transition-old(root),
  ::view-transition-new(root) { animation-duration: 1ms; }
}

/* ------------------------------------------- scroll-driven enhancement */

/* The hero releases the page as you leave it. Entirely optional: browsers
   without scroll-driven animations skip the whole block and get a hero that
   simply scrolls away, which is the same thing minus the grace note.

   Because an animation outranks a transition, these elements are also lifted
   out of the .reveal system — they render at full opacity from the first
   frame. That is the right outcome above the fold anyway: there is nothing to
   scroll into view when the content is already on screen. */
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    /* Two animations on one element, on two different timelines: the wipe runs
       once on load against the clock, the exit is driven by scroll position.
       They are kept to disjoint properties on purpose — clip-path for one,
       opacity and transform for the other — because when two animations touch
       the same property the later one in the list wins outright and the first
       would simply never be seen. */
    .hero > * {
      animation: hero-wipe 0.95s var(--spring) both, hero-exit linear both;
      animation-timeline: auto, scroll(root);
      animation-range: normal, 12vh 78vh;
    }

    .hero > :nth-child(3) { animation-delay: 0.06s, 0s; }
    .hero > :nth-child(4) { animation-delay: 0.12s, 0s; }
    .hero > :nth-child(5) { animation-delay: 0.18s, 0s; }
    .hero > :nth-child(6) { animation-delay: 0.24s, 0s; }
  }
}

/* The headline is uncovered rather than faded in. The end state uses a
   negative bottom inset so the finished element is not clipped at its own
   border box — button shadows in .hero-actions sit outside it. */
@keyframes hero-wipe {
  from { clip-path: inset(0 0 100% 0); }
  to   { clip-path: inset(0 0 -60% 0); }
}

@keyframes hero-exit {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(-26px); }
}

/* -------------------------------------------------------------- mobile */

@media (max-width: 900px) {
  /* The nav must never be hidden: this is a multi-page site, and on a phone
     these links are the only way to reach the other pages. */
  .nav { flex-wrap: wrap; gap: 12px; padding-bottom: 10px; }
  .nav-links {
    order: 3;
    width: 100%;
    margin-left: 0;
    justify-content: flex-start;
    flex-wrap: wrap;
    /* The links are 44px targets pulled back by a negative margin so the bar
       keeps its height on desktop. Once they wrap — five items do, at 360px —
       that pull makes the two rows overlap by 14px. Where it wraps, the links
       take their real height and the row gap comes down to match. */
    gap: 2px 18px;
    font-size: 13.5px;
  }
  .nav-links a { margin-block: 0; }
  .products, .stats { grid-template-columns: 1fr; }
  .company { grid-template-columns: 1fr; gap: 32px; }
  .btn { white-space: normal; }
  .hero-actions .btn, .cta-actions .btn { width: 100%; justify-content: center; }
  .footer-inner { grid-template-columns: 1fr 1fr; gap: 28px; }
  .footer-brand-col { grid-column: 1 / -1; }
  /* The nav wraps to two rows on a phone, so the anchor offset grows with it. */
  html { scroll-padding-top: 132px; }
}

@media (max-width: 620px) {
  /* 32px of padding inside a 340px card leaves very little line length. */
  .svc, .tile, .stat { padding: 24px; }
  .product { padding: 26px; }
  .facts { padding: 4px 20px; }
  /* Blurred blooms are the most expensive thing on the page to composite, and
     a mid-range phone is exactly where that is felt. Smaller radius, same
     effect at this screen size. */
}

@media (max-width: 520px) {
  .footer-inner { grid-template-columns: 1fr; }
}

/* ------------------------------------------------ partners & platforms */
.partner-showcase{padding:34px 20px 72px;border-bottom:1px solid var(--hairline)}
.partner-inner{max-width:1120px;margin:0 auto}
.partner-copy{display:flex;align-items:end;justify-content:space-between;gap:28px;margin-bottom:28px}
.partner-copy h2{font-family:var(--display);font-size:clamp(24px,3vw,34px);line-height:1.12;letter-spacing: -0.017em;max-width:560px}
.partner-copy>p:last-child{color:var(--dim);font-size:14px;max-width:310px;text-align:right}
.partner-copy .kicker{position:absolute;transform:translateY(-32px)}
.partner-rail{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:0;border:1px solid var(--hairline);border-radius:20px;background:rgba(var(--cyan-rgb),.025);overflow:hidden;box-shadow:var(--edge),var(--lift)}
.partner-mark{min-height:128px;border:0;border-right:1px solid var(--hairline);border-radius:0;background:transparent;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:20px;text-align:center;transition:background .35s var(--ease),transform .35s var(--spring)}.partner-mark:last-child{border-right:0}
.partner-mark:hover{transform:none;background:rgba(var(--cyan-rgb),.055)}
.partner-mark strong{font-family:var(--display);font-size:14px;font-weight:600;letter-spacing: 0;color:var(--ink)}
.partner-symbol{width:38px;height:38px;border-radius:12px;display:grid;place-items:center;font-family:var(--display);font-weight:700;font-size:20px;background:var(--surface-hi);box-shadow:var(--edge)}
.partner-mark.talaricloud .partner-symbol{color:#087244}
.partner-mark.vetnova .partner-symbol{color:#6c45a5}
.partner-mark.verto .partner-symbol{color:#075747}
.partner-mark.shape .partner-symbol{color:#37df6c}
.partner-mark.synergy .partner-symbol{color:#8eff16}
@media(max-width:900px){.partner-copy{align-items:start;flex-direction:column}.partner-copy>p:last-child{text-align:left}.partner-rail{grid-template-columns:repeat(2,minmax(0,1fr));overflow:visible}.partner-mark{min-width:0;border-right:1px solid var(--hairline);border-bottom:1px solid var(--hairline)}.partner-mark:nth-child(2n){border-right:0}.partner-mark:last-child{grid-column:1/-1;border-right:0;border-bottom:0}}

.seo-service-links{max-width:1120px;margin:0 auto;padding:34px 20px 54px;display:flex;justify-content:center;gap:10px;flex-wrap:wrap}.seo-service-links a{font-size:12.5px;color:var(--muted);border:1px solid var(--hairline);border-radius:var(--pill);padding:8px 13px;min-height:44px;display:inline-flex;align-items:center;transition:.25s var(--ease)}.seo-service-links a:hover{color:var(--ink);border-color:var(--hairline-hi);transform:translateY(-1px)}

/* PERFORMANCE FAST PATH
   Keep the premium visual language while avoiding expensive paint/composite
   work on phones and while content is outside the viewport. */
@media (max-width: 900px) {
  body::before { display: none; }
  .nav.stuck { backdrop-filter: none; -webkit-backdrop-filter: none; background: rgba(var(--bg-rgb),.97); }
  .svc, .tile, .stat, .product, .facts, .partner-mark { box-shadow: var(--edge); }
}
@supports (content-visibility: auto) {
  .section, .partner-showcase, .cta, .footer {
    content-visibility: auto;
    contain-intrinsic-size: auto 700px;
  }
}

/* ULTRA FAST STATIC PATH — prioritize first paint and scrolling. */
.svc,.tile,.stat,.product,.facts,.partner-mark{will-change:auto!important}
@media (hover:none),(pointer:coarse){.svc:hover,.tile:hover,.partner-mark:hover{transform:none!important}}
@media (max-width:900px){.nav,.nav.stuck{backdrop-filter:none!important;-webkit-backdrop-filter:none!important}}

.partner-logo{display:block;width:auto!important;height:auto!important;max-width:96px;max-height:64px;object-fit:contain;object-position:center}.partner-logo-wide{max-width:132px}.partner-mark strong{margin-top:2px}@media(max-width:900px){.partner-logo{display:block;width:auto!important;height:auto!important;max-width:96px;max-height:64px;object-fit:contain;object-position:center}.partner-logo-wide{max-width:132px}}

/* Final partner-logo balance — artwork is never cropped or stretched. */
.partner-mark{min-height:126px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:9px}
/* Every mark gets the same 72px-tall box and is contained inside it, rather
   than sizing to its own artwork. The five are a mix of wide lockups and tall
   symbols, and letting each set its own height pushed the labels underneath
   onto five different baselines. The box fixes the row; the per-brand width
   caps below set optical weight, which is the part that actually differs —
   a tall symbol needs less width than a wordmark to read as the same size. */
.partner-logo{display:block!important;width:auto!important;height:72px!important;object-fit:contain!important;object-position:center!important;max-width:104px!important}
.partner-mark.talaricloud .partner-logo{max-width:142px!important}
.partner-mark.vetnova .partner-logo{max-width:96px!important}
.partner-mark.verto .partner-logo{max-width:68px!important}
.partner-mark.shape .partner-logo{max-width:86px!important}
.partner-mark.synergy .partner-logo{max-width:56px!important}

/* Fitts: a link on its own line at the foot of a tile is aimed at, not read
   mid-sentence — it measured 17px tall. Prose links inside .prose are left
   alone, where padding would break the line rhythm of a paragraph. */
.tile > p > a,
.svc > p > a {
  display: inline-block;
  padding: 13px 4px;
  margin: -13px -4px;
}

/* Fitts: an anchor wrapping only a logo collapses to the image's own height —
   20-26px here. The link keeps its visual size and gains a real target. */
.partner-showcase a,
.product-logo a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

/* A value in a fact list is a discrete thing being aimed at, not a word inside
   a sentence, so the email address gets a target. The one deliberate exception
   left is a link inside running prose (.company-text, .prose): padding there
   would overlap the lines above and below, and a reader aims at those
   differently.

   The height comes from the row rather than from padding pulled back by a
   negative margin. The first attempt did the latter and the link's box bled
   20px past the end of the list, landing on top of the service links below —
   a target that overlaps its neighbour is the very thing this is meant to
   prevent. */
.facts > div { padding: 5px 0; }
.facts dd { min-height: 44px; display: flex; align-items: center; }
.facts dt { align-self: center; }

.facts dd a {
  display: flex;
  align-items: center;
  align-self: stretch;
  padding-inline: 4px;
  margin-inline: -4px;
}

/* A paragraph whose entire content is one link is a call to action, not prose. */
.company-text > p > a:only-child {
  display: inline-block;
  padding: 12px 4px;
  margin: -12px -4px;
}

/* ================================================================ work */

/* The selected-work page. Layout follows a reference the client supplied:
   an oversized headline beside a stats column, a filter row, then a mosaic
   of one large featured card, two supporting cards, a row of equal panels
   and a row of small cards. Everything is on the house palette — the cards
   are the same --panel material as the rest of the site, so this reads as
   another page of the product rather than a template dropped in. */

.work-hero {
  position: relative;
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(40px, 7vh, 76px) 20px clamp(26px, 4vh, 40px);
}

.work-hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(280px, 1fr);
  gap: clamp(28px, 5vw, 64px);
  align-items: end;
}

.work-hero .kicker { justify-content: flex-start; }

.work-hero h1 {
  font-family: var(--display);
  font-size: clamp(40px, 7vw, 76px);
  line-height: 1.02;
  letter-spacing: -0.029em;
  font-weight: 700;
  text-wrap: balance;
}

.work-hero .lede { margin: 20px 0 0; max-width: 54ch; text-align: left; }

.work-hero-side { display: flex; flex-direction: column; gap: 22px; }

.work-status {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  align-self: flex-start;
  font-size: 13px;
  color: var(--muted);
  border: 1px solid var(--hairline);
  background: rgba(0, 0, 0, 0.28);
  padding: 7px 15px;
  border-radius: var(--pill);
}

/* Three facts, not three boasts — each one is either a count of what is on
   this page or a claim the site already makes elsewhere. */
.work-stats { display: grid; gap: 2px; margin: 0; }

.work-stats > div {
  display: grid;
  grid-template-columns: 1fr max-content;
  align-items: baseline;
  gap: 18px;
  padding: 13px 0;
  border-top: 1px solid var(--hairline);
}

.work-stats dt {
  color: var(--dim);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.work-stats dd {
  margin: 0;
  font-family: var(--display);
  font-size: 26px;
  font-weight: 700;
  letter-spacing: -0.017em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------ filters */

.work-filters {
  max-width: 1120px;
  margin: 0 auto;
  padding: 6px 20px 30px;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
}

.work-chip {
  font: inherit;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--muted);
  background: rgba(0, 0, 0, 0.28);
  border: 1px solid var(--hairline);
  border-radius: var(--pill);
  padding: 0 17px;
  min-height: 44px;            /* Fitts, same floor as every other target */
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  transition: color 0.2s var(--ease), background 0.25s var(--ease),
              border-color 0.25s var(--ease), transform 0.4s var(--spring);
}

.work-chip:hover { color: var(--ink); border-color: var(--hairline-hi); }
.work-chip:active { transform: scale(0.97); transition-duration: 0.08s; }

/* Von Restorff: the selected filter is the one different thing in the row,
   so it takes the action colour and everything else stays quiet. */
.work-chip.is-on {
  background: var(--cyan);
  border-color: var(--cyan);
  color: var(--on-cyan);
  font-weight: 600;
}

.work-count { margin-left: auto; font-size: 13px; color: var(--dim); }

/* --------------------------------------------------------- the mosaic */

.work-grid {
  max-width: 1120px;
  margin: 0 auto;
  padding: 0 20px;
  display: grid;
  grid-template-columns: minmax(0, 1.62fr) minmax(0, 1fr);
  gap: 20px;
}

/* The portfolio is a gallery, and a gallery wants its work lifted off the wall
   rather than sunk into it. The rest of the site is a dark page with cards on
   it; here the ground comes up a step and the card wash roughly doubles, so
   the projects read as objects sitting above the surface. Scoped to this page
   alone — it is the only one whose job is to show things rather than say them.

   The lift is small on purpose. Everything on this page was re-measured from
   pixels afterwards: a lighter ground costs contrast, and the text on it still
   has to clear AA. */
body.work-page {
  --bg: #051026;
  --panel: linear-gradient(180deg, rgba(var(--cyan-rgb), 0.115), rgba(var(--cyan-rgb), 0.045));
  --hairline: rgba(var(--cyan-rgb), 0.17);
  --hairline-hi: rgba(var(--cyan-rgb), 0.30);
  /* The card sub-labels ride up with the surface. On the lighter panel the
     site-wide --dim measured 4.06:1; this holds 5.10:1 and still reads as the
     quiet tier under the project name. */
  --dim: #8B9AB2;
}

.work-card {
  position: relative;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: clamp(24px, 3vw, 34px);
  box-shadow: var(--edge), var(--lift);
  transition: border-color 0.45s var(--ease), box-shadow 0.45s var(--ease),
              transform 0.45s var(--spring), opacity 0.4s var(--ease);
}

.work-card:hover {
  border-color: var(--hairline-hi);
  box-shadow: var(--edge-hi), var(--lift-hi);
  transform: translateY(-2px);
}

.work-feature { grid-row: span 2; }

.work-card-top {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: clamp(20px, 3vw, 30px);
}

.work-logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 46px; height: 46px;
  border-radius: 12px;
  background: linear-gradient(158deg, var(--plate-hi), var(--plate) 62%, var(--plate-lo));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7), 0 2px 8px rgba(0, 0, 0, 0.4);
  overflow: hidden;
}

/* The plate exists for marks that need a light ground to read on — Vetnova's
   deep purple disappears without it. A mark that already carries its own
   contrast does not need one, and a plate under a bright mark only dulls it.
   straight on the card, as they do in the homepage rail.

   The box keeps its size so the card headers stay on one rhythm; only the
   surface goes. */
.work-logo.bare {
  background: none;
  box-shadow: none;
}
.work-logo.bare img { max-width: 40px; max-height: 40px; }
.work-logo.bare.sm img { max-width: 34px; max-height: 34px; }

/* Constrained on both axes rather than by width alone. The partner marks are a
   mix of wide lockups and tall symbols, and a width-only rule made a tall one
   (Synergy) 44px high inside a 46px plate — clipped by the rounded corners and
   optically twice the size of its neighbours. */
.work-logo img {
  max-width: 34px; max-height: 32px;
  width: auto; height: auto;
  object-fit: contain;
  display: block;
}
.work-logo.sm { width: 40px; height: 40px; }
.work-logo.sm img { max-width: 29px; max-height: 27px; }

.work-id { min-width: 0; }
.work-id h2 {
  font-family: var(--display);
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.007em;
  line-height: 1.2;
}
.work-id p { font-size: 12.5px; color: var(--dim); margin-top: 2px; }

.work-badge {
  margin-left: auto;
  flex: none;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--on-cyan);
  background: var(--cyan);
  padding: 6px 12px;
  border-radius: var(--pill);
}

.work-headline {
  font-family: var(--display);
  font-size: clamp(22px, 2.9vw, 33px);
  line-height: 1.12;
  letter-spacing: -0.019em;
  font-weight: 600;
  text-wrap: balance;
}

.work-desc {
  color: var(--muted);
  font-size: 15px;
  margin-top: 14px;
  max-width: 52ch;
  text-wrap: pretty;
}

.work-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px 20px;
  margin-top: 26px;
}

.work-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  font-size: 14px;
  font-weight: 550;
  color: var(--cyan);
}

.work-link .arrow { transition: transform 0.4s var(--spring); }
.work-link:hover .arrow { transform: translateX(4px); }

/* The featured card's facts sit on a rule at the foot, the way the numbers
   do in the reference — but they are statements, not invented percentages. */
.work-metrics {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 18px;
  margin: auto 0 0;
  padding-top: 24px;
  border-top: 1px solid var(--hairline);
}

.work-metrics > div { min-width: 0; }
.work-metrics dt {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--dim);
  margin-bottom: 5px;
}
.work-metrics dd { margin: 0; font-size: 14px; font-weight: 550; color: var(--ink); }

.work-tall .btn, .work-side .work-link { margin-top: auto; align-self: flex-start; }
.work-tall .btn { margin-top: 26px; }

/* -------------------------------------------------- supporting panels */

.work-detail {
  max-width: 1120px;
  margin: 20px auto 0;
  padding: 0 20px;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 20px;
}

.work-panel {
  min-width: 0;
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: 26px;
  box-shadow: var(--edge), var(--lift);
}

.work-panel h2 {
  font-family: var(--display);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.005em;
  margin-bottom: 12px;
}

.work-panel p { color: var(--muted); font-size: 14px; text-wrap: pretty; }
.work-panel .work-link { margin-top: auto; padding-top: 16px; }
.work-note { margin-top: 14px; font-size: 12.5px; color: var(--dim); }

.work-stack {
  list-style: none;
  padding: 0;
  margin: 0 0 4px;
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
}

.work-stack li {
  font-size: 12.5px;
  color: var(--muted);
  border: 1px solid var(--hairline);
  border-radius: var(--pill);
  padding: 6px 11px;
}

.work-steps { list-style: none; padding: 0; margin: 0; display: grid; gap: 13px; }

.work-steps li {
  display: grid;
  grid-template-columns: max-content 1fr;   /* Proximity: number beside its text */
  align-items: baseline;
  gap: 11px;
  font-size: 13.5px;
  color: var(--muted);
}

.work-steps span {
  font-variant-numeric: tabular-nums;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--blue-soft);
}

/* ------------------------------------------------------- small cards */

.work-more {
  max-width: 1120px;
  margin: 20px auto 0;
  padding: 0 20px clamp(60px, 9vh, 104px);
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 20px;
}

.work-mini {
  min-width: 0;
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: 26px;
  box-shadow: var(--edge), var(--lift);
  transition: border-color 0.45s var(--ease), box-shadow 0.45s var(--ease),
              transform 0.45s var(--spring), opacity 0.4s var(--ease);
}

.work-mini:hover {
  border-color: var(--hairline-hi);
  box-shadow: var(--edge-hi), var(--lift-hi);
  transform: translateY(-2px);
}

.work-mini h2 {
  font-family: var(--display);
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.007em;
  margin: 16px 0 8px;
}

.work-mini p { color: var(--muted); font-size: 14px; text-wrap: pretty; }
.work-mini .work-link, .work-mini .btn { margin-top: auto; padding-top: 16px; align-self: flex-start; }
.work-mini .btn { padding-top: 0; margin-top: 22px; }

.work-soon {
  margin-top: auto;
  padding-top: 18px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dim);
}

.work-cta {
  background:
    radial-gradient(120% 140% at 0% 100%, rgba(var(--blue-rgb), 0.42), transparent 62%),
    linear-gradient(180deg, #16215A, #0B1838);
  border-color: var(--hairline-hi);
}

.work-cta h2 { margin-top: 0; font-size: 19px; }

/* Filtering hides cards; app.js sets this. Without JS every card shows. */
.work-card[hidden], .work-mini[hidden] { display: none; }

@media (max-width: 1000px) {
  .work-detail { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .work-more { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 860px) {
  .work-hero-grid { grid-template-columns: 1fr; gap: 30px; }
  .work-hero-side { gap: 16px; }
  .work-grid { grid-template-columns: 1fr; }
  .work-feature { grid-row: auto; }
  .work-metrics { grid-template-columns: 1fr; gap: 14px; }
  .work-count { margin-left: 0; width: 100%; }
}

@media (max-width: 620px) {
  .work-detail, .work-more { grid-template-columns: 1fr; }
}

/* The featured card's product shot, in a minimal browser frame. The reference
   uses a rendered device mockup; a real screenshot of the running product says
   more and weighs less. Bottom-cropped so the card stays a consistent height
   whatever the page behind it looks like. */
.work-shot {
  margin: 26px 0 0;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: #0B0B0B;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 18px 44px -20px rgba(0, 0, 0, 0.9);
}

.work-chrome {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 9px 12px;
  background: rgba(255, 255, 255, 0.045);
  border-bottom: 1px solid var(--hairline);
}

.work-chrome span {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.16);
  flex: none;
}

.work-chrome em {
  margin-left: 10px;
  font-style: normal;
  font-size: 11px;
  letter-spacing: 0.02em;
  color: var(--dim);
}

.work-shot img {
  display: block;
  width: 100%;
  height: auto;
  max-height: 280px;
  object-fit: cover;
  object-position: top center;
}

/* The metrics rule sits under the shot rather than being pushed to the bottom
   of an empty card. */
.work-feature .work-metrics { margin-top: 26px; }

@media (max-width: 860px) {
  .work-shot img { max-height: 200px; }
}

