/* =========================================================
   Lannex — styles
   Light theme. Type and spacing both run off a single scale;
   change the tokens below and the page re-proportions with them.
   ========================================================= */

:root{
  /* Apogee ground. One committed theme — there is no light variant and no
     prefers-color-scheme switch, so every colour below is the final value. */
  --bg:#080A19;
  --bg-2:#0B0E1E;
  --panel:#12141F;

  /* Glass. Apogee's cards are a near-black wash at 35% over whatever is behind them,
     which is what lets the background video read through a panel instead of being
     covered by it. */
  --surface:rgba(17,16,15,.35);
  --surface-2:#12141F;
  --line:rgba(255,255,255,.08);
  --line-2:rgba(255,255,255,.16);
  /* Panel edge. Far softer than the light theme's bright top lip: a .9 alpha highlight
     on near-black reads as a scratch. */
  --glass-line:rgba(255,255,255,.06);

  --text:#FFFFFF;
  --muted:rgba(255,255,255,.8);

  /* Brand accents, lifted for a near-black ground — #5B45E0 only reaches about 3:1 here.
     The purple is shared by both halves of the business; --blue is swapped per brand
     (blue for Studio, orange for Installs) further down. */
  --violet:#9280FF;
  --violet-lt:#AE9EFF;
  --blue:#7AA2FF;

  /* Apogee's primary button: near-white fill, near-black label. */
  --btn-fill:#E9E9E9;
  --btn-ink:#0A0707;

  --shadow-sm:0 1px 2px rgba(0,0,0,.4),0 2px 10px -4px rgba(0,0,0,.5);
  --shadow-md:0 2px 6px rgba(0,0,0,.45),0 14px 34px -16px rgba(0,0,0,.6);
  --shadow-lg:0 4px 10px rgba(0,0,0,.5),0 30px 64px -28px rgba(0,0,0,.7);

  /* Radii follow Apogee: 12 on controls, 20 on panels, 24 on the big cards. The old
     999px pill buttons are gone — square-ish corners are half of what makes the theme
     read as this theme. */
  --radius:12px;
  --radius-lg:24px;
  --radius-panel:20px;
  --wrap:1280px;
  --ease:cubic-bezier(.22,1,.36,1);
  /* Apogee's entrance easing. Kept separate from --ease: this one is a long, hard
     decelerate that only suits things arriving, not things reacting to a hover. */
  --ease-enter:cubic-bezier(0.16,1,0.3,1);

  --section-y:clamp(3.75rem,7vw,6rem);
  --head-gap:clamp(2rem,3.6vw,3rem);
  --gap:clamp(1.1rem,1.7vw,1.6rem);
  --card-p:clamp(1.5rem,2vw,2rem);
}


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

html{scroll-behavior:smooth;background:var(--bg)}

/* Inter, self-hosted.
   Previously this came from fonts.googleapis.com, which meant a render-blocking
   stylesheet plus DNS + TLS to a second origin (fonts.gstatic.com) before any text
   could paint in the real face. Same file, same latin subset Google was serving —
   now same-origin, preloaded, and cached for a year. One variable file covers every
   weight from 400 to 800, so the five separate weights are a single download. */
@font-face{
  font-family:'Inter';
  font-style:normal;
  font-weight:400 800;
  font-display:swap;
  src:url('fonts/inter-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
                U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
                U+2212, U+2215, U+FEFF, U+FFFD;
}

body{
  margin:0;
  color:var(--text);
  font-family:'Inter','Segoe UI Variable Display','Segoe UI',system-ui,-apple-system,sans-serif;
  font-size:18px;
  line-height:1.7;
  -webkit-font-smoothing:antialiased;
  /* text-rendering:optimizeLegibility was here. It forces kerning and ligature work on
     every text run and measurably slows first layout on long pages, for a difference
     you cannot see in Inter. */
  overflow-x:hidden;
  /* A single tint that fades out below the hero — a fixed wash haunts every section. */
  background:
    linear-gradient(180deg,rgba(91,69,224,.07),rgba(91,69,224,0) 760px),
    var(--bg);
  background-repeat:no-repeat;
}

img,svg{max-width:100%;display:block}
a{color:inherit;text-decoration:none}
h1,h2,h3,h4{margin:0;line-height:1.1;letter-spacing:-.03em;font-weight:700}
p{margin:0}
ul,ol{margin:0;padding:0;list-style:none}

:focus-visible{outline:2px solid var(--violet);outline-offset:3px;border-radius:6px}

.wrap{width:min(var(--wrap),90vw);margin-inline:auto}
/* The nav is fixed, so anchor targets need clearance or their heading lands underneath it. */
section[id]{scroll-margin-top:5.5rem}

/* ---------- Ambient ---------- */
/* A fixed, full-viewport layer. mix-blend-mode:multiply used to sit here, which forced
   the compositor to blend the whole screen every frame — the most expensive thing on the
   page during a scroll, for a texture at 2.8% opacity nobody can consciously see. The
   texture stays; the per-frame blend does not. */
.grain{
  position:fixed;inset:0;z-index:100;pointer-events:none;opacity:.028;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='.5'/%3E%3C/svg%3E");
}
/* ---------- Buttons ---------- */
.btn{
  --pad:1.05rem 1.9rem;
  display:inline-flex;align-items:center;gap:.6rem;
  padding:var(--pad);border-radius:999px;border:1px solid transparent;
  font-weight:600;font-size:1rem;line-height:1.2;letter-spacing:-.01em;cursor:pointer;
  transition:transform .35s var(--ease),background .3s,border-color .3s,box-shadow .35s,color .3s;
  ;
}
.btn span{transition:transform .35s var(--ease)}
.btn:hover span{transform:translateX(4px)}
.btn-primary{background:var(--grad);color:#fff;box-shadow:0 6px 18px -8px rgba(91,69,224,.5)}
.btn-primary:hover{box-shadow:0 12px 28px -10px rgba(91,69,224,.45);transform:translateY(-1px)}
.btn-ghost{border-color:var(--line-2);color:var(--text);background:rgba(255,255,255,.7)}
.btn-ghost:hover{border-color:var(--violet);color:var(--violet);background:#fff;box-shadow:var(--shadow-sm)}
.btn-sm{--pad:.7rem 1.3rem;font-size:.95rem}
.btn-block{width:100%;justify-content:center}

.pill{
  display:inline-block;padding:.45rem 1rem;border-radius:999px;
  border:1px solid rgba(91,69,224,.16);font-size:.75rem;font-weight:600;
  letter-spacing:.11em;text-transform:uppercase;color:var(--violet);
  background:rgba(91,69,224,.06);line-height:1.4;
}

/* ---------- Nav ---------- */
.nav{
  position:fixed;top:0;left:0;right:0;z-index:110;
  display:flex;align-items:center;justify-content:space-between;
  gap:2rem;padding:.85rem clamp(1.25rem,5vw,3rem);
  /* The one place a real blur is always worth it: a single fixed element, and the
     content sliding under it is what makes the effect visible. */
  background:rgba(247,248,252,.72);
  -webkit-backdrop-filter:blur(18px) saturate(180%);
          backdrop-filter:blur(18px) saturate(180%);
  border-bottom:1px solid var(--line);
}
/* .nav.scrolled lived here. Nothing has added that class since the scroll listener was
   removed, and its opaque background would have cancelled the nav's blur if it ever
   came back. Deleted rather than left as a trap. */
.brand{display:inline-flex;align-items:center;color:var(--text);flex:none}
/* Wordmark is set in the heading typeface, not drawn. */
.wordmark{font-size:1.65rem;font-weight:700;letter-spacing:-.02em;line-height:1;transition:color .35s}
.brand:hover .wordmark{color:var(--violet)}
.brand-lg .wordmark{font-size:2.05rem}
.nav-links{display:flex;align-items:center;gap:2.4rem}
.nav-links a:not(.btn):not(.nav-ig){font-size:1rem;color:var(--muted);position:relative;transition:color .3s;white-space:nowrap}
.nav-links a:not(.btn):not(.nav-ig)::after{
  content:"";position:absolute;left:0;bottom:-7px;height:2px;width:0;background:var(--violet);border-radius:2px;transition:width .35s var(--ease);
}
.nav-links a:not(.btn):not(.nav-ig):hover{color:var(--text)}
.nav-links a:not(.btn):not(.nav-ig):hover::after{width:100%}

/* CTA + Instagram sit closer together than the nav's own gap. */
.nav-actions{display:inline-flex;align-items:center;gap:.7rem}
.nav-ig{
  display:inline-grid;place-items:center;width:38px;height:38px;flex:none;
  border-radius:50%;border:1px solid transparent;color:var(--muted);
  transition:color .3s,background .3s,border-color .3s;
}
.nav-ig svg{width:19px;height:19px}
.nav-ig:hover{color:var(--violet);background:rgba(91,69,224,.07);border-color:rgba(91,69,224,.16)}

.menu-ig{display:inline-flex;align-items:center;gap:.6rem}
.menu-ig svg{width:.85em;height:.85em}

.burger{display:none;flex-direction:column;gap:6px;background:none;border:0;padding:12px;cursor:pointer}
.burger span{width:26px;height:2px;background:var(--text);border-radius:2px;transition:transform .35s var(--ease),opacity .25s}
.burger[aria-expanded="true"] span:first-child{transform:translateY(4px) rotate(45deg)}
.burger[aria-expanded="true"] span:last-child{transform:translateY(-4px) rotate(-45deg)}

.mobile-menu{
  position:fixed;inset:0;z-index:105;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:1.8rem;
  background:#F6F7FB;
  font-size:clamp(1.8rem,7vw,2.4rem);font-weight:700;letter-spacing:-.03em;
}
.mobile-menu[hidden]{display:none}
.mobile-menu a{opacity:0;transform:translateY(14px);animation:menuIn .5s var(--ease) forwards}
.mobile-menu a:nth-child(2){animation-delay:.06s}
.mobile-menu a:nth-child(3){animation-delay:.12s}
.mobile-menu a:nth-child(4){animation-delay:.18s}
.mobile-menu a:nth-child(5){animation-delay:.24s}
.mobile-menu a:nth-child(6){animation-delay:.3s}
@keyframes menuIn{to{opacity:1;transform:none}}

/* ---------- Reveal ----------
   Deliberately a no-op. This used to fade elements in on scroll via IntersectionObserver,
   with .reveal starting at opacity:0. When the observer failed to fire — which it did,
   repeatedly, on mobile — whole sections stayed permanently invisible. No entrance
   animation is worth a blank page, so content is now always visible. The .reveal classes
   are left in the markup harmlessly; the page's motion comes from the pinned services
   track, the parallax orbs and the marquee instead. */

/* ---------- Section furniture ---------- */
.section-head{max-width:70ch;margin-bottom:var(--head-gap)}
.section-head .pill{margin-bottom:1.35rem}
.section-title{font-size:clamp(1.85rem,2.9vw,2.5rem);line-height:1.14;letter-spacing:-.03em}
.section-sub{margin-top:1.1rem;color:var(--muted);font-size:clamp(1rem,1.1vw,1.08rem);line-height:1.65;max-width:58ch}

/* Large blurred orbs used to sit here. They were the most expensive thing on the page:
   44vw boxes with blur(120px), moved by scroll, forcing a full repaint every frame. The
   body's background gradient gives the same colour wash for free. */

/* ---------- Hero ---------- */
.hero{
  position:relative;min-height:100svh;
  display:grid;grid-template-columns:1.05fr .95fr;align-items:center;
  gap:clamp(3rem,6vw,5rem);
  padding:clamp(7.5rem,14vh,11rem) clamp(1.25rem,5vw,3rem) clamp(5rem,9vw,7.5rem);
  overflow:hidden;
}
/* 44ch was too tight for the headline: ch is relative to the ROOT font size, so this
   measure stayed ~500px while .hero-title scales to 61.6px on wide screens. The heading
   outgrew its column and "We build" ended up alone on a line. The grid column has the
   room — it was the measure holding it back. .hero-sub keeps the old 44ch explicitly so
   the body copy's line length is unchanged. */
.hero-inner{position:relative;z-index:2;max-width:min(52ch,100%)}
.hero-title{font-size:clamp(2.45rem,4.2vw,3.85rem);line-height:1.12;letter-spacing:-.035em}
.hero-title em{
  font-style:normal;
  background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent;
}

/* Rotating hero word.
   --rot-lh must equal .hero-title's line-height: the mask is one line tall and each
   word occupies exactly one line, so any mismatch clips words. Both read the same
   variable rather than repeating the number, which is what drifted last time. */
.rot{
  --rot-lh:1.12;
  display:inline-block;vertical-align:top;overflow:hidden;
  height:calc(1em * var(--rot-lh));
}
.rot-in{
  display:flex;flex-direction:column;
  animation:rot 13s cubic-bezier(.76,0,.24,1) infinite;
  will-change:transform;
}
.rot em{
  height:calc(1em * var(--rot-lh));line-height:var(--rot-lh);
  white-space:nowrap;
}
/* Five rows, so each step is 20%. The fifth repeats the first: the jump from
   -80% back to 0 lands on the same word and is invisible. */
@keyframes rot{
  0%,17%   {transform:translateY(0)}
  20%,37%  {transform:translateY(-20%)}
  40%,57%  {transform:translateY(-40%)}
  60%,77%  {transform:translateY(-60%)}
  80%,100% {transform:translateY(-80%)}
}
.hero-sub{margin-top:1.5rem;color:var(--muted);font-size:clamp(1.05rem,1.25vw,1.18rem);line-height:1.7;max-width:44ch}
.hero-cta{display:flex;flex-wrap:wrap;gap:1rem;margin-top:2.25rem}

/* ---------- Hero: laptop showing a dashboard screenshot ----------
   Static. A tiny 3D rotation and a glare gradient are what stop this reading as a flat
   vector drawing, and both are one-off composites with no per-frame cost. */
.hero-stage{position:relative;z-index:2;width:100%;display:flex;align-items:center;justify-content:center}

.laptop{
  width:100%;max-width:620px;
  transform:perspective(1600px) rotateY(-3deg) rotateX(2.2deg);
  filter:drop-shadow(0 34px 46px rgba(13,16,32,.20));
}
.laptop-lid{
  position:relative;border-radius:14px 14px 4px 4px;
  padding:11px 11px 15px;
  background:linear-gradient(165deg,#2A2D38,#171922 60%,#12141B);
}
.laptop-cam{
  position:absolute;top:5px;left:50%;transform:translateX(-50%);
  width:4px;height:4px;border-radius:50%;background:#3D414F;
}
.laptop-screen{
  position:relative;overflow:hidden;border-radius:5px;
  aspect-ratio:16/10;background:#F5F6FA;
  box-shadow:0 0 0 1px rgba(0,0,0,.5) inset;
}
/* Diagonal sheen across the panel — the main "this is a photo" cue. */
.screen-glare{
  position:absolute;inset:0;pointer-events:none;border-radius:5px;
  background:
    linear-gradient(108deg,rgba(255,255,255,.50) 0%,rgba(255,255,255,.16) 22%,rgba(255,255,255,0) 44%),
    linear-gradient(200deg,rgba(255,255,255,.22) 0%,rgba(255,255,255,0) 30%);
}
.laptop-base{
  position:relative;height:11px;margin:0 auto;width:107%;left:-3.5%;
  border-radius:0 0 13px 13px;
  background:linear-gradient(180deg,#C9CDD8 0%,#AEB3C1 55%,#8E93A2 100%);
}
.laptop-notch{
  position:absolute;top:0;left:50%;transform:translateX(-50%);
  width:86px;height:5px;border-radius:0 0 7px 7px;background:rgba(13,16,32,.22);
}

/* --- browser chrome --- */
.shot-ui{position:absolute;inset:0;display:flex;flex-direction:column;font-size:10px;background:#fff}
.shot-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:top left}
.br{flex:none;background:#E6E8EE;border-bottom:1px solid #D2D5DE}
.br-tabs{display:flex;align-items:flex-end;gap:6px;padding:5px 7px 0}
.br-dots{display:flex;gap:4px;padding:0 4px 5px 0}
.br-dots i{width:6px;height:6px;border-radius:50%;background:#C6C9D2}
.br-dots i:first-child{background:#FF5F57}
.br-dots i:nth-child(2){background:#FEBC2E}
.br-dots i:nth-child(3){background:#28C840}
.br-tab{
  display:inline-flex;align-items:center;gap:5px;max-width:60%;
  padding:4px 8px;border-radius:6px 6px 0 0;background:#fff;color:#3A3F52;
  font-size:8.5px;white-space:nowrap;overflow:hidden;
}
.br-fav{width:7px;height:7px;border-radius:2px;background:var(--grad);flex:none}
.br-tab b{margin-left:2px;color:#9AA0B2;font-weight:400}
.br-plus{color:#8A90A2;font-size:10px;padding:0 3px 4px}
.br-bar{display:flex;align-items:center;gap:5px;padding:5px 8px;background:#fff;border-top:1px solid #EDEFF4}
.br-nav{color:#A3A8B8;font-size:10px;line-height:1;width:11px;text-align:center}
.br-url{
  flex:1;display:flex;align-items:center;gap:5px;min-width:0;
  background:#F1F2F6;border-radius:20px;padding:3px 9px;
  font-size:8.5px;color:#6B7186;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.br-lock{
  width:5px;height:7px;border-radius:1.5px;background:#8B90A2;position:relative;flex:none;
}
.br-lock::after{
  content:"";position:absolute;left:1px;top:-3px;width:3px;height:4px;
  border:1.2px solid #8B90A2;border-bottom:0;border-radius:2px 2px 0 0;
}
.br-avatar{
  flex:none;width:15px;height:15px;border-radius:50%;background:var(--grad);color:#fff;
  font-size:6.5px;font-weight:700;display:grid;place-items:center;letter-spacing:.02em;
}

/* --- the app --- */
.ap{flex:1;min-height:0;display:grid;grid-template-columns:78px 1fr;background:#F6F7FB}
.ap-side{
  padding:8px 6px;border-right:1px solid #E7E9F0;background:#fff;
  display:flex;flex-direction:column;gap:1px;
}
.ap-brand{font-size:8.5px;font-weight:700;letter-spacing:-.01em;color:var(--text);padding:2px 5px 7px}
.ap-nav{font-size:7.5px;line-height:1.5;color:#6E7488;padding:3.5px 5px;border-radius:4px;white-space:nowrap}
.ap-nav.is-on{background:rgba(91,69,224,.10);color:var(--violet);font-weight:600}
.ap-main{min-width:0;min-height:0;overflow:hidden;padding:9px 10px;display:flex;flex-direction:column;gap:6px}
.ap-top{display:flex;align-items:center;justify-content:space-between;gap:6px}
.ap-h{font-size:11px;font-weight:700;letter-spacing:-.02em}
.ap-range{font-size:7px;color:#6E7488;background:#fff;border:1px solid #E4E7EF;border-radius:20px;padding:2.5px 7px;white-space:nowrap}

.ap-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:5px}
.ap-kpi{background:#fff;border:1px solid #E7E9F0;border-radius:6px;padding:5px 6px;display:grid;gap:0}
.ap-kpi span{font-size:6px;letter-spacing:.07em;text-transform:uppercase;color:#7C8298;line-height:1.6}
.ap-kpi b{font-size:11px;letter-spacing:-.025em;line-height:1.3}
.ap-kpi i{font-style:normal;font-size:6px;line-height:1.6;color:#7C8298;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ap-kpi i.up{color:#1B7A4B}
/* The conversion figure lives in the dashboard as a highlighted tile. */
.ap-kpi-hi{border-color:rgba(91,69,224,.30);background:linear-gradient(160deg,rgba(91,69,224,.09),rgba(62,109,240,.04)),#fff}
.ap-kpi-hi b{color:var(--violet)}
.ap-kpi-hi i.up{color:var(--violet)}

.ap-chart{flex:1;min-height:0;background:#fff;border:1px solid #E7E9F0;border-radius:6px;padding:6px 7px 4px;display:flex;flex-direction:column;gap:4px}
.chart-head{display:flex;align-items:center;justify-content:space-between;gap:5px}
.chart-title{font-size:6.5px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#7C8298;line-height:1.5}
.chart-legend{display:inline-flex;align-items:center;gap:4px;font-size:6.5px;color:#7C8298;line-height:1.5;white-space:nowrap}
.chart-legend i{width:9px;height:2px;border-radius:2px;background:var(--violet)}
.chart-plot{flex:1;min-height:0;display:flex;gap:4px}
.chart-y{width:13px;flex:none;display:flex;flex-direction:column;justify-content:space-between;font-size:5.5px;line-height:1;color:#8A90A4;text-align:right}
.chart-canvas{position:relative;flex:1;min-width:0}
.chart-canvas svg{width:100%;height:100%;display:block;overflow:visible}
/* Stroke widths would smear under preserveAspectRatio="none" — this keeps them even. */
.chart-grid line{stroke:#EDEFF5;stroke-width:1;vector-effect:non-scaling-stroke}
.chart-dot{position:absolute;right:0;top:19.7%;width:5px;height:5px;margin:-2.5px -2.5px 0 0;border-radius:50%;background:var(--violet);box-shadow:0 0 0 1.5px #fff}
.chart-x{display:flex;justify-content:space-between;padding-left:17px;font-size:5.5px;line-height:1;color:#8A90A4}
.chart-line,.chart-area{opacity:1}

.ap-feed{flex:none;background:#fff;border:1px solid #E7E9F0;border-radius:6px;overflow:hidden}
.ap-feed-row{display:flex;align-items:center;gap:6px;padding:4.5px 7px;border-top:1px solid #F1F2F7}
.ap-feed-row:first-child{border-top:0}
.ap-tag{flex:none;font-size:5.5px;font-weight:700;letter-spacing:.05em;color:#7C8298;background:#F1F2F7;border-radius:3px;padding:1.5px 4px}
.ap-tag-ai{color:#fff;background:var(--grad)}
.ap-feed-t{flex:1;min-width:0;font-size:7px;color:#414761;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ap-feed-m{flex:none;font-size:6px;color:#8A90A4}

.scroll-hint{
  position:absolute;bottom:2.5rem;left:50%;transform:translateX(-50%);
  display:flex;align-items:center;gap:.7rem;font-size:.75rem;letter-spacing:.18em;
  text-transform:uppercase;color:var(--muted);z-index:3;
}
.scroll-hint span{width:22px;height:34px;border:1px solid var(--line-2);border-radius:12px;position:relative;background:rgba(255,255,255,.6)}
.scroll-hint span::after{content:"";position:absolute;top:9px;left:50%;width:3px;height:7px;border-radius:2px;background:var(--violet);transform:translateX(-50%)}

/* ---------- Marquee ---------- */
.marquee{
  position:relative;z-index:2;border-block:1px solid var(--line);
  background:rgba(255,255,255,.55);padding:1.15rem 0;
  overflow:hidden;
  /* Soften both ends so words enter and leave instead of being chopped off. */
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 6%,#000 94%,transparent);
          mask-image:linear-gradient(90deg,transparent,#000 6%,#000 94%,transparent);
}
/* Scrolls again — but as a CSS animation, not the rAF loop this used to run. A single
   compositor-driven transform, so it costs nothing per scroll frame. It moves -50%, which
   is exactly one of the two identical sets in the markup, so the wrap is invisible. */
.marquee-track{
  display:flex;width:max-content;
  animation:marqueeScroll 40s linear infinite;
}
.marquee-set{
  display:flex;align-items:center;gap:1.8rem;padding-right:1.8rem;
  font-size:.78rem;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);
  white-space:nowrap;
}
.marquee-set b{color:var(--violet);font-size:.4rem;opacity:.7}
@keyframes marqueeScroll{
  from{transform:translateX(0)}
  to{transform:translateX(-50%)}
}
/* Let people stop it to read a word. */
.marquee:hover .marquee-track{animation-play-state:paused}

/* ---------- Services ----------
   This was a 560vh section whose cards were dragged sideways by scroll position. It looked
   good on desktop but meant a transform on every scroll frame, and it collapsed to a plain
   stack on mobile anyway. It is now simply a grid at every size. */
.services{position:relative;z-index:2;padding:var(--section-y) 0}
.svc-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(290px,1fr));gap:var(--gap)}
.svc{
  padding:var(--card-p);border-radius:var(--radius-lg);
  border:1px solid var(--line);background:var(--surface);box-shadow:var(--shadow-sm);
  position:relative;overflow:hidden;transition:border-color .4s,transform .5s var(--ease),box-shadow .4s;
}
.svc:hover{border-color:rgba(91,69,224,.22);box-shadow:var(--shadow-md)}
.svc-num{font-size:.78rem;letter-spacing:.22em;color:var(--violet);font-weight:700}
.svc-icon{width:52px;height:52px;margin:1.5rem 0 1.8rem;color:var(--violet);border:1px solid rgba(91,69,224,.14);border-radius:14px;display:grid;place-items:center;background:rgba(91,69,224,.05)}
.svc-icon svg{width:27px;height:27px}
.svc h3{font-size:1.4rem;line-height:1.25;margin-bottom:.85rem}
.svc p{color:var(--muted);font-size:1.02rem;line-height:1.65}
.svc ul{margin-top:1.9rem;display:grid;gap:.85rem;position:relative}
.svc li{padding-left:1.7rem;position:relative;font-size:.98rem;line-height:1.5;color:var(--text)}
.svc li::before{content:"";position:absolute;left:.2rem;top:.66em;width:5px;height:5px;border-radius:50%;background:var(--violet)}
.svc-cta{display:flex;flex-direction:column;justify-content:center;gap:1.1rem;align-items:flex-start;
  background:linear-gradient(165deg,rgba(91,69,224,.1),rgba(62,109,240,.05)),#fff;
  border-color:rgba(91,69,224,.2);
}
.svc-cta .btn{margin-top:.8rem}

/* ---------- Reusable device frames ----------
   The hero laptop's shell and browser chrome are reused for the section screenshots.
   A standalone browser window (.win) and a phone (.phone) give some variety so the page
   doesn't read as the same laptop four times. All static. */

/* Phone */
.phone{
  width:100%;max-width:250px;margin-inline:auto;
  border-radius:30px;padding:8px;
  background:linear-gradient(165deg,#2A2D38,#171922 60%,#12141B);
  box-shadow:var(--shadow-lg);
}
.phone-screen{
  position:relative;overflow:hidden;border-radius:23px;
  aspect-ratio:9/17.5;background:#fff;
}
.phone-notch{
  position:absolute;top:6px;left:50%;transform:translateX(-50%);z-index:3;
  width:56px;height:15px;border-radius:20px;background:#14161D;
}
.phone-screen .screen-glare{border-radius:23px}

/* --- Section figure wrapper --- */
.shot-fig{margin:0 0 var(--head-gap)}
.shot-fig figcaption{
  margin-top:1rem;text-align:center;font-size:.85rem;color:var(--muted);line-height:1.6;
}

/* --- AI: intake agent conversation on a phone --- */
.chat-ui{position:absolute;inset:0;display:flex;flex-direction:column;background:#F6F7FB}
.chat-top{
  flex:none;padding:26px 12px 9px;background:#fff;border-bottom:1px solid #EDEFF4;
  display:flex;align-items:center;gap:7px;
}
.chat-av{width:20px;height:20px;border-radius:50%;background:var(--grad);flex:none}
.chat-who b{display:block;font-size:9px;line-height:1.3;color:#16182A}
.chat-who span{font-size:7px;color:#1B7A4B}
.chat-body{flex:1;min-height:0;overflow:hidden;padding:9px;display:flex;flex-direction:column;justify-content:flex-end;gap:7px}
.msg{max-width:84%;font-size:8px;line-height:1.5;padding:6px 8px;border-radius:11px}
.msg-in{align-self:flex-start;background:#fff;color:#2C3145;border:1px solid #EBEDF3;border-bottom-left-radius:3px}
.msg-out{align-self:flex-end;background:var(--grad);color:#fff;border-bottom-right-radius:3px}
.msg-note{
  align-self:center;font-size:6.5px;color:#6E7488;background:#EEF0F7;
  border-radius:20px;padding:3px 8px;text-align:center;
}
.chat-in{
  flex:none;margin:0 9px 10px;display:flex;align-items:center;gap:6px;
  background:#fff;border:1px solid #E4E7EF;border-radius:20px;padding:6px 9px;
}
.chat-in span{font-size:7.5px;color:#9AA0B2}
.chat-send{margin-left:auto;width:15px;height:15px;border-radius:50%;background:var(--grad);flex:none}

/* --- IT: the service desk queue, now inside a laptop --- */
.desk-ui{position:absolute;inset:0;display:flex;flex-direction:column;background:#F6F7FB}
.desk-head{
  flex:none;display:flex;align-items:center;justify-content:space-between;gap:8px;
  padding:9px 11px;background:#fff;border-bottom:1px solid #E7E9F0;
}
.desk-h{font-size:11px;font-weight:700;letter-spacing:-.02em}
.desk-count{font-size:7px;color:#6E7488;background:#F1F2F7;border-radius:20px;padding:2.5px 7px}
.desk-list{flex:1;min-height:0;overflow:hidden;padding:9px 10px;display:flex;flex-direction:column;gap:6px}
.desk-item{background:#fff;border:1px solid #E7E9F0;border-radius:6px;padding:6px 8px}
.desk-row{display:flex;align-items:center;justify-content:space-between;gap:8px}
.desk-ref{font-size:6px;font-weight:700;letter-spacing:.08em;color:#8A90A4}
.desk-chip{font-size:6px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;padding:1.5px 5px;border-radius:20px;background:#F1F2F7;color:#7C8298}
.desk-chip-high{background:rgba(224,67,91,.13);color:#C9243C}
.desk-chip-done{background:rgba(30,150,90,.13);color:#1B7A4B}
.desk-title{margin:3px 0 2px;font-size:8.5px;font-weight:600;line-height:1.35;color:#20243A}
.desk-meta{display:flex;justify-content:space-between;gap:8px;font-size:6.5px;color:#8A90A4}

/* ---------- AI ---------- */
.ai{position:relative;z-index:2;padding:var(--section-y) 0;border-top:1px solid var(--line)}
/* Four across, matching the card grids elsewhere rather than a single tall column.
   .ai-grid and .ai-sticky lived here for the old two-column layout. */
.ai-steps{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--gap)}
.ai-step{
  padding:clamp(1.75rem,2.4vw,2.4rem);border-radius:var(--radius-lg);border:1px solid var(--line);
  background:var(--surface);box-shadow:var(--shadow-sm);
  transition:transform .5s var(--ease),border-color .4s,box-shadow .4s;
}
.ai-step:hover{transform:translateX(6px);border-color:rgba(91,69,224,.22);box-shadow:var(--shadow-md)}
.ai-step-num{font-size:.78rem;letter-spacing:.22em;color:var(--violet);font-weight:700}
.ai-step h3{font-size:1.3rem;margin:.8rem 0 .65rem}
.ai-step p{color:var(--muted);font-size:1rem;line-height:1.65}

/* ---------- IT support ---------- */
.itsupport{position:relative;z-index:2;overflow:hidden;padding:var(--section-y) 0;border-top:1px solid var(--line)}
.it-head{max-width:64ch}
.itsupport .section-title em{
  font-style:normal;background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent;
}

/* Lead feature — the service desk */
.it-desk{
  display:grid;grid-template-columns:1fr 1fr;gap:clamp(2.5rem,5vw,4.5rem);align-items:center;
  padding:clamp(2rem,4vw,3.25rem);border-radius:var(--radius-lg);
  border:1px solid rgba(91,69,224,.2);box-shadow:var(--shadow-md);
  background:linear-gradient(155deg,rgba(91,69,224,.09),rgba(62,109,240,.04)),#fff;
}
.it-desk-tag{
  display:inline-block;font-size:.72rem;font-weight:700;letter-spacing:.16em;text-transform:uppercase;
  color:var(--violet);margin-bottom:1rem;
}
.it-desk-copy h3{font-size:clamp(1.5rem,2.3vw,1.95rem);line-height:1.2;margin-bottom:1rem}
.it-desk-copy > p{color:var(--muted);line-height:1.7;max-width:46ch}
.it-desk-list{margin-top:1.9rem;display:grid;gap:1rem}
.it-desk-list li{position:relative;padding-left:1.9rem;color:var(--muted);font-size:1rem;line-height:1.6}
.it-desk-list strong{color:var(--text);font-weight:600}
.it-desk-list li::before{
  content:"";position:absolute;left:.2rem;top:.6em;width:5px;height:5px;border-radius:50%;background:var(--violet);
}

/* The service desk now renders inside a laptop — see the device frames above. */
.it-desk-visual{display:flex;align-items:center}

/* Section-closing line. Was three near-identical classes (.it-foot, .budget-foot,
   .promo-foot), one of which was centred while the others were left-aligned. */
.section-foot{
  margin-top:2.75rem;color:var(--muted);font-size:1.05rem;line-height:1.75;max-width:70ch;
}
.section-foot a{color:var(--violet);font-weight:600;border-bottom:1px solid rgba(91,69,224,.35)}
.section-foot a:hover{border-bottom-color:var(--violet)}

/* ---------- Budget ---------- */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

.budget{position:relative;z-index:2;overflow:hidden;padding:var(--section-y) 0;border-top:1px solid var(--line)}
.budget-head{max-width:64ch}
.budget .section-title em{
  font-style:normal;background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent;
}
.budget-notes{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--gap)}
.note{
  padding:clamp(1.75rem,2.4vw,2.25rem);border-radius:var(--radius-lg);border:1px solid var(--line);
  background:var(--surface);box-shadow:var(--shadow-sm);
  transition:border-color .4s,box-shadow .4s,transform .5s var(--ease);
}
.note:hover{border-color:rgba(91,69,224,.22);box-shadow:var(--shadow-md);transform:translateY(-4px)}
.note h4{font-size:1.15rem;margin-bottom:.8rem}
.note p{color:var(--muted);font-size:.98rem;line-height:1.65}

/* ---------- Contact ---------- */
.contact{position:relative;z-index:2;overflow:hidden;padding:var(--section-y) 0;border-top:1px solid var(--line)}
.contact-grid{display:grid;grid-template-columns:1fr 1fr;gap:clamp(3rem,6vw,5.5rem);align-items:start;position:relative;z-index:2}

.form{
  padding:clamp(1.9rem,3vw,2.75rem);border-radius:var(--radius-lg);border:1px solid var(--line);
  background:var(--surface);box-shadow:var(--shadow-md);display:grid;gap:1.35rem;
}
.field{position:relative}
/* Honeypot. Off-canvas rather than display:none — some bots skip hidden inputs. */
.field-trap{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
.field input,.field textarea{
  width:100%;padding:1.6rem 1.15rem .7rem;border-radius:14px;
  border:1px solid var(--line-2);background:#FAFBFE;color:var(--text);
  font:inherit;font-size:1rem;line-height:1.5;outline:none;transition:border-color .3s,box-shadow .3s,background .3s;resize:vertical;
}
.field input:focus,.field textarea:focus{border-color:var(--violet);background:#fff;box-shadow:0 0 0 3px rgba(91,69,224,.14);outline:none}
.field label{
  position:absolute;left:1.15rem;top:1.15rem;color:var(--muted);font-size:1rem;
  pointer-events:none;transition:transform .25s var(--ease),font-size .25s var(--ease),color .25s;
  transform-origin:left top;
}
.field input:focus + label,.field input:not(:placeholder-shown) + label,
.field textarea:focus + label,.field textarea:not(:placeholder-shown) + label{
  transform:translateY(-.7rem) scale(.75);color:var(--violet);
}
.field.invalid input,.field.invalid textarea{border-color:#E0435B}
.form-note{min-height:1.5rem;font-size:.95rem;line-height:1.5;color:var(--violet);text-align:center;font-weight:500}
.form-note.err{color:#C9243C}

/* ---------- Footer ---------- */
.footer{position:relative;z-index:2;border-top:1px solid var(--line-2);padding:clamp(4rem,7vw,5.5rem) 0 2.5rem;background:#fff}
.footer-grid{display:grid;grid-template-columns:1.7fr 1fr 1fr 1fr;gap:clamp(2.5rem,4vw,3.5rem)}
.footer-tag{margin-top:1.4rem;color:var(--muted);font-size:.98rem;line-height:1.7;max-width:36ch}
.footer h4{font-size:.76rem;letter-spacing:.18em;text-transform:uppercase;color:var(--muted);margin-bottom:1.4rem;font-weight:600}
.footer nav,.footer-grid > div:last-child{display:flex;flex-direction:column;align-items:flex-start;gap:.9rem;font-size:1rem}
.footer nav a:hover,.footer-grid a:hover{color:var(--violet)}
.footer-base{display:flex;justify-content:space-between;align-items:center;margin-top:4rem;padding-top:2rem;border-top:1px solid var(--line);color:var(--muted);font-size:.9rem;flex-wrap:wrap;gap:1rem}
.footer-base a{transition:color .3s}
.footer-base a:hover{color:var(--violet)}
.footer-email{font-weight:600;color:var(--text)}
.footer-email:hover{color:var(--violet)}
.to-top:hover{color:var(--violet)}

/* ============ AI promotional video ============ */
.promo{padding-block:var(--section-y)}
/* Match the IT and Budget heads — both use a pill plus a gradient em. */
.promo .section-head{max-width:64ch}
.promo .section-title em{
  font-style:normal;background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent;
}
.promo-grid{
  display:grid;grid-template-columns:repeat(3,1fr);gap:var(--gap);margin-top:var(--head-gap);
}
.promo-card{
  padding:var(--card-p);border-radius:var(--radius-lg);background:var(--surface);
  border:1px solid var(--line);box-shadow:var(--shadow-sm);
}
.promo-card h3{font-size:1.24rem;letter-spacing:-.02em;margin-bottom:.7rem}
.promo-card p{color:var(--muted);line-height:1.75}


@media (max-width:900px){
  .promo-grid{grid-template-columns:1fr}
}
/* Section screenshot figures */
.ai-shot{margin-top:var(--head-gap);margin-bottom:0}
.itsupport .it-desk-visual .laptop{max-width:none}
@media (max-width:560px){
  /* Fine detail in these shots stops being legible on a phone — keep the essentials. */
  .site-shot{display:none}
  .site-strip{grid-template-columns:1fr 1fr}
  .site-card:last-child{display:none}
  .desk-list .desk-item:last-child{display:none}
}

/* ---------- Responsive ---------- */
@media (max-width:1150px){
  .nav-links{gap:1.6rem}
  .nav-links a:not(.btn){font-size:.95rem}
}
@media (max-width:1080px){
  .hero{grid-template-columns:1fr;gap:4.5rem;min-height:auto}
  .hero-inner{max-width:min(52ch,100%)}
  .laptop{max-width:560px}
  .ai-steps{grid-template-columns:repeat(2,1fr)}
  .footer-grid{grid-template-columns:1fr 1fr}
  .budget-notes{grid-template-columns:1fr}
  .it-desk{grid-template-columns:1fr}
  .contact-grid{grid-template-columns:1fr}
}
@media (max-width:820px){
  /* Hide the text links and the CTA, but NOT the whole of .nav-links — the
     Instagram icon lives inside it, and hiding the lot left nothing to tap on a
     phone. Both hidden items are already in the burger menu; the icon wasn't. */
  .nav-links > a{display:none}
  .nav-actions .btn{display:none}
  .nav-links{margin-left:auto;gap:0}
  .nav{gap:.35rem}
  .nav-ig{width:44px;height:44px}  /* full-size touch target */
  .nav-ig svg{width:21px;height:21px}
  .burger{display:flex}
  .scroll-hint{display:none}
}
/* Small phones only. At 375px "We build <word>" clears the line by ~11px, but below
   about 360px it doesn't, and the rotator drops to its own line leaving "We build"
   stranded. 375px and up are untouched. */
@media (max-width:365px){
  .hero-title{font-size:2.05rem}
}

@media (max-width:560px){
  .ai-steps{grid-template-columns:1fr}
  body{font-size:17px}
  .wrap{width:90vw}
  .footer-grid{grid-template-columns:1fr}
  /* A phone-width screen can't carry the sidebar, the fourth tile or the activity feed. */
  .laptop{transform:none}
  .ap{grid-template-columns:1fr}
  .ap-side{display:none}
  .ap-kpis{grid-template-columns:1fr 1fr}
  .ap-kpi:nth-child(3){display:none}
  /* Hiding a tile leaves an odd count, so let the conversion figure take the full row. */
  .ap-kpi-hi{grid-column:1 / -1}
  .ap-feed{display:none}
  .ap-main{padding:8px}
}

/* ================= Glass =================
   Two ingredients, and the cheap one does most of the work:

   1. Colour behind the panels. These are plain static radial gradients painted once —
      not the blurred orbs that used to live here, which were 44vw boxes running
      blur(120px) and repainting every scroll frame. A gradient costs nothing to scroll.
   2. Translucent panels (--surface is 62% white) with a light top edge.

   backdrop-filter is the expensive part, so it is used sparingly: always on the nav,
   which is one fixed element, and on cards only from 900px up. Phones get the
   translucency and the light edge without paying for a blur per card. */
/* Sections alternate between the page ground and a slightly recessed band, each with a
   hairline above it. Every section used to carry a wash on the same ground, so the page
   read as one continuous gradient and you couldn't tell where one section ended.
   background-image (not the shorthand) so the band colour below still applies. */
.services{background-image:radial-gradient(900px 520px at 88% -5%,rgba(62,109,240,.10),transparent 62%)}
.ai{background-image:radial-gradient(880px 540px at 8% 6%,rgba(91,69,224,.11),transparent 60%)}
.promo{background-image:radial-gradient(900px 540px at 92% 4%,rgba(124,99,255,.10),transparent 60%)}
.itsupport{background-image:radial-gradient(860px 520px at 6% 8%,rgba(62,109,240,.09),transparent 60%)}
.contact{background-image:radial-gradient(820px 540px at 12% 104%,rgba(91,69,224,.10),transparent 60%)}

/* The alternation itself. */
.ai,.itsupport,.contact{background-color:var(--bg-2)}
.services,.promo,.budget{border-top:1px solid var(--line)}

.svc,.note,.ai-step,.form,.promo-card,.faq-item{border-color:var(--glass-line)}

@media (min-width:900px){
  .svc,.note,.ai-step,.form,.promo-card,.faq-item{
    -webkit-backdrop-filter:blur(14px) saturate(150%);
            backdrop-filter:blur(14px) saturate(150%);
  }
}

/* Service card headings link through to their own page. Kept visually identical to the
   plain headings they replaced — the whole card already reads as clickable. */
.svc-link{color:inherit;transition:color .3s}
.svc-link:hover{color:var(--violet)}

/* ================= Service pages ================= */
/* Clears the fixed nav — these pages have no tall hero to sit under it. */
.crumbs{padding:7.25rem 0 0;font-size:.88rem;color:var(--muted)}
.crumbs a{color:var(--muted);transition:color .3s}
.crumbs a:hover{color:var(--violet)}
.crumbs span[aria-hidden]{margin:0 .45rem;opacity:.5}

/* Service pages need their own washes — the homepage ones are keyed to section classes
   that don't exist here, so without these the glass panels sit on flat white and read
   as ordinary cards. */
.svc-hero{
  padding:2.6rem 0 var(--section-y);
  background:radial-gradient(900px 540px at 88% -12%,rgba(91,69,224,.11),transparent 62%);
}
/* Long measures are hard to read. The steps run full width on these pages, unlike the
   homepage where they sit in a narrow column. */
/* Two across on the service pages — their step copy is longer than the homepage's, so
   four columns would squeeze it into narrow ribbons. */
.page-section .ai-steps{grid-template-columns:repeat(2,1fr)}
@media (max-width:760px){
  .page-section .ai-steps{grid-template-columns:1fr}
}
.svc-hero .section-head{max-width:62ch}
.svc-hero .pill{margin-bottom:1.6rem}
.svc-hero h1{font-size:clamp(2.3rem,4.4vw,3.5rem);line-height:1.1;letter-spacing:-.035em}
.svc-hero h1 em{
  font-style:normal;background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent;
}
.svc-hero .hero-cta{margin-top:2.6rem;display:flex;gap:1rem;flex-wrap:wrap}

.page-section{padding-block:var(--section-y);position:relative;z-index:2}
.page-section.alt{background:var(--bg-2);border-block:1px solid var(--line)}
.page-section h2{
  font-size:clamp(1.75rem,2.7vw,2.4rem);line-height:1.15;letter-spacing:-.03em;
  margin-bottom:var(--head-gap);max-width:24ch;
}

/* Reuses .promo-card for the "what you get" tiles. */
.faq-list{display:grid;gap:1rem;max-width:74ch}
.faq-item{
  padding:clamp(1.5rem,2.2vw,2rem);border:1px solid var(--line);border-radius:var(--radius);
  background:var(--surface);box-shadow:var(--shadow-sm);
}
.faq-item h3{font-size:1.12rem;letter-spacing:-.015em;margin-bottom:.6rem}
.faq-item p{color:var(--muted);line-height:1.75}
.faq-item p + p{margin-top:.8rem}

/* Long-form text pages (privacy, terms). Measure kept near 70 characters — the reason
   these pages are usually unreadable is a 1200px line length. */
.prose{max-width:70ch}
.prose h2{
  font-size:clamp(1.25rem,1.8vw,1.5rem);line-height:1.25;letter-spacing:-.02em;
  margin:2.75rem 0 .9rem;
}
.prose h2:first-child{margin-top:0}
.prose p{color:var(--muted);line-height:1.8;margin-bottom:1.1rem}
.prose strong{color:var(--text);font-weight:600}
.prose a{color:var(--violet);font-weight:600;border-bottom:1px solid rgba(91,69,224,.35)}
.prose a:hover{border-bottom-color:var(--violet)}

/* Keyboard users shouldn't have to tab through the whole nav to reach the content. */
.skip-link{
  position:absolute;left:1rem;top:-4rem;z-index:200;
  padding:.7rem 1.15rem;border-radius:0 0 10px 10px;
  background:var(--violet);color:#fff;font-weight:600;font-size:.95rem;
  transition:top .2s var(--ease);
}
.skip-link:focus{top:0}

.cta-band{padding-block:var(--section-y);text-align:center}
.cta-band h2{margin:0 auto 1.2rem;max-width:20ch}
.cta-band p{color:var(--muted);max-width:52ch;margin:0 auto 2.4rem;line-height:1.75}
.cta-band .hero-cta{display:flex;gap:1rem;justify-content:center;flex-wrap:wrap}

/* Other services, linked from the foot of each page. */
.also{display:flex;flex-wrap:wrap;gap:.7rem;justify-content:center;margin-top:3rem}
.also a{
  font-size:.92rem;font-weight:600;color:var(--muted);
  border:1px solid var(--line-2);border-radius:100px;padding:.6rem 1.1rem;background:var(--surface);
  transition:color .3s,border-color .3s;
}
.also a:hover{color:var(--violet);border-color:rgba(91,69,224,.35)}

/* ================= Scroll-driven parallax =================
   Driven by scroll-driven CSS animations, not JavaScript. There is still no scroll
   listener and no rAF loop on this site: the browser runs these on the compositor,
   off the main thread, which is why the parallax can come back without the jank that
   got the old version removed.

   Two deliberate constraints:

   1. Everything is gated behind @supports. A browser without scroll-driven animations
      simply gets the static page — no half-applied effects.
   2. Every effect is a transform. Nothing here animates opacity, so no element can
      ever be left invisible. That is what made the old JS reveal system dangerous:
      one missing script tag and the whole page was blank.

   Parallax is the *difference* in speed between layers, so the mockups travel further
   than the headings they sit beside. Magnitudes are kept under ~3.5rem so nothing
   drifts into the section above or below. */
@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference){

    /* Hero: the laptop sinks and shrinks slightly as the hero scrolls away. */
    .hero-stage{
      animation:heroDrift linear both;
      animation-timeline:scroll(root);
      animation-range:0 90vh;
    }
    @keyframes heroDrift{
      to{transform:translateY(-4rem) scale(.96)}
    }

    /* Section mockups: the fastest-moving layer. */
    .shot-fig,.it-desk-visual{
      animation:shotDrift linear both;
      animation-timeline:view();
      animation-range:cover 0% cover 100%;
    }
    @keyframes shotDrift{
      from{transform:translateY(3.4rem)}
      to{transform:translateY(-3.4rem)}
    }

    /* Headings move at roughly a third of that. The gap is the effect. */
    .section-head{
      animation:headDrift linear both;
      animation-timeline:view();
      animation-range:cover 0% cover 100%;
    }
    @keyframes headDrift{
      from{transform:translateY(1.2rem)}
      to{transform:translateY(-1.2rem)}
    }

    /* Card grids drift least, so they read as the layer nearest the page. */
    .svc,.promo-card,.note,.ai-step{
      animation:cardDrift linear both;
      animation-timeline:view();
      animation-range:cover 0% cover 100%;
    }
    @keyframes cardDrift{
      from{transform:translateY(.85rem)}
      to{transform:translateY(-.85rem)}
    }
  }
}

/* ================= Section deck =================
   The five service sections as a carousel of glass panels — on desktop only.

   Below 821px the deck degrades to a plain vertical stack: same sections, same order,
   no sliding. That breakpoint is deliberately the same one the full nav uses, so the
   carousel appears exactly when the desktop navigation does. A carousel is a poor fit for
   a phone here because the panels run from 950px to 2,800px tall — sliding sideways
   through content that long means a lot of scrolling with the controls out of reach.

   Slides are never removed from the DOM; the track is moved with a transform. That keeps
   every section's text in the document for search engines, and is what lets the same
   markup fall back to a stack for phones and for anyone with JavaScript off. */
.deck{position:relative;z-index:2;padding:var(--section-y) 0}

.deck-bar{
  display:flex;align-items:center;justify-content:space-between;gap:1.5rem;
  margin-bottom:1.5rem;flex-wrap:wrap;
}
.deck-tabs{display:flex;gap:.5rem;flex-wrap:wrap}
.deck-tab{
  font:inherit;font-size:.92rem;font-weight:600;letter-spacing:-.01em;cursor:pointer;
  padding:.62rem 1.15rem;border-radius:100px;
  border:1px solid var(--line-2);background:var(--surface);color:var(--muted);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.7);
  transition:color .3s,border-color .3s,background .3s,box-shadow .3s;
}
.deck-tab:hover{color:var(--text);border-color:rgba(91,69,224,.32)}
.deck-tab.is-on{
  color:#fff;border-color:transparent;background:var(--grad);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.4),0 6px 18px -8px rgba(91,69,224,.5);
}
.deck-nav{display:flex;gap:.5rem;flex:none}
.deck-arrow{
  width:42px;height:42px;flex:none;display:grid;place-items:center;cursor:pointer;
  border-radius:50%;border:1px solid var(--line-2);background:var(--surface);color:var(--muted);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.7);
  transition:color .3s,border-color .3s,transform .3s var(--ease);
}
.deck-arrow:hover{color:var(--violet);border-color:rgba(91,69,224,.32)}
.deck-arrow:active{transform:scale(.94)}
.deck-arrow svg{width:19px;height:19px}

.deck-stage{
  position:relative;overflow:hidden;border-radius:var(--radius-lg);
  transition:height .5s var(--ease);
}
.deck-track{
  display:flex;align-items:flex-start;
  transition:transform .55s var(--ease);
  will-change:transform;
}

/* Each section becomes a glass panel. Its own section padding, band colour and top rule
   all belonged to the stacked layout and are cancelled here. */
.deck .slide{
  flex:0 0 100%;width:100%;min-width:100%;
  padding:clamp(1.75rem,3vw,2.75rem);
  border:1px solid var(--glass-line);border-radius:var(--radius-lg);
  background-color:var(--surface);
  background-image:
    linear-gradient(135deg,
      rgba(255,255,255,.58) 0%,
      rgba(255,255,255,.20) 16%,
      rgba(255,255,255,.04) 34%,
      rgba(255,255,255,0) 52%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.9),
    inset 0 -1px 0 rgba(16,19,43,.05),
    var(--shadow-md);
  border-top-width:1px;
}
@media (min-width:900px){
  .deck .slide{
    -webkit-backdrop-filter:blur(16px) saturate(175%) brightness(1.04);
            backdrop-filter:blur(16px) saturate(175%) brightness(1.04);
  }
}
.deck .slide .wrap{width:100%}
.deck .slide .section-head{margin-bottom:1.5rem}
.deck .slide.ai,
.deck .slide.itsupport{background-color:var(--surface)}
.deck .slide.services,
.deck .slide.promo,
.deck .slide.budget{border-top:1px solid var(--glass-line)}

/* Phones, and anyone without JavaScript: a plain stack of the same panels. */
@media (max-width:820px){
  .deck-bar{display:none}
  .deck-stage{height:auto!important;overflow:visible;border-radius:0}
  .deck-track{display:block}
  .deck-track{transform:none!important}
  .deck .slide{width:auto;min-width:0;margin-bottom:var(--gap);padding:clamp(1.35rem,4vw,1.9rem)}
}
.no-js .deck-bar{display:none}
.no-js .deck-stage{height:auto!important;overflow:visible}
.no-js .deck-track{display:block;transform:none!important}
.no-js .deck .slide{width:auto;min-width:0;margin-bottom:var(--gap)}

/* ================= Shiny glass =================
   Translucency alone reads as frosted, not polished. Three things make a pane look shiny:

   1. A bright top lip. Real glass catches light along its top edge — an inset 1px
      highlight does more for the effect than any amount of blur.
   2. A specular sweep falling across the top-left, where the light is coming from.
   3. A faint dark line along the bottom edge, so the panel has thickness rather than
      floating as a flat rectangle.

   Applied as background-image rather than a ::before overlay: several of these cards set
   `background` as a shorthand later in the file, which would wipe an image but not a
   pseudo-element — and a pseudo-element would need z-index juggling against .svc-num and
   the list bullets. This block sits after every card rule so it always wins.

   Deliberately sits BEFORE the dark theme block, which tones the highlights down: at
   full strength on a near-black panel they look like scratches, not shine. */
.svc,.note,.ai-step,.form,.promo-card,.faq-item,.also a,.svc-cta,.it-desk{
  background-image:
    linear-gradient(135deg,
      rgba(255,255,255,.58) 0%,
      rgba(255,255,255,.20) 16%,
      rgba(255,255,255,.04) 34%,
      rgba(255,255,255,0) 52%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.9),
    inset 0 -1px 0 rgba(16,19,43,.05),
    var(--shadow-sm);
}
/* The feature panels carry their own gradient, so keep the sheen but let their tint show. */
.svc-cta,.it-desk{
  background-image:
    linear-gradient(135deg,rgba(255,255,255,.5) 0%,rgba(255,255,255,.14) 18%,rgba(255,255,255,0) 46%),
    linear-gradient(165deg,rgba(91,69,224,.1),rgba(62,109,240,.05));
}
.form{
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.9),
    inset 0 -1px 0 rgba(16,19,43,.05),
    var(--shadow-md);
}
/* A touch more gloss than the frosted version: brightness lifts the panel off the wash. */
@media (min-width:900px){
  .svc,.note,.ai-step,.form,.promo-card,.faq-item{
    -webkit-backdrop-filter:blur(16px) saturate(175%) brightness(1.04);
            backdrop-filter:blur(16px) saturate(175%) brightness(1.04);
  }
}
/* On hover the highlight strengthens, as though the pane has tilted into the light. */
.svc:hover,.note:hover,.ai-step:hover,.promo-card:hover,.faq-item:hover{
  background-image:
    linear-gradient(135deg,
      rgba(255,255,255,.72) 0%,
      rgba(255,255,255,.28) 16%,
      rgba(255,255,255,.06) 36%,
      rgba(255,255,255,0) 54%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,1),
    inset 0 -1px 0 rgba(16,19,43,.06),
    var(--shadow-md);
}
/* Buttons and pills get the same lip so the whole page reads as one material. */
.btn-primary{
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.4),
    0 6px 18px -8px rgba(91,69,224,.5);
}
.btn-primary:hover{
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.5),
    0 12px 28px -10px rgba(91,69,224,.45);
}
.btn-ghost,.pill{box-shadow:inset 0 1px 0 rgba(255,255,255,.75)}
/* The nav is a glass bar, so it gets a lit bottom edge as well as its blur. */

/* ================= Dark surfaces =================
   The site is dark-only: one committed theme, no toggle, no prefers-color-scheme switch.
   These rules are the handful of places that need a literal colour rather than a token.

   Deliberately NOT inverted: the device mockups (hero laptop, AI phone, IT desk). Those
   are screenshots of light-themed software, and a screenshot doesn't repaint itself
   because the page around it is dark. That is why .shot-ui and friends below re-declare
   the light tokens inside their own subtree. */

/* The device mockups are screenshots of light-themed software, so they keep their light
   palette in both themes. They set a white background but never set a text colour, which
   meant they inherited the page's — turning their text white-on-white in dark mode.
   Re-declaring the tokens inside the subtree fixes every descendant at once, including
   anything using var(--violet) or var(--grad). */
.shot-ui,.chat-ui,.desk-ui{
  --text:#16182A;
  --muted:#6E7488;
  --violet:#5B45E0;
  --violet-lt:#7A63F5;
  --blue:#3E6DF0;
  color:#16182A;
}
.ap-brand{color:#16182A}

body{
  background:
    linear-gradient(180deg,rgba(146,128,255,.14),rgba(146,128,255,0) 760px),
    var(--bg);
}
.nav{background:rgba(11,13,22,.72)}
.marquee{background:rgba(255,255,255,.03)}
.footer{background:#0E111C}
.btn-ghost{background:rgba(255,255,255,.06)}
.btn-ghost:hover{background:rgba(255,255,255,.11);border-color:var(--violet);color:var(--violet-lt)}
.pill{background:rgba(146,128,255,.14);border-color:rgba(146,128,255,.30)}
.scroll-hint span{background:rgba(255,255,255,.06)}
.field input,
.field textarea{background:rgba(255,255,255,.05)}
.field input:focus,
.field textarea:focus{background:rgba(255,255,255,.09)}
.it-desk,
.svc-cta{
  background:linear-gradient(155deg,rgba(146,128,255,.16),rgba(122,162,255,.07)),var(--panel);
  border-color:rgba(146,128,255,.26);
}
/* White noise over a dark page reads as dust, so dial it back. */
.grain{opacity:.016}

/* Shine, toned for a dark panel. The light-theme values are roughly five times too strong
   here — a .9 alpha top lip on near-black reads as a scratch rather than a highlight. */
.svc,
.note,
.ai-step,
.form,
.promo-card,
.faq-item,
.also a{
  background-image:
    linear-gradient(135deg,
      rgba(255,255,255,.10) 0%,
      rgba(255,255,255,.04) 18%,
      rgba(255,255,255,0) 44%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.14),
    inset 0 -1px 0 rgba(0,0,0,.35),
    var(--shadow-sm);
}
.svc:hover,
.note:hover,
.ai-step:hover,
.promo-card:hover,
.faq-item:hover{
  background-image:
    linear-gradient(135deg,
      rgba(255,255,255,.16) 0%,
      rgba(255,255,255,.06) 18%,
      rgba(255,255,255,0) 46%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.22),
    inset 0 -1px 0 rgba(0,0,0,.4),
    var(--shadow-md);
}
.svc-cta,
.it-desk{
  background-image:
    linear-gradient(135deg,rgba(255,255,255,.10) 0%,rgba(255,255,255,.03) 20%,rgba(255,255,255,0) 48%),
    linear-gradient(155deg,rgba(146,128,255,.16),rgba(122,162,255,.07));
}
.btn-ghost,
.pill{box-shadow:inset 0 1px 0 rgba(255,255,255,.12)}
.nav{box-shadow:inset 0 -1px 0 rgba(255,255,255,.07)}

/* The section washes are near-invisible at light-theme alpha on a dark ground. */
.services{background:radial-gradient(900px 520px at 88% -5%,rgba(122,162,255,.16),transparent 62%)}
.ai{background:radial-gradient(880px 540px at 8% 6%,rgba(146,128,255,.17),transparent 60%)}
.promo{background:radial-gradient(900px 540px at 92% 4%,rgba(146,128,255,.16),transparent 60%)}
.itsupport{background:radial-gradient(860px 520px at 6% 8%,rgba(122,162,255,.15),transparent 60%)}
.contact{background:radial-gradient(820px 540px at 12% 104%,rgba(146,128,255,.16),transparent 60%)}
.svc-hero{background:radial-gradient(900px 540px at 88% -12%,rgba(146,128,255,.17),transparent 62%)}
.page-section{background:radial-gradient(820px 500px at 6% 0%,rgba(122,162,255,.12),transparent 60%)}
.page-section.alt{
  background:radial-gradient(860px 520px at 92% 4%,rgba(146,128,255,.16),transparent 60%),var(--bg-2);
}

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  /* Stop the marquee outright rather than letting the blanket rule below race it to
     the end of its keyframes. */
  .marquee-track{animation:none}
  *,*::before,*::after{animation-duration:.001ms !important;animation-iteration-count:1 !important;transition-duration:.001ms !important}
  .laptop{transform:none}
}

/* Sits under the submit button — the point where someone actually hands over their data. */
.form-privacy{
  margin-top:.35rem;font-size:.82rem;line-height:1.65;color:var(--muted);text-align:center;
}
.form-privacy a{color:var(--violet);font-weight:600;border-bottom:1px solid rgba(91,69,224,.3)}
.form-privacy a:hover{border-bottom-color:var(--violet)}

/* ================= Brand accents =================
   Lannex is one company with two halves, so they share everything structural — type,
   spacing, glass, dark theme — and differ only in an accent colour:

     studio    purple + blue    (the original palette)
     installs  purple + orange

   Two tokens carry the whole difference. --blue is the accent (it is used in exactly two
   places: the wordmark's E and the gradient) and --grad is re-declared per brand so its
   var() references resolve against the branded element rather than :root. Without that
   re-declaration the homepage's two panels would both inherit the root gradient, because
   a custom property that references another one is substituted where it is DECLARED, not
   where it is used.

   Scoped to [data-brand] rather than :root so the homepage can carry both palettes at
   once — one per panel — while /studio and /installs set it on <html>.

   TWO oranges, not one, and the reason is contrast:

     --blue           #B85C00   the working accent. It carries the gradient, so white
                                button labels sit on it: 4.60:1, AA. It is deliberately
                                NOT used for small text anywhere — see the note below.
     --accent-bright  #DB6A00   the wordmark only. A logotype is exempt from the contrast
                                minimum, so the mark can be the orange people picture
                                when they say orange (3.46:1, still over the 3:1 that
                                large text needs).

   Both sit at hue 29-30°, which is the middle of orange. The first attempt used #C2410C
   at hue 19° — technically an orange, read by everyone as red. Depth was never the
   problem; hue was. Do not brighten --blue to close the gap: #F97316 reaches only 2.8:1
   against white and would fail every button label on the page.

   --accent-bright is deliberately left UNDEFINED for studio, so the wordmark rule's
   var(--accent-bright,var(--blue)) fallback keeps the blue half working unchanged.

   NEITHER orange is used for small text. Labels and inline highlights stay on --violet,
   which is shared by both halves and clears 6:1 everywhere. That is not timidity: every
   panel here is a translucent surface over a radial wash over the body gradient, so the
   colour small text actually sits on only exists after compositing and cannot be checked
   from a stylesheet. Keeping the accent to the gradient, the wordmark and decorative
   marks means every contrast claim on this page is against a solid, knowable colour. */
[data-brand="studio"]{--blue:#7AA2FF}
[data-brand="installs"]{--blue:#E8770A;--accent-bright:#FB8500}
[data-brand]{--grad:linear-gradient(120deg,var(--violet),var(--blue))}

/* Dark theme lifts both accents off the near-black ground. These need the extra
   attribute to outrank :root, which sets --blue for the whole page. */



/* ---------- Wordmark lockup ----------
   LANNEX, then the division in joined handwriting.

   Dancing Script, self-hosted and subset to lowercase a-z — 6 KB for the whole face,
   because it only ever has to set one word. See public/fonts/README.txt before changing
   what it's used for: there are no capitals, digits or punctuation in the file, and
   anything outside a-z silently falls through to the next font in the stack.

   The fallbacks are real joined scripts too (Segoe Script ships on Windows, Snell
   Roundhand on macOS), so a failed font load degrades to the same idea rather than to
   an upright sans. */
@font-face{
  font-family:'Lannex Script';
  font-style:normal;
  font-weight:600;
  font-display:swap;
  src:url('fonts/script-latin.woff2') format('woff2');
  unicode-range:U+0061-007A;
}
.lockup{
  display:flex;align-items:baseline;gap:.34rem;line-height:1;
  margin:0 0 1.2rem;
}
.lockup .wordmark{font-size:clamp(1.7rem,2.5vw,2.15rem)}
.submark{
  font-family:'Lannex Script','Segoe Script','Snell Roundhand','Brush Script MT',cursive;
  /* A script face carries its own slant; asking for italic on top would make the browser
     synthesise a second one and shear the letterforms. */
  font-style:normal;font-weight:600;text-transform:lowercase;
  /* Set larger than the wordmark's point size on purpose: Dancing Script's x-height is
     roughly half its em, so matching the numbers would leave the word looking half-size. */
  font-size:clamp(1.55rem,2.25vw,1.95rem);letter-spacing:0;line-height:1;
  color:var(--accent-bright,var(--blue));
}
.lockup-lg .wordmark{font-size:clamp(2rem,3.1vw,2.8rem)}
.lockup-lg .submark{font-size:clamp(1.85rem,2.85vw,2.55rem)}
/* The E of the wordmark carries the same brand orange as the division word. */
.wordmark-e{color:var(--accent-bright,var(--blue))}
.svc-hero .lockup{margin-bottom:1.7rem}
.hero-inner .lockup{margin-bottom:1.9rem}

/* Marks the current page in the nav. Without it the two halves look identical from the
   bar and you cannot tell which one you are standing in. */
.nav-links a[aria-current="page"]{color:var(--text)}
.nav-links a[aria-current="page"]::after{width:100%}

/* ================= Homepage fork =================
   The homepage is a choice between the two halves and nothing else. Everything that used
   to be here now lives on /studio. */
.fork-head{
  position:relative;z-index:2;
  padding:clamp(8rem,15vh,10.5rem) 0 clamp(1.25rem,2.5vw,2rem);
}
.fork-h1{
  margin-top:1.35rem;max-width:19ch;
  font-size:clamp(2.15rem,4vw,3.4rem);line-height:1.1;letter-spacing:-.035em;
}
.fork-lede{
  margin-top:1.35rem;color:var(--muted);max-width:62ch;
  font-size:clamp(1.02rem,1.15vw,1.15rem);line-height:1.7;
}

.fork{position:relative;z-index:2;padding:clamp(1.25rem,2.5vw,2rem) 0}
.fork:last-of-type{padding-bottom:clamp(3rem,6vw,5rem)}

.fork-panel{
  display:grid;grid-template-columns:1.02fr .98fr;
  gap:clamp(2rem,4vw,3.5rem);align-items:center;
  padding:clamp(1.75rem,3.4vw,3rem);
  border:1px solid var(--glass-line);border-radius:var(--radius-lg);
  background-color:var(--surface);
  background-image:
    linear-gradient(135deg,
      rgba(255,255,255,.58) 0%,
      rgba(255,255,255,.20) 16%,
      rgba(255,255,255,.04) 34%,
      rgba(255,255,255,0) 52%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.9),
    inset 0 -1px 0 rgba(16,19,43,.05),
    var(--shadow-lg);
}
@media (min-width:900px){
  .fork-panel{
    -webkit-backdrop-filter:blur(16px) saturate(175%) brightness(1.04);
            backdrop-filter:blur(16px) saturate(175%) brightness(1.04);
  }
}
.fork-copy .pill{margin-bottom:1.35rem}
.fork-title{
  font-size:clamp(1.65rem,2.5vw,2.15rem);line-height:1.16;letter-spacing:-.03em;max-width:22ch;
}
.fork-sub{margin-top:1.1rem;color:var(--muted);font-size:1.02rem;line-height:1.7;max-width:52ch}

.fork-list{display:flex;flex-wrap:wrap;gap:.5rem;margin-top:1.65rem}
.fork-list li{
  font-size:.88rem;font-weight:600;color:var(--muted);
  padding:.48rem 1rem;border-radius:100px;
  border:1px solid var(--line-2);background:var(--surface);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.7);
}
/* The Installs panel carries no mockup, so its copy runs the full width of the tile
   rather than leaving an empty grid column beside it. A class, not :has(), because the
   layout should not depend on what happens to be inside the panel this week. */
.fork-panel.is-solo{grid-template-columns:1fr}
.fork-panel.is-solo .fork-title{max-width:30ch;font-size:clamp(1.75rem,2.8vw,2.4rem)}
.fork-panel.is-solo .fork-sub{max-width:66ch}
.fork-btn{margin-top:2rem}
.fork-visual{display:flex;align-items:center;justify-content:center}

/* ---------- Mini dashboard (homepage, Studio panel) ----------
   Sits inside the shared .laptop frame. .shot-ui already pins the light-theme tokens for
   this subtree, so it stays a screenshot of light software when the page goes dark. */
.mini{flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column;gap:7px;padding:8px 10px;background:#fff}
.mini-row{display:grid;grid-template-columns:repeat(3,1fr);gap:6px}
.mini-kpi{
  display:flex;flex-direction:column;gap:2px;padding:6px 7px;
  border-radius:5px;background:#F5F6FA;border:1px solid #E7E9F1;
  font-size:7px;letter-spacing:.05em;text-transform:uppercase;color:#6E7488;
}
.mini-kpi b{font-size:12px;letter-spacing:-.02em;color:#16182A;text-transform:none}
.mini-chart{
  flex:1;min-height:34px;padding:7px;border-radius:5px;
  border:1px solid #E7E9F1;background:#FBFCFE;color:#5B45E0;
}
.mini-chart svg{width:100%;height:100%}
.mini-feed{display:flex;align-items:center;gap:6px}
.mini-tag{
  font-size:7px;font-weight:700;letter-spacing:.05em;padding:2px 5px;
  border-radius:4px;background:rgba(91,69,224,.11);color:#5B45E0;
}
.mini-tag-2{background:#F0F1F6;color:#6E7488}
.mini-line{flex:1;height:5px;border-radius:3px;background:#EBEDF3}
.mini-line.short{max-width:56%}

/* ================= Installs page =================
   The washes elsewhere in this file are keyed to purple and blue rgba values written out
   longhand, so the branded page needs its own set or it reads as the studio page wearing
   an orange hat. */
[data-brand="installs"] .page-section.alt{
  background:
    radial-gradient(880px 520px at 92% 4%,rgba(91,69,224,.10),transparent 60%),
    var(--bg-2);
  border-block:1px solid var(--line);
}
[data-brand="installs"] .contact{
  background-image:radial-gradient(840px 540px at 12% 104%,rgba(184,92,0,.10),transparent 60%);
}
[data-brand="installs"] .it-desk{
  border-color:rgba(184,92,0,.22);
  background-image:
    linear-gradient(135deg,rgba(255,255,255,.5) 0%,rgba(255,255,255,.14) 18%,rgba(255,255,255,0) 46%),
    linear-gradient(165deg,rgba(184,92,0,.1),rgba(91,69,224,.05));
}
[data-brand="installs"] .it-desk-list li::before{background:var(--blue)}

/* ---------- Cable grade comparison ----------
   Scrolls inside its own container. Five columns of prose cannot be made to fit a phone,
   and letting the table size the page made the whole document scroll sideways. */
.spec-lede{max-width:66ch;margin-bottom:var(--head-gap)}
.spec-scroll{
  overflow-x:auto;-webkit-overflow-scrolling:touch;
  border:1px solid var(--glass-line);border-radius:var(--radius);
  background:var(--surface);box-shadow:var(--shadow-sm);
}
.spec{width:100%;min-width:700px;border-collapse:collapse;font-size:.95rem}
.spec th,.spec td{
  padding:.95rem 1.15rem;text-align:left;vertical-align:top;
  border-bottom:1px solid var(--line);
}
.spec thead th{
  font-size:.72rem;font-weight:600;letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted);white-space:nowrap;
}
.spec tbody th{font-size:1.02rem;font-weight:700;white-space:nowrap}
.spec tbody td{color:var(--muted);line-height:1.6}
.spec tbody td:nth-child(2),
.spec tbody td:nth-child(3),
.spec tbody td:nth-child(4){white-space:nowrap;color:var(--text);font-weight:600;font-size:.9rem}
.spec tbody tr:last-child th,.spec tbody tr:last-child td{border-bottom:0}
.spec-hi th{color:var(--violet)}
/* Two across, not three: four cards in a three-column grid leaves one stranded. */
.spec-notes{margin-top:var(--gap);grid-template-columns:repeat(2,1fr)}
.spec-after{margin-top:1.15rem;color:var(--muted);line-height:1.75;max-width:46ch}

/* ---------- Test results card ---------- */
.cert{
  width:100%;padding:clamp(1.2rem,2vw,1.7rem);
  border:1px solid var(--glass-line);border-radius:var(--radius);
  background:var(--surface);box-shadow:var(--shadow-md);
}
.cert-head{
  display:flex;justify-content:space-between;align-items:baseline;
  gap:1rem;flex-wrap:wrap;margin-bottom:1.15rem;
}
.cert-title{font-weight:700;letter-spacing:-.02em}
.cert-count{font-size:.82rem;color:var(--muted)}
.cert-rows{display:grid;gap:.6rem}
.cert-row{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:.8rem}
.cert-id{font-size:.8rem;font-weight:600;color:var(--muted);font-variant-numeric:tabular-nums}
.cert-bar{display:block;height:8px;border-radius:5px;background:var(--line);overflow:hidden}
.cert-bar i{display:block;height:100%;border-radius:5px;background:var(--grad)}
.cert-verdict{
  font-size:.68rem;font-weight:700;letter-spacing:.1em;
  padding:.24rem .5rem;border-radius:5px;
}
.cert-foot{margin-top:1.15rem;font-size:.82rem;color:var(--muted);line-height:1.6}

/* ---------- Select field ----------
   A select always has a value, so its label can never sit at rest over the control the
   way the text inputs floating labels do — it starts raised and stays there. */
.field select{
  width:100%;padding:1.6rem 2.6rem .7rem 1.15rem;border-radius:14px;
  border:1px solid var(--line-2);background-color:#FAFBFE;color:var(--text);
  font:inherit;font-size:1rem;line-height:1.5;outline:none;cursor:pointer;
  -webkit-appearance:none;appearance:none;
  transition:border-color .3s,box-shadow .3s,background-color .3s;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='9' viewBox='0 0 14 9' fill='none'%3E%3Cpath d='M1 1.5 7 7.5l6-6' stroke='%2359607A' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat;
  background-position:right 1.15rem center;
}
.field select:focus{border-color:var(--violet);background-color:#fff;box-shadow:0 0 0 3px rgba(91,69,224,.14)}
.label-static{
  position:absolute;left:1.15rem;top:1.15rem;pointer-events:none;
  font-size:1rem;color:var(--muted);transform-origin:left top;
  transform:translateY(-.7rem) scale(.75);
}

/* ---------- Responsive: the new blocks ---------- */
@media (max-width:900px){
  /* Re-flattens spec-notes: its two-column rule sits after the shared .promo-grid
     mobile rule, so it would otherwise win at phone width. */
  .spec-notes{grid-template-columns:1fr}
}
@media (max-width:980px){
  .fork-panel{grid-template-columns:1fr;gap:2.25rem}
  .fork-title,.fork-sub{max-width:none}
  .fork-visual .laptop{max-width:460px}
}
@media (max-width:560px){
  .fork-head{padding-top:7.5rem}
  .cert-row{gap:.6rem}
  .cert-id{font-size:.74rem}
}

/* ---------- Dark theme: the new blocks ----------
   Same reasoning as the cards above — the light-theme highlights are roughly five times
   too strong on a near-black panel and read as scratches rather than shine. */
.fork-panel,
.cert,
.spec-scroll{
  background-image:
    linear-gradient(135deg,
      rgba(255,255,255,.10) 0%,
      rgba(255,255,255,.04) 18%,
      rgba(255,255,255,0) 44%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.14),
    inset 0 -1px 0 rgba(0,0,0,.35),
    var(--shadow-md);
}
.fork-list li{
  background:rgba(255,255,255,.055);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.12);
}
.fork-installs{
  background-image:radial-gradient(940px 560px at 92% -12%,rgba(251,133,0,.16),transparent 62%);
}
.fork-studio{
  background-image:radial-gradient(940px 560px at 8% -12%,rgba(122,162,255,.15),transparent 62%);
}
[data-brand="installs"] .svc-hero{
  background:radial-gradient(920px 540px at 88% -12%,rgba(251,133,0,.15),transparent 62%);
}
[data-brand="installs"] .page-section{
  background:radial-gradient(840px 500px at 6% 0%,rgba(251,133,0,.10),transparent 60%);
}
[data-brand="installs"] .page-section.alt{
  background:
    radial-gradient(880px 520px at 92% 4%,rgba(146,128,255,.16),transparent 60%),
    var(--bg-2);
}
[data-brand="installs"] .contact{
  background:radial-gradient(840px 540px at 12% 104%,rgba(251,133,0,.13),transparent 60%);
}
[data-brand="installs"] .it-desk{
  background:linear-gradient(155deg,rgba(251,133,0,.15),rgba(146,128,255,.07)),var(--panel);
  border-color:rgba(251,133,0,.24);
}
.spec-hi{background:rgba(255,255,255,.05)}
.cert-verdict.ok{color:#63DDA0;background:rgba(61,220,132,.14)}
.field select{background-color:rgba(255,255,255,.05)}
.field select:focus{background-color:rgba(255,255,255,.09)}
/* The chevron has to be redrawn — it is baked into a data URI, so it cannot take a token. */
.field select{
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='9' viewBox='0 0 14 9' fill='none'%3E%3Cpath d='M1 1.5 7 7.5l6-6' stroke='%23A6ADC4' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* =========================================================
   Apogee layer

   The theme sits on top of the existing component CSS rather than replacing it, so the
   deck carousel, the service pages, the spec table and the contact form all keep working
   and simply inherit the new surface. Four things carry the look:

     1. Weight. Headings drop from 700 to 400/450 and the leading tightens to about 0.95.
        A 72px display line at weight 400 is most of what makes this read as Apogee.
     2. Corners. 12px on controls, 20-24px on panels. The old 999px pill buttons are gone.
     3. Glass. Panels are a near-black wash at 35% with a real blur, so the background
        video reads *through* them instead of being covered up.
     4. A near-white primary button instead of the brand gradient.

   Typeface note: the Apogee mock specifies Suisse Int'l. That is a commercial licence
   from Swiss Typefaces, and the CDN copy floating around is not a licensed distribution —
   putting it on a live commercial site would be an infringement, and it would also need
   the CSP's font-src opened to a third-party host. Inter stays: same grotesque skeleton,
   already self-hosted, already subset, and licensed for this.
   ========================================================= */

/* ---------- Rhythm ----------
   Apogee runs a 1800px measure with generous gutters. The existing .wrap already clamps
   to 90vw, which lands within a few pixels of Apogee's 82px padding at laptop widths and
   degrades better on very wide screens. Only the cap changes. */
:root{--wrap:1800px}

/* ---------- Type ----------
   The single biggest change in the file. Everything got lighter and tighter. */
h1,h2,h3,h4{font-weight:450}
.hero-title,.fork-h1,.svc-hero h1{font-weight:400;line-height:.95;letter-spacing:-.035em}
.section-title,.page-section h2,.cta-band h2{font-weight:400;line-height:1.02;letter-spacing:-.03em}
.fork-title{font-weight:400;line-height:1.04}
.svc h3,.promo-card h3,.faq-item h3,.note h4,.ai-step h3,.it-desk-copy h3,.cert-title{font-weight:450}
.section-sub,.fork-sub,.fork-lede,.hero-sub{font-weight:450}
.footer h4{font-weight:450}

/* ---------- Entrance animations ----------
   Apogee's timeline: a staggered reveal on load, driven entirely by CSS. Elements carry
   the start state in the stylesheet and the animation's `forwards` fill is what reveals
   them — so there is no observer to misfire and no state that can leave a section blank,
   which is the failure that got the old JS reveal system deleted.

   Delay comes from --d on the element, which keeps the timeline readable in the markup. */
@keyframes ap-up{from{opacity:0;transform:translateY(24px)}to{opacity:1;transform:none}}
@keyframes ap-down{from{opacity:0;transform:translateY(-16px)}to{opacity:1;transform:none}}
@keyframes ap-scale{from{opacity:0;transform:scale(.92)}to{opacity:1;transform:none}}

.enter,.enter-down,.enter-scale{opacity:0;animation-fill-mode:forwards;animation-delay:var(--d,0ms)}
.enter{animation-name:ap-up;animation-duration:.8s;animation-timing-function:var(--ease-enter)}
.enter-down{animation-name:ap-down;animation-duration:.7s;animation-timing-function:var(--ease-enter)}
.enter-scale{animation-name:ap-scale;animation-duration:.9s;animation-timing-function:var(--ease-enter)}

/* The blanket reduced-motion rule further down collapses durations but not delays, which
   would leave a 900ms-delayed element invisible for 900ms. Opt out completely instead. */
@media (prefers-reduced-motion:reduce){
  .enter,.enter-down,.enter-scale{opacity:1;animation:none}
}

/* ---------- Background video ----------
   Self-hosted: 1440x810, 20s, 466 KB, and a genuinely seamless loop — the crop path is a
   circle, so the last frame lands back on the first (SSIM 0.993).

   The scrim is not decoration. Apogee's own clip needs none because it is nearly black,
   and mine is damped to match, but this page has to fade the clip into a long scrolling
   document rather than end at the fold — hence the ramp to --bg at the bottom. */
.hero-bg{position:absolute;inset:0;z-index:0;overflow:hidden;pointer-events:none}
.hero-bg video,.hero-bg img{
  width:100%;height:100%;object-fit:cover;display:block;
}
.hero-bg::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(180deg,
    rgba(8,10,25,.30) 0%,
    rgba(8,10,25,.45) 45%,
    rgba(8,10,25,.82) 82%,
    var(--bg) 100%);
}
/* Anything sharing a stacking context with the clip has to be lifted above it. */
.hero > .wrap,.hero-inner,.hero-stage,.scroll-hint,
.fork-head > .wrap,.svc-hero > .wrap{position:relative;z-index:2}

/* Installs runs purple-and-orange, so its hero gets an orange pass over the shared clip
   rather than a second 466 KB encode. */
[data-brand="installs"] .hero-bg::after{
  background:
    radial-gradient(1200px 700px at 78% 18%,rgba(251,133,0,.16),transparent 62%),
    linear-gradient(180deg,
      rgba(8,10,25,.30) 0%,
      rgba(8,10,25,.45) 45%,
      rgba(8,10,25,.82) 82%,
      var(--bg) 100%);
}

/* ---------- Controls ----------
   Fixed heights with the label centred, which is what guarantees the horizontal padding
   actually shows. The old --pad shorthand let vertical padding fight the line box. */
.btn{
  --pad:0 27px;
  height:51px;border-radius:var(--radius);
  font-size:15.5px;font-weight:450;line-height:15.5px;letter-spacing:0;
  box-shadow:none;
}
.btn-sm{--pad:0 24px;height:46px;font-size:14px;line-height:14px;border-radius:11px}
.btn-primary{
  background:var(--btn-fill);color:var(--btn-ink);border-color:transparent;
  box-shadow:none;transition:opacity .3s,background .3s;
}
.btn-primary:hover{opacity:.9;transform:none;box-shadow:none}
.btn-ghost{
  background:transparent;border-color:#fff;color:#fff;
  box-shadow:none;transition:opacity .3s;
}
.btn-ghost:hover{background:transparent;border-color:#fff;color:#fff;opacity:.8;box-shadow:none}
@media (max-width:640px){
  .btn{--pad:0 20px;height:46px;font-size:14px}
}

.pill{border-radius:8px;box-shadow:none;font-weight:450}

/* ---------- Nav ----------
   Apogee's bar has no background of its own — the glass lives in the pills, and the
   content scrolls underneath in the open. That only works on a dark-only site, where
   whatever passes behind the wordmark is always dark enough to keep it legible. */
.nav{
  background:none;border-bottom:0;box-shadow:none;
  -webkit-backdrop-filter:none;backdrop-filter:none;
  padding:30px clamp(1.25rem,5vw,82px) 0;
  gap:1.5rem;
}
.nav-links{gap:0}
.brand .wordmark{font-weight:450;letter-spacing:-.02em}

.nav-pill{
  display:flex;align-items:center;gap:30px;
  height:52px;padding:0 24px;border-radius:11px;
  background:rgba(10,7,7,.35);
  -webkit-backdrop-filter:blur(17px);backdrop-filter:blur(17px);
}
.nav-pill a{
  font-size:14px;font-weight:450;line-height:14px;color:var(--muted);
  white-space:nowrap;transition:color .3s;
}
.nav-pill a:hover,.nav-pill a[aria-current="page"]{color:#fff}

.nav-auth{
  display:flex;align-items:center;gap:5px;
  height:52px;padding:3px;border-radius:13px;
  background:rgba(0,0,0,.35);
  -webkit-backdrop-filter:blur(17px);backdrop-filter:blur(17px);
}
.nav-auth .nav-ig{width:46px;height:46px;border-radius:11px;border:0;color:var(--muted)}
.nav-auth .nav-ig:hover{background:rgba(255,255,255,.06);color:#fff}
.nav-auth .btn-sm{height:46px}

.burger{
  width:44px;height:44px;padding:0;gap:0;
  display:none;align-items:center;justify-content:center;
  border-radius:11px;background:rgba(10,7,7,.35);
  -webkit-backdrop-filter:blur(17px);backdrop-filter:blur(17px);
  transition:background .3s;
}
.burger:hover{background:rgba(255,255,255,.1)}
/* Both icons stay mounted and cross-fade — swapping them by hiding one outright leaves
   the closing state nothing to animate from. */
.burger-icons{position:relative;width:20px;height:20px}
.burger-icons svg{
  position:absolute;inset:0;width:20px;height:20px;color:#fff;
  transition:opacity .3s ease-out,transform .3s ease-out;
}
.burger .icon-close{opacity:0;transform:rotate(-90deg) scale(.75)}
.burger[aria-expanded="true"] .icon-open{opacity:0;transform:rotate(90deg) scale(.75)}
.burger[aria-expanded="true"] .icon-close{opacity:1;transform:none}

@media (max-width:1024px){
  .nav-pill,.nav-auth{display:none}
  .burger{display:flex}
  .nav{padding-top:20px}
}

/* ---------- Mobile menu ----------
   Visibility-toggled rather than removed from the DOM, so the close transition has
   something to play. `inert` is what keeps the links out of the tab order while it is
   shut — `visibility:hidden` alone would do that too, but inert also survives the
   transition window when visibility is still `visible`. */
.mobile-menu{
  position:fixed;inset:0;z-index:105;display:block;
  visibility:hidden;
  background:none;
  transition:visibility .5s cubic-bezier(.32,.72,0,1);
}
.mobile-menu.is-open{visibility:visible}
.mobile-menu[hidden]{display:block}
.menu-backdrop{
  position:absolute;inset:0;opacity:0;
  background:rgba(8,10,25,.9);
  -webkit-backdrop-filter:blur(24px);backdrop-filter:blur(24px);
  transition:opacity .5s cubic-bezier(.32,.72,0,1);
}
.mobile-menu.is-open .menu-backdrop{opacity:1}
.menu-panel{
  position:absolute;top:86px;left:24px;right:24px;
  padding:32px;border-radius:var(--radius-panel);
  background:rgba(17,16,15,.6);
  -webkit-backdrop-filter:blur(30px);backdrop-filter:blur(30px);
  border:1px solid rgba(255,255,255,.06);
  transform-origin:top;
  opacity:0;transform:translateY(-16px) scale(.97);
  transition:opacity .5s cubic-bezier(.32,.72,0,1),transform .5s cubic-bezier(.32,.72,0,1);
}
.mobile-menu.is-open .menu-panel{opacity:1;transform:none}
.menu-links{display:flex;flex-direction:column;gap:4px}
.menu-links a{
  display:flex;align-items:center;justify-content:space-between;
  padding:16px;border-radius:var(--radius);
  font-size:18px;font-weight:450;color:rgba(255,255,255,.9);
  opacity:0;transform:translateX(-12px);
  transition:opacity .3s,transform .3s,background .3s;
}
.mobile-menu.is-open .menu-links a{opacity:1;transform:none}
.menu-links a:hover{background:rgba(255,255,255,.06)}
.mobile-menu.is-open .menu-links a:nth-child(1){transition-delay:100ms}
.mobile-menu.is-open .menu-links a:nth-child(2){transition-delay:150ms}
.mobile-menu.is-open .menu-links a:nth-child(3){transition-delay:200ms}
.mobile-menu.is-open .menu-links a:nth-child(4){transition-delay:250ms}
.menu-divider{height:1px;background:rgba(255,255,255,.1);margin:20px 0}
.menu-cta{
  display:flex;flex-direction:column;gap:12px;
  opacity:0;transform:translateY(8px);
  transition:opacity .3s,transform .3s;
}
.mobile-menu.is-open .menu-cta{opacity:1;transform:none;transition-delay:350ms}
.menu-cta .btn{width:100%;justify-content:center;height:50px;font-size:15px}
@media (max-width:640px){
  .menu-panel{top:76px;left:16px;right:16px;padding:24px}
}

/* ---------- Glass panels ----------
   One surface everywhere: the token does the work, these just align the radii and kill
   the light theme's leftover hard shadows. */
.svc,.note,.ai-step,.form,.promo-card,.faq-item,.it-desk,.svc-cta,
.fork-panel,.cert,.spec-scroll,.deck .slide{
  border-radius:var(--radius-lg);
  border-color:var(--glass-line);
}
.faq-item,.also a,.field input,.field textarea,.field select{border-radius:var(--radius)}
.deck-tab,.deck-arrow{border-radius:11px}
.deck-arrow{width:46px;height:46px}
.deck-tab{font-weight:450;font-size:14px;line-height:14px;height:46px;padding:0 20px;display:inline-flex;align-items:center}
.deck-tab.is-on{background:var(--btn-fill);color:var(--btn-ink);box-shadow:none}
.deck-stage{border-radius:var(--radius-lg)}

/* Nudge the card blur up to Apogee's 20px and drop the specular sweep: a bright diagonal
   highlight over a moving video reads as a smear rather than a sheen. */
@media (min-width:900px){
  .svc,.note,.ai-step,.form,.promo-card,.faq-item,.fork-panel,.cert,.deck .slide{
    -webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);
  }
}
.svc,.note,.ai-step,.form,.promo-card,.faq-item,.also a,.svc-cta,.it-desk,
.fork-panel,.cert,.spec-scroll,.deck .slide{
  background-image:none;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.05);
}
.svc:hover,.note:hover,.ai-step:hover,.promo-card:hover,.faq-item:hover{
  background-image:none;
  border-color:rgba(255,255,255,.14);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.08);
}

/* ---------- Form ---------- */
.field input,.field textarea,.field select{
  background-color:rgba(255,255,255,.04);border-color:var(--line-2);
}
.field input:focus,.field textarea:focus,.field select:focus{
  background-color:rgba(255,255,255,.08);border-color:rgba(255,255,255,.4);box-shadow:none;
}
.field input:focus + label,.field input:not(:placeholder-shown) + label,
.field textarea:focus + label,.field textarea:not(:placeholder-shown) + label{color:var(--muted)}

/* ---------- Footer ---------- */
.footer{background:transparent;border-top:1px solid var(--line)}

/* The fixed nav is taller now that the pills are 52px inside 30px of top padding. */
section[id]{scroll-margin-top:7rem}

/* ---------- Hero region ----------
   The clip is a child of <main> rather than of a section, so one block covers the nav,
   the breadcrumb and the hero whatever shape the page's opening is. */
main{position:relative}
.hero-bg{height:min(100svh,1040px);inset:0 0 auto}
/* The homepage masthead is short on its own; this gives the clip a hero to sit in. */
.fork-head{
  position:relative;z-index:2;
  min-height:min(76svh,820px);display:flex;align-items:center;
  padding:clamp(7rem,14vh,10rem) 0 clamp(2rem,5vw,4rem);
}
.fork-head > .wrap{width:min(var(--wrap),90vw)}
/* Breadcrumbs sit over the clip on the service pages and on /installs. */
.crumbs{position:relative;z-index:2}

/* ---------- Entrance safety net ----------
   A CSS animation on a display:none element still runs its clock. The nav pills and the
   hamburger swap display at 1024px, so loading wide and then narrowing left the burger
   at opacity:0 — invisible, but still clickable, which is worse than either.

   Neutralising the animation at the breakpoint where the element is hidden fixes both
   directions: it is opaque while hidden, and crossing the breakpoint changes
   animation-name from none back to a real one, which restarts it so it still fades in. */
@media (max-width:1024px){
  .nav-pill.enter-down,.nav-auth.enter-down{opacity:1;animation:none}
}
@media (min-width:1025px){
  .burger.enter-down{opacity:1;animation:none}
}

/* The masthead pill is gone, so the headline is the first thing in the hero and its
   top margin — which existed to clear that pill — would just push the block off centre. */
.fork-h1:first-child{margin-top:0}

/* =========================================================
   Homepage interactivity

   The fork page asks for one decision, so the two panels behave like what they are:
   targets. Each is clickable across its whole surface, lights up under the pointer, and
   the Installs one carries a working comparator rather than a static list.
   ========================================================= */

/* ---------- Whole-panel target ----------
   The stretched-link pattern: the CTA stays the only real <a>, and its ::after covers the
   panel. One tab stop, a real href for middle-click and open-in-new-tab, and no anchor
   nested inside another. The cost is that text inside the panel can't be selected — an
   acceptable trade on a panel whose entire job is to be chosen. */
.fork-panel{
  /* isolation:isolate so the highlight below can sit at z-index:-1 — above this panel's
     own background, beneath its content — without escaping behind the section. */
  position:relative;isolation:isolate;
  transition:border-color .4s var(--ease),box-shadow .4s var(--ease),transform .5s var(--ease);
}
/* .fork-copy must NOT be positioned. It was, briefly, to lift the text above the
   highlight — which quietly made it the containing block for the stretched link below,
   so only the copy column was clickable instead of the whole panel. The highlight's
   negative z-index solves the paint order without a positioned ancestor in the way. */
.fork-btn::after{content:"";position:absolute;inset:0;z-index:2;border-radius:inherit}

/* The pointer highlight. Painted on the panel's own ::before so it sits under the copy;
   --mx/--my are written by main.js, coalesced into one rAF per frame. */
.fork-panel::before{
  content:"";position:absolute;inset:0;z-index:-1;border-radius:inherit;pointer-events:none;
  opacity:0;transition:opacity .45s var(--ease);
  background:radial-gradient(460px circle at var(--mx,50%) var(--my,0%),
    rgba(255,255,255,.075),transparent 62%);
}

@media (hover:hover) and (pointer:fine){
  .fork-panel:hover{
    transform:translateY(-4px);
    border-color:rgba(255,255,255,.18);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.09),0 24px 60px -30px rgba(0,0,0,.85);
  }
  .fork-panel:hover::before{opacity:1}
  .fork-panel:hover .fork-btn{opacity:.92}
}

/* Focus lands on the CTA but should be drawn around the whole target, so the ring matches
   what a click would actually hit. Gated: without :has() the button keeps its own ring
   rather than losing the indicator altogether. */
@supports selector(:has(*)){
  .fork-panel:has(.fork-btn:focus-visible){
    outline:2px solid var(--blue);outline-offset:4px;border-color:rgba(255,255,255,.18);
  }
  .fork-panel:has(.fork-btn:focus-visible)::before{opacity:1}
  .fork-btn:focus-visible{outline:none}
}

/* ---------- Cable grade comparator ----------
   Sits above the stretched link (z-index 3 beats the overlay's 2) so its own buttons win
   the click, while everything around it still activates the panel. .fork-visual takes no
   z-index of its own on purpose — a stacking context there would trap this underneath. */
.fork-visual{position:relative;pointer-events:none}
.grade{
  position:relative;z-index:3;pointer-events:auto;
  width:100%;max-width:420px;
  padding:clamp(1.15rem,2vw,1.6rem);
  border:1px solid var(--glass-line);border-radius:var(--radius-lg);
  background:var(--surface);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.05);
}
@media (min-width:900px){
  .grade{-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px)}
}
.grade-tabs{display:flex;gap:6px;margin-bottom:1.25rem}
.grade-tab{
  flex:1;height:38px;border-radius:10px;cursor:pointer;
  font:inherit;font-size:13px;font-weight:450;line-height:13px;letter-spacing:0;
  color:var(--muted);background:rgba(255,255,255,.04);
  border:1px solid var(--line-2);
  transition:color .3s,background .3s,border-color .3s;
}
.grade-tab:hover{color:#fff;border-color:rgba(255,255,255,.3)}
.grade-tab.is-on{
  background:var(--btn-fill);color:var(--btn-ink);border-color:transparent;font-weight:500;
}
.grade-rows{display:grid;gap:.9rem}
.grade-row dt{
  font-size:.7rem;font-weight:450;letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted);margin-bottom:.45rem;
}
.grade-row dd{display:flex;align-items:center;gap:.75rem;margin:0}
.grade-bar{
  flex:1;height:8px;border-radius:5px;overflow:hidden;
  background:rgba(255,255,255,.07);
}
.grade-bar i{
  display:block;height:100%;border-radius:5px;background:var(--grad);
  transition:width .55s var(--ease-enter);
}
.grade-row b{
  flex:none;min-width:92px;text-align:right;
  font-size:.92rem;font-weight:450;color:var(--text);font-variant-numeric:tabular-nums;
}
.grade-note{
  margin-top:1.15rem;padding-top:1.1rem;border-top:1px solid var(--line);
  font-size:.88rem;line-height:1.6;color:var(--muted);
}

/* ---------- Studio chart ----------
   Draws itself in once, timed to land just after the panel has arrived. pathLength="1"
   on the <path> normalises the dash units so the numbers don't depend on the geometry. */
.mini-line-path{
  stroke-dasharray:1;stroke-dashoffset:1;
  animation:mini-draw 1.6s var(--ease-enter) 1.15s forwards;
}
@keyframes mini-draw{to{stroke-dashoffset:0}}

@media (prefers-reduced-motion:reduce){
  .mini-line-path{stroke-dashoffset:0;animation:none}
  .fork-panel:hover{transform:none}
}

@media (max-width:980px){
  .grade{max-width:none}
}

/* Comfortable thumb targets where there is no cursor. 38px clears the WCAG minimum
   either way, but it is under the 44px a finger actually wants. */
@media (pointer:coarse){
  .grade-tab{height:44px}
}

/* =========================================================
   Hero orb

   A disc of blue glass behind the headline. Four stacked layers, because a single
   blurred circle reads as a smudge rather than an object:

     .orb-glow    the bloom it throws onto the page, breathing slowly
     .orb-glass   the disc itself — a real backdrop-filter, so the clip behind it
                  genuinely refracts rather than being hidden
     .orb-gleam   the specular highlight, which follows the cursor
     .orb-rim     the lit edge along the top-left, where the light is coming from

   Transforms are split across two elements on purpose. .orb carries the scroll-driven
   parallax and .orb-float carries the idle drift: two animations on one element would
   both target transform, and the later one would simply replace the earlier.
   ========================================================= */

/* Registering these lets the highlight interpolate instead of snapping, which is what
   gives it the slight lag behind the cursor. Without @property support the values still
   apply — they just track exactly rather than trailing. */
@property --gx{syntax:'<percentage>';inherits:false;initial-value:30%}
@property --gy{syntax:'<percentage>';inherits:false;initial-value:24%}

.orb{
  --orb-size:min(42vw,520px);
  position:absolute;z-index:1;pointer-events:none;
  width:var(--orb-size);height:var(--orb-size);
  /* Positioned by its edges rather than centred with a transform — .orb's transform
     belongs to the scroll animation below. Nudged down and right of true centre so it
     clears the nav pills and leaves the headline its own air. */
  right:max(-8vw,-80px);
  top:calc(50% - var(--orb-size) / 2 + 3.5rem);
}
.orb-float{position:absolute;inset:0;animation:orb-float 22s ease-in-out infinite}
.orb-glow,.orb-glass,.orb-gleam,.orb-rim{position:absolute;border-radius:50%}

.orb-glow{
  inset:-34%;
  background:radial-gradient(circle at 50% 50%,
    rgba(56,196,255,.26),rgba(40,130,255,.12) 38%,transparent 66%);
  filter:blur(60px);
  animation:orb-breathe 13s ease-in-out infinite alternate;
}
.orb-glass{
  inset:0;
  background:linear-gradient(145deg,
    rgba(255,255,255,.07) 0%,
    rgba(255,255,255,.015) 32%,
    rgba(62,109,240,.05) 66%,
    rgba(122,162,255,.09) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.16),
    inset 0 -50px 90px -55px rgba(78,128,255,.42),
    0 60px 130px -60px rgba(24,48,140,.5);
}
/* The refraction is the expensive part, so it is desktop-only — phones get the gradient
   and the glow, which at this blur radius is very close to the same picture. */
/* No backdrop-filter: see the note above. */
.orb-gleam{
  inset:0;
  background:radial-gradient(closest-side circle at var(--gx) var(--gy),
    rgba(255,255,255,.20),rgba(255,255,255,.035) 32%,transparent 58%);
  transition:--gx .6s var(--ease),--gy .6s var(--ease);
}
.orb-rim{
  inset:0;
  /* A ring, not a disc: a transparent fill with a bright border, faded out around the
     bottom-right so the edge only catches light where the source is. */
  border:1.5px solid transparent;
  background:
    linear-gradient(transparent,transparent) padding-box,
    linear-gradient(140deg,
      rgba(210,248,255,.85),
      rgba(90,205,255,.48) 40%,
      rgba(56,150,255,.30) 72%,
      rgba(56,150,255,.18) 100%) border-box;
}

@keyframes orb-float{
  0%,100%{transform:translate3d(0,0,0) scale(1)}
  50%{transform:translate3d(-2%,-3%,0) scale(1.03)}
}
@keyframes orb-breathe{
  from{opacity:.72;transform:scale(.95)}
  to{opacity:1;transform:scale(1.05)}
}

/* Scroll-driven departure. Same mechanism as the rest of the page's parallax: it runs on
   the compositor off the main thread, so there is still no scroll listener anywhere on
   this site. Gated behind @supports — a browser without it simply gets a static orb. */
@supports (animation-timeline:scroll()){
  @media (prefers-reduced-motion:no-preference){
    .orb{
      animation:orb-depart linear both;
      animation-timeline:scroll(root);
      animation-range:0 95vh;
    }
    @keyframes orb-depart{
      to{transform:translate3d(0,-9rem,0) scale(.82);opacity:.2}
    }
    /* The copy rises at about a third of that. The gap between the two speeds is the
       depth cue — the orb reads as further away. Applied to the wrapper, not to the
       headline: the headline carries its own entrance animation, and a second animation
       on the same element would replace it. */
    .fork-head > .wrap{
      animation:hero-copy-drift linear both;
      animation-timeline:scroll(root);
      animation-range:0 95vh;
    }
    @keyframes hero-copy-drift{to{transform:translate3d(0,-3rem,0)}}
  }
}

@media (prefers-reduced-motion:reduce){
  .orb-float,.orb-glow{animation:none}
  .orb-gleam{transition:none}
}

/* Below the fork the orb would fight the panels for attention, and on a phone it would
   sit behind the headline rather than beside it. */
@media (max-width:820px){
  /* Measured, not guessed. The neon version is far brighter than the glass one it
     replaced, and at .5 sitting behind the lede it pulled that paragraph to 3.10:1 —
     under AA. Pushed below the copy so only the faint top edge overlaps, and dimmed
     until the sampled worst case cleared 4.5:1. */
  .orb{--orb-size:min(82vw,400px);right:-28vw;top:auto;bottom:-30%;opacity:.34}
}

/* The orb deliberately bleeds past the right edge, which grew the document 226px wider
   than the viewport. body{overflow-x:hidden} clips it visually but does not stop <html>
   scrolling — the classic version of this bug. Clip it where it is generated instead. */
.fork-head{overflow:hidden}
/* Permanently animating, so give it its own compositor layer rather than making the
   backdrop-filter re-rasterise against a shared one every frame. */
.orb-float{will-change:transform}

/* =========================================================
   Lava lamp

   Turns the glass disc into a blob of fluid. Two things do the work, and neither of them
   is the circle:

     1. border-radius morphs through eight values at once, on a 13s cycle. Animating all
        four corners independently on both axes is what produces a shape that bulges and
        pinches rather than one that just squashes.
     2. The rise is viscous, not linear — a 17s slow-in/slow-out curve with two peaks per
        cycle, plus squash and stretch: taller while it climbs, spreading as it settles.
        The two durations are deliberately coprime-ish so the loop never lines up.

   Every layer shares one morph animation with identical timing. They are siblings that
   start together, so they stay in lockstep — give any of them its own duration and the
   highlight immediately slides off the edge of the body.
   ========================================================= */
@keyframes blob-morph{
  0%,100%{border-radius:58% 42% 45% 55% / 48% 52% 48% 52%}
  14%    {border-radius:41% 59% 63% 37% / 39% 61% 39% 61%}
  28%    {border-radius:67% 33% 37% 63% / 63% 34% 66% 37%}
  42%    {border-radius:37% 63% 56% 44% / 53% 67% 33% 47%}
  57%    {border-radius:56% 44% 32% 68% / 64% 39% 61% 36%}
  71%    {border-radius:46% 54% 65% 35% / 36% 59% 41% 64%}
  85%    {border-radius:63% 37% 43% 57% / 56% 43% 57% 44%}
}

/* Viscous. The blob hangs at the top and bottom of its travel and moves quickest through
   the middle, which is what fluid does — a linear drift reads as a floating balloon. */
@keyframes lava-rise{
  0%,100%{transform:translate3d(0,0,0) scale(1.06,.93)}
  18%    {transform:translate3d(2%,-4%,0) scale(.93,1.09)}
  36%    {transform:translate3d(-1%,-11%,0) scale(1.05,.94)}
  55%    {transform:translate3d(2%,-5%,0) scale(.95,1.08)}
  74%    {transform:translate3d(-2%,-12%,0) scale(1.04,.95)}
  88%    {transform:translate3d(1%,-4%,0) scale(.97,1.05)}
}

.orb-float{
  animation:
    lava-rise 17s cubic-bezier(.45,.05,.55,.95) infinite,
    blob-morph 13s ease-in-out infinite;
}
.orb-glow,.orb-glass,.orb-gleam,.orb-rim{
  border-radius:58% 42% 45% 55% / 48% 52% 48% 52%;
  animation:blob-morph 13s ease-in-out infinite;
}
/* The bloom keeps its own breathing on top of the shared morph — different properties, so
   the two animations coexist rather than one replacing the other. */
.orb-glow{animation:blob-morph 13s ease-in-out infinite,orb-breathe 8s ease-in-out infinite alternate}

/* A denser fill than the glass version. A lava blob is a body of fluid, not a windowpane:
   it should look like it has something inside it. */
.orb-glass{
  background:
    radial-gradient(circle at 36% 30%,rgba(140,232,255,.30),transparent 52%),
    radial-gradient(circle at 52% 62%,rgba(22,160,255,.46),rgba(12,96,240,.40) 58%,rgba(8,54,190,.30) 100%);
  box-shadow:
    0 0 40px rgba(40,190,255,.50),
    0 0 100px rgba(30,150,255,.32),
    0 0 200px rgba(22,110,255,.20),
    inset 0 0 70px rgba(120,225,255,.22),
    inset 0 12px 46px rgba(190,245,255,.14);
}


@media (prefers-reduced-motion:reduce){
  .orb-float,.orb-glow,.orb-glass,.orb-gleam,.orb-rim{animation:none}
}

/* =========================================================
   Lannex Support — the third division

   Yellow is the easy accent on this ground. It is only ever used on near-black, where a
   bright yellow clears 13:1, so unlike the Installs orange it needs no second darker
   variant for small text — one token does both jobs.

   These sit at the end of the file on purpose: the shared section washes are written
   longhand in purple and blue further up, and a branded page without its own set reads
   as the Studio page wearing a different hat.
   ========================================================= */
[data-brand="support"]{--blue:#FFC93C;--accent-bright:#FFDD70}

[data-brand="support"] .svc-hero{
  background:radial-gradient(920px 540px at 88% -12%,rgba(255,201,60,.12),transparent 62%);
}
[data-brand="support"] .page-section{
  background:radial-gradient(840px 500px at 6% 0%,rgba(255,201,60,.07),transparent 60%);
}
[data-brand="support"] .page-section.alt{
  background:
    radial-gradient(880px 520px at 92% 4%,rgba(146,128,255,.16),transparent 60%),
    var(--bg-2);
  border-block:1px solid var(--line);
}
[data-brand="support"] .contact{
  background:radial-gradient(840px 540px at 12% 104%,rgba(255,201,60,.10),transparent 60%);
}
[data-brand="support"] .it-desk{
  background:linear-gradient(155deg,rgba(255,201,60,.13),rgba(146,128,255,.07)),var(--panel);
  border-color:rgba(255,201,60,.24);
}
[data-brand="support"] .it-desk-tag{color:var(--blue)}
[data-brand="support"] .it-desk-list li::before{background:var(--blue)}
[data-brand="support"] .hero-bg::after{
  background:
    radial-gradient(1200px 700px at 78% 18%,rgba(255,201,60,.14),transparent 62%),
    linear-gradient(180deg,
      rgba(8,10,25,.30) 0%,
      rgba(8,10,25,.45) 45%,
      rgba(8,10,25,.82) 82%,
      var(--bg) 100%);
}

/* Homepage panel wash. */
.fork-support{
  background-image:radial-gradient(940px 560px at 92% -12%,rgba(255,201,60,.11),transparent 62%);
}

/* ---------- Ticket queue (homepage, Support panel) ----------
   Same card shell as the cable comparator on the Installs panel, so the three panels
   read as one system. Static: the point is to show what a service desk looks like, not
   to fake activity that isn't there. */
.tickets{
  position:relative;z-index:3;pointer-events:auto;
  width:100%;max-width:420px;
  padding:clamp(1.15rem,2vw,1.6rem);
  border:1px solid var(--glass-line);border-radius:var(--radius-lg);
  background:var(--surface);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.05);
}
@media (min-width:900px){
  .tickets{-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px)}
}
.tickets-head{
  display:flex;align-items:baseline;justify-content:space-between;gap:1rem;
  padding-bottom:.9rem;margin-bottom:.9rem;border-bottom:1px solid var(--line);
}
.tickets-title{font-weight:450;letter-spacing:-.02em}
.tickets-count{font-size:.8rem;color:var(--muted)}
.tickets-list{display:grid;gap:.85rem}
.ticket-row{display:flex;align-items:center;justify-content:space-between;gap:.75rem;margin-bottom:.35rem}
.ticket-ref{
  font-size:.7rem;font-weight:450;letter-spacing:.1em;color:var(--muted);
  font-variant-numeric:tabular-nums;
}
.ticket-chip{
  font-size:.66rem;font-weight:500;letter-spacing:.06em;
  padding:.2rem .45rem;border-radius:6px;
  background:rgba(255,255,255,.08);color:var(--muted);
}
.ticket-chip.is-high{background:rgba(255,201,60,.16);color:var(--blue)}
.ticket-chip.is-done{background:rgba(90,200,140,.14);color:#63DDA0}
.ticket-title{font-size:.95rem;font-weight:450;line-height:1.4;margin-bottom:.25rem}
.ticket-meta{font-size:.76rem;color:var(--muted)}
@media (max-width:980px){
  .tickets{max-width:none}
}

/* ---------- Wordmark glow ----------
   A slight halo on the letters, so the logo sits in the same lit world as the blob
   behind it rather than looking pasted on.

   The accent parts glow in their own colour by deriving the shadow from currentColor
   with color-mix — which means the E and the division word pick up blue, orange or
   yellow automatically from --blue, with no per-brand rule. The white letters get a
   plain white halo at a lower alpha; a coloured one would tint them.

   Deliberately gentle. Two stops: a tight one that reads as emission and a wide one that
   reads as the light falling off. Anything stronger and the letterforms start to close
   up at nav size. */
.wordmark{
  text-shadow:
    0 0 18px rgba(255,255,255,.20),
    0 0 52px rgba(255,255,255,.09);
}
.wordmark-e,
.submark{
  text-shadow:
    0 0 16px color-mix(in srgb, currentColor 55%, transparent),
    0 0 44px color-mix(in srgb, currentColor 30%, transparent);
}
/* The big lockups carry more light than the small nav one — a halo scaled for 26px
   disappears at 45px. */
.lockup-lg .wordmark{
  text-shadow:
    0 0 26px rgba(255,255,255,.24),
    0 0 74px rgba(255,255,255,.11);
}
.lockup-lg .wordmark-e,
.lockup-lg .submark{
  text-shadow:
    0 0 24px color-mix(in srgb, currentColor 58%, transparent),
    0 0 64px color-mix(in srgb, currentColor 32%, transparent);
}

/* ============================================================================
   TRUSTED BY
   ----------------------------------------------------------------------------
   A logo strip between the last section and the footer, on every page.

   The brief was greyscale at rest, colour and a glow on hover. Greyscale alone
   doesn't get there, because only one of the two logos has any colour in it:
   Heron Foods is a blue-and-yellow badge, SLS is white line-work on a cut-out.
   grayscale(1) is a no-op on white, so on its own the strip would look like one
   logo waking up and one sitting there dead.

   So the resting state dims as well as desaturates — opacity plus a brightness
   cut — and hover restores both. Heron gains its colour back; SLS goes from a
   soft grey to full white. Different mechanism under the bonnet, same read on
   the screen: the one you're pointing at lights up.

   The glow is a drop-shadow rather than a box-shadow because these are cut-outs.
   box-shadow would halo the image's rectangle, which on a transparent PNG means
   a glowing box around nothing. drop-shadow follows the alpha, so the light
   comes off the letterforms.

   Two shadow stops, same reasoning as the wordmark glow above: a tight white one
   that reads as the logo emitting, and a wide accent-coloured one that reads as
   that light falling on the page. The accent is var(--blue), so the strip turns
   orange on Installs and yellow on Support without a per-brand rule. */
.trusted{
  position:relative;z-index:2;
  padding-block:clamp(2.5rem,4.5vw,3.5rem);
  text-align:center;
}
/* A short centred rule instead of a full-bleed border-top.
   Edge-to-edge, it read as a second divider stacked a few centimetres above the
   footer's own one — two rules bracketing a band only ~140px tall. Short and
   centred, it reads as a mark introducing the strip rather than as a boundary,
   and it sits on the same centre line as the caption and the logos.

   Faded at both ends rather than cut square: a 1px hairline stopping dead at
   rgba(255,255,255,.22) looks like a dash someone left behind. */
.trusted::before{
  content:"";
  display:block;width:132px;height:1px;
  margin:0 auto clamp(1.6rem,2.8vw,2.25rem);
  background:linear-gradient(90deg,
    transparent,
    rgba(255,255,255,.22) 30%,
    rgba(255,255,255,.22) 70%,
    transparent);
}
.trusted-inner{
  display:flex;flex-direction:column;align-items:center;
  gap:clamp(1.25rem,2.4vw,1.75rem);
}
/* Small and quiet — this is a caption for the logos, not a heading competing with
   the section above it. */
.trusted-title{
  margin:0;
  font-size:.8125rem;font-weight:500;line-height:1;
  letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted);opacity:.7;
}
.trusted-list{
  display:flex;flex-wrap:wrap;align-items:center;justify-content:center;
  gap:clamp(1.75rem,4vw,3rem);
  margin:0;padding:0;list-style:none;
}
.trusted-logo{
  display:block;width:auto;
  /* Sized per logo below. height:auto with a width would let the two drift apart
     vertically, and it's the height the eye lines up. */
  transition:
    filter .45s var(--ease),
    opacity .45s var(--ease),
    transform .45s var(--ease);
}
/* Optical sizing, not arithmetic. The Heron mark is a badge — its outer chrome
   stroke is part of the height but none of it is letter — so it needs to run
   taller than SLS to put the two wordmarks on the same visual scale. Matching
   their box heights instead makes Heron's type look small. */
.trusted-heron{height:38px}
.trusted-sls{height:30px}

/* Only gate the resting state behind a real pointer. On a phone there is no
   hover, so without this the logos would be permanently grey with no way to
   ever bring them up — the muted state would stop being a rest state and just
   become the design. Touch gets them lit. */
@media (hover:hover){
  .trusted-logo{
    filter:grayscale(1) brightness(.85);
    opacity:.45;
  }
  .trusted-logo:hover{
    filter:
      grayscale(0) brightness(1)
      drop-shadow(0 0 9px rgba(255,255,255,.28))
      drop-shadow(0 0 24px color-mix(in srgb, var(--blue) 65%, transparent));
    opacity:1;
    transform:translateY(-2px);
  }
}

@media (prefers-reduced-motion:reduce){
  .trusted-logo{transition-duration:.01ms}
  .trusted-logo:hover{transform:none}
}

/* The stack is the layout at every width now, so all phones need is smaller marks
   and a rule that isn't wider than the gap it's sitting in. */
@media (max-width:640px){
  .trusted::before{width:96px}
  .trusted-heron{height:32px}
  .trusted-sls{height:25px}
}
