/* Talari Technologies PLC — 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 is two colours, and they are not interchangeable. Royal Violet
   is the *material* — the washes, the lit edges, the chrome — and unlike the
   palettes before it, it is saturated enough to also be a real button fill:
   10.14:1 with white, 9.55:1 with the cream. Lemon Chiffon is the *action*,
   filling precisely one button per screen at 18.85:1 on the ground.

   Because the violet 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 —
   cream links would put an 18.85: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");
}

@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url("fonts/space-grotesk-var.woff2") format("woff2");
}

:root {
  /* Identity: Royal Violet #55219A and Lemon Chiffon #FEFACD, both taken from
     the supplied mark.

     The violet is saturated enough to carry text directly — 10.14:1 with
     white, 9.55:1 with the cream — so it is a real button fill, not just a
     wash. It is still only 1.97:1 as a fill against the page ground, which is
     why a violet pill needs its specular edge and shadow to read as an object,
     and why link text uses the lighter tint rather than the brand value. */
  --violet: #55219A;            /* Royal Violet — chrome fills and the mark  */
  --violet-hi: #6A2CBC;         /* hover                                     */
  --violet-deep: #3B1670;
  --violet-soft: #A47FCE;       /* 6.20:1 on the ground, 5.04:1 on the band  */

  --cream: #FEFACD;             /* Lemon Chiffon — the action colour         */
  --cream-hi: #FFFDE8;          /* hover — brighter, not a different hue     */
  --cream-dim: rgba(254, 250, 205, 0.16);
  --on-cream: #55219A;          /* 9.55:1 on the cream fill — the brand hue  */

  /* AirPass is unlisted; these are scoped to that one page. Green is legible
     as a separate signal again now that the brand is violet, so "live" goes
     back to a true green rather than a hue borrowed from the palette. */
  --airpass: #1A7343;
  --airpass-soft: #4AD07F;
  --airpass-fill: #1A7343;
  --airpass-fill-hi: #218A52;

  /* Surfaces: violet-black base, two elevation steps above it. The brand
     violet is deliberately NOT the card fill — at this saturation a panel in
     it would compete with every button on the page. */
  --bg: #0A0614;
  --surface: #17102A;
  --surface-hi: #241A3D;

  /* Hairlines carry the cream tint rather than plain white — on a violet
     ground a neutral-grey rule reads as a seam from a different design. */
  --hairline: rgba(254, 250, 205, 0.13);
  --hairline-hi: rgba(254, 250, 205, 0.24);

  /* Text ramp. Measured against the lightest surface any of it lands on
     (#241A3D, the dark band) rather than the page ground:
     17.93 / 9.27 / 5.77 on the ground, and 14.58 / 7.54 / 4.69 on that band. */
  --ink: #F4F1FA;
  --muted: #B5ACCB;
  --dim: #8E85A6;

  --ok: #4AD07F;
  --radius: 18px;
  --radius-sm: 12px;
  --pill: 980px;
  --display: "Space Grotesk", "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(254, 250, 205, 0.07), rgba(254, 250, 205, 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 {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0.025;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* 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(124, 79, 208, 0.10), transparent 62%);
  background-attachment: fixed;
  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(--violet-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(--cream); color: var(--on-cream); }

/* 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: #3A2E55 transparent; scrollbar-width: thin; }
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: #3A2E55;
  border: 3px solid var(--bg);
  border-radius: var(--pill);
}
::-webkit-scrollbar-thumb:hover { background: #4E3F70; }

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(--cream);
  color: var(--on-cream);
  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 cream button and over the CTA band
   alike — one ring that does not need a variant per surface. */
:focus-visible {
  outline: 2px solid var(--cream);
  outline-offset: 3px;
  border-radius: 8px;
  box-shadow: 0 0 0 5px rgba(10, 6, 20, 0.9);
}

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

/* Short travel, long ease. Movement should be noticed only in aggregate. */
.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);
}

.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; }
  .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; }
  .glow { animation: none !important; }
  .spot::after { display: none; }
  .hero > :not(.glow) { animation: none !important; clip-path: none !important; }
  .nav .logo-plate, .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(10, 6, 20, 0);
  backdrop-filter: saturate(180%) blur(0px);
  -webkit-backdrop-filter: saturate(180%) 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(10, 6, 20, 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 .logo-plate {
  transition: transform 0.5s var(--spring);
  transform-origin: left center;
}

.nav.stuck .logo-plate { 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(10, 6, 20, 0.5), transparent);
  transition: opacity 0.5s var(--spring);
}

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

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

.brand:hover { opacity: 0.82; }

/* The mark sits on the page with its own transparency — no plate, no tile, no
   fill behind it. The artwork's background is the page's background.

   The Lemon Chiffon mane is the large, bright mass and the Royal Violet body
   reads as the shadowed side of the same object rather than a missing one —
   and at 1.97:1 against the ground the violet separates rather better than the
   olive it replaces. Without a plate the mark also runs larger, which buys
   back the detail the plate's padding was costing. */
.logo-plate {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 0;
  padding: 0;
  box-shadow: none;
}

/* 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. */
.logo-plate img { display: block; width: auto; }
.nav .logo-plate img { height: 34px; }
.footer .logo-plate img { height: 30px; }

.brand-word {
  font-family: var(--display);
  font-weight: 700;
  font-size: 18px;
  letter-spacing: -0.02em;
  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;
}

.nav-links a {
  position: relative;
  color: var(--muted);
  padding-bottom: 2px;
  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: 0; right: 0; bottom: -3px;
  height: 1.5px;
  border-radius: var(--pill);
  background: var(--cream);
  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 cream there would be two loudest things on screen and
   neither would win, so the chrome takes the brand violet and the content
   keeps the action colour. Same markup — the difference is positional. */
.nav .btn-primary {
  background: var(--violet);
  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(--violet-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(124, 79, 208, 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.
   9.55:1 with the Royal Violet text it carries. */
.btn-primary {
  background: var(--cream);
  color: var(--on-cream);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.7),
    inset 0 -1px 0 rgba(124, 79, 208, 0.18),
    0 1px 2px rgba(0, 0, 0, 0.45),
    0 8px 22px -8px rgba(254, 250, 205, 0.35);
}

.btn-primary:hover {
  background: var(--cream-hi);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.8),
    inset 0 -1px 0 rgba(124, 79, 208, 0.18),
    0 2px 4px rgba(0, 0, 0, 0.45),
    0 14px 34px -10px rgba(254, 250, 205, 0.5);
}

/* Kept for places that need a filled button without claiming to be *the*
   action — the violet reads as a sibling of the cream, not a competitor. */
.btn-violet {
  background: var(--violet);
  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(124, 79, 208, 0.65);
}
.btn-violet:hover { background: var(--violet-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(124, 79, 208, 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(--cream);
  color: var(--on-cream);
  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(254, 250, 205, 0.45);
}

.btn-invert:hover {
  background: var(--cream-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(254, 250, 205, 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); }

.btn-lg { padding: 14px 28px; font-size: 16px; }

/* 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. */
.glow {
  position: absolute;
  z-index: -1;
  border-radius: 50%;
  filter: blur(120px);
  pointer-events: none;
  opacity: 0.5;
}

/* 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. */
.glow-a {
  width: 620px; height: 420px;
  top: -90px; left: 50%;
  margin-left: -310px;
  background: rgba(124, 79, 208, 0.27);
  animation: drift-a 26s var(--ease-in-out) infinite alternate;
}

.hero::before,
.page-hero::before {
  content: "";
  position: absolute;
  z-index: -1;
  width: 420px; height: 320px;
  top: -60px; left: 50%;
  margin-left: -120px;
  border-radius: 50%;
  filter: blur(130px);
  pointer-events: none;
  opacity: 0.24;
  background: rgba(254, 250, 205, 0.5);
  animation: drift-b 34s var(--ease-in-out) infinite alternate;
}

@keyframes drift-a {
  from { transform: translate3d(-60px, -10px, 0) scale(1); }
  to   { transform: translate3d(60px, 26px, 0) scale(1.12); }
}

@keyframes drift-b {
  from { transform: translate3d(40px, 14px, 0) scale(1.05); }
  to   { transform: translate3d(-70px, -18px, 0) scale(0.9); }
}

.glow-b { display: none; }

.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.04em;
  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. The stop sits late so the mid-green
   reads as the body of the word and the cream as its lit edge. */
.grad {
  background: linear-gradient(102deg, var(--violet-soft) 6%, var(--cream) 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(254, 250, 205, 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(--violet-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(--cream));
  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.035em;
  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(254, 250, 205, 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(254, 250, 205, 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.025em;
  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(254, 250, 205, 0.14); color: var(--cream); 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(124, 79, 208, 0.10), transparent 70%),
    linear-gradient(180deg, #55219A, #0E0A1A);
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
  box-shadow: var(--edge);
}

.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.035em;
  line-height: 1.1;
  color: var(--ink);
}

.stat h3 {
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--violet-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(--cream);
}

.lat-row i.slow { background: #2E2545; }
.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; }

.facts > div {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
  padding: 16px 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;
}

.facts dd { margin: 0; font-size: 14.5px; font-weight: 500; text-align: right; }

/* --------------------------------------------------- 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.038em;
  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;
}

.crumbs a { color: var(--muted); transition: color 0.2s var(--ease); }
.crumbs a:hover { color: var(--ink); }
.crumbs [aria-current="page"] { color: var(--cream); }
/* 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.028em;
  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);
  background: linear-gradient(158deg, rgba(164, 127, 206, 0.26), rgba(164, 127, 206, 0.07));
  color: var(--violet-soft);
  box-shadow: var(--edge), inset 0 0 0 1px rgba(164, 127, 206, 0.16);
}

.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(254, 250, 205, 0.22), rgba(254, 250, 205, 0.06));
  color: var(--cream);
  box-shadow: var(--edge), inset 0 0 0 1px rgba(254, 250, 205, 0.16);
}

.tile h3 {
  font-family: var(--display);
  font-size: 17.5px;
  font-weight: 600;
  letter-spacing: -0.02em;
  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.022em;
  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(254, 250, 205, 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(254, 250, 205, 0.26); }
.status.soon::before { background: var(--cream); opacity: 1; box-shadow: 0 0 0 3px rgba(254, 250, 205, 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;
  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(--cream);
  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;
  padding-right: 34px;
  transition: color 0.2s var(--ease);
}

.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { color: var(--violet-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(124, 79, 208, 0.45), transparent 65%),
    linear-gradient(180deg, #241640, #0D0818);
  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(254, 250, 205, 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. */
.glow-c {
  width: 680px; height: 400px;
  bottom: -240px; left: 50%;
  margin-left: -340px;
  background: rgba(254, 250, 205, 0.42);
  opacity: 0.7;
  animation: drift-c 30s var(--ease-in-out) infinite alternate;
}

@keyframes drift-c {
  from { transform: translate3d(-40px, 0, 0) scale(1); }
  to   { transform: translate3d(40px, -22px, 0) scale(1.14); }
}

.cta h2 {
  font-family: var(--display);
  font-size: clamp(32px, 5vw, 54px);
  line-height: 1.08;
  letter-spacing: -0.035em;
  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(254, 250, 205, 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: 12px; font-size: 14px; }

.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;
  padding: 5px 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 > :not(.glow) {
      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;
    gap: 8px 18px;
    font-size: 13.5px;
  }
  .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. */
  .glow { filter: blur(90px); }
  .hero::before, .page-hero::before { filter: blur(100px); }
}

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