/* ─────────────────────────────────────────────────────────────────────────
   Design tokens for the cam2.cloud marketing origin.

   These are NOT new values. Every colour is lifted verbatim from one of the
   two places the brand already lives:

     • apps/vsaas-core/viewer/src/index.css  — the product palette (@theme
       block for dark, :root[data-theme="light"] for the light overrides)
     • apps/sconnect-portal/static/css/style.css — the sconnect.bg site
       palette (--brand-*, --generic-*, --match-*)

   Keeping them identical is the point: a visitor who clicks "Sign in" lands
   in the viewer and the magenta must not shift.

   THEME MODEL — three states, matching the product exactly:
     no attribute    → follow the OS via prefers-color-scheme
     data-theme=dark → forced dark
     data-theme=light→ forced light
   Dark is the base declaration; light is applied by BOTH the media query
   (guarded so an explicit dark choice wins) and the explicit attribute. Never
   define a colour only inside a media query — the toggle has to win in both
   directions.
   ───────────────────────────────────────────────────────────────────────── */

:root {
  /* ── Brand core (identical in both themes: the magenta is the identity) ── */
  --primary: #a52b84;
  --gradient-1: #a52b84;
  --gradient-2: #d84075;
  --gradient-3: #f96763;

  /* The portal's --match-* ramp: magenta → violet → blue → cyan → teal. It is
     already brand-sanctioned (bg-gradient-matching on sconnect.bg) and it is
     the natural palette for the connecting-dots / network motif, because it
     travels from the brand hue to the accent hue through cool intermediates
     instead of through mud. */
  --net-1: #a52b84;
  --net-2: #7656b7;
  --net-3: #0075d0;
  --net-4: #008bcb;
  --net-5: #009aaf;
  --net-6: #00a48b;

  /* ── Dark theme (base) ── */
  --bg: #1a1218;
  --bg-card: #2a1f26;
  --bg-card-hover: #3a2d35;
  --surface: #fff4ff;        /* body text */
  --surface-muted: #baa6b2;  /* secondary text */
  --primary-light: #d599bf;  /* links/hover ON dark */
  --accent: #66baa8;
  --accent-light: #c5fcee;

  /* Product signal colours (viewer's KIND/STATE tokens) — reused so the
     map mock-up on this page encodes state the same way the real app does. */
  --kind-site: #6366f1;
  --kind-camera: #38bdf8;
  --state-healthy: #10b981;
  --state-degraded: #f59e0b;
  --state-unhealthy: #ef4444;
  --evt-vehicle: #a855f7;   /* viewer's timeline vehicle hue */

  /* Derived surfaces. color-mix keeps these in lockstep with the base
     tokens above, so a palette change never leaves a stale hairline behind. */
  --hairline: color-mix(in srgb, var(--surface) 12%, transparent);
  --hairline-strong: color-mix(in srgb, var(--primary) 42%, transparent);
  --glass: color-mix(in srgb, var(--bg-card) 72%, transparent);
  --glass-blur: 14px;
  --glow-primary: color-mix(in srgb, var(--primary) 55%, transparent);
  --glow-accent: color-mix(in srgb, var(--accent) 45%, transparent);
  /* Ambient orb strength. Dark can take a wide, bright bloom; light cannot
     (see the light block) — so every orb reads its alpha from here rather
     than hard-coding one that only works on one ground. */
  --orb-alpha: 0.46;
  --grid-alpha: 0.085;
  --noise-alpha: 0.035;
  /* Camera-tile lens vignette + line weight. A black vignette that reads as
     "lens falloff" on the dark ground just greys the tile out on the light
     one, so both the darkening and the stroke opacity are tokens. */
  --tile-vignette: rgba(0, 0, 0, .42);
  --tile-scene-alpha: .55;
  /* Hub bloom in the network diagram. Same reasoning as the orbs: the halo
     that reads as "this node is the centre" on the dark ground turns into a
     fuzzy pink blob on the pale one. */
  --hub-glow-alpha: 1;

  /* ── Type ─────────────────────────────────────────────────────────────
     System stack, matching the viewer's body font. No webfont: a marketing
     page that blocks on a font download loses more than it gains, and the
     brand's own wordmark is vector, not type. */
  --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;

  /* Fluid scale — clamp() so there is no step-change at a breakpoint. The
     hero is the only truly display-sized step; everything below it stays on
     a 1.2-ish ratio so long Bulgarian compounds do not overflow. */
  --t-display: clamp(2.35rem, 1.65rem + 2.9vw, 4.15rem);
  --t-h2: clamp(1.85rem, 1.35rem + 2.1vw, 3rem);
  --t-h3: clamp(1.15rem, 1.02rem + 0.55vw, 1.5rem);
  --t-lead: clamp(1.05rem, 0.97rem + 0.38vw, 1.3rem);
  --t-body: 1rem;
  --t-small: 0.9rem;
  --t-micro: 0.78rem;

  /* ── Space / shape ── */
  --sp-section: clamp(3.5rem, 2.4rem + 4.4vw, 6.5rem);  /* vertical section rhythm */
  --sp-gap: clamp(1rem, 0.7rem + 1.2vw, 1.75rem);
  --maxw: 1180px;
  --maxw-prose: 62ch;
  --r-sm: 10px;
  --r-md: 16px;
  --r-lg: 24px;
  --r-xl: 32px;
  --r-pill: 999px;

  --shadow-card: 0 1px 2px rgba(0, 0, 0, .18), 0 12px 32px -12px rgba(0, 0, 0, .45);
  --shadow-lift: 0 2px 4px rgba(0, 0, 0, .2), 0 28px 60px -22px rgba(0, 0, 0, .6);

  --ease: cubic-bezier(.22, .61, .36, 1);
  --ease-out: cubic-bezier(.16, 1, .3, 1);
}

/* ── Light theme ─────────────────────────────────────────────────────────
   Applied when the OS asks for light AND the visitor has not forced dark.
   Values mirror the viewer's :root[data-theme="light"] block, including its
   two non-obvious rules: --primary-light must DARKEN (it is link text on a
   white ground, not a tint), and the accent must darken to stay AA.

   The atmospheric layers are pulled way down rather than removed. A glow
   that reads as "futuristic" at 0.5 alpha on #1a1218 reads as "smudged
   print" at the same alpha on #f7f3f6, because the eye judges bloom
   relative to the ground it sits on. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --bg: #f7f3f6;
    --bg-card: #ffffff;
    --bg-card-hover: #f2ecf0;
    --surface: #1a1218;
    --surface-muted: #6b5d66;
    --primary-light: #7a1f61;
    --accent: #2f7a68;
    --accent-light: #2f7a68;

    --kind-camera: #0284c7;
    --state-healthy: #059669;
    --state-degraded: #d97706;
    --evt-vehicle: #9333ea;

    --hairline: color-mix(in srgb, var(--surface) 11%, transparent);
    --hairline-strong: color-mix(in srgb, var(--primary) 30%, transparent);
    --glass: color-mix(in srgb, #ffffff 78%, transparent);
    --glow-primary: color-mix(in srgb, var(--primary) 26%, transparent);
    --glow-accent: color-mix(in srgb, var(--accent) 22%, transparent);
    --orb-alpha: 0.13;
    --grid-alpha: 0.085;
    --noise-alpha: 0.02;
    --tile-vignette: rgba(26, 18, 24, .16);
    --tile-scene-alpha: .72;
    --hub-glow-alpha: .5;

    --shadow-card: 0 1px 2px rgba(26, 18, 24, .06), 0 12px 28px -14px rgba(26, 18, 24, .18);
    --shadow-lift: 0 2px 6px rgba(26, 18, 24, .08), 0 26px 54px -24px rgba(26, 18, 24, .26);
  }
}

/* Explicit light choice — same values, higher specificity, wins over an OS
   that prefers dark. Duplicated rather than @extend'ed because CSS has no
   such mechanism and a selector list would collapse the media guard. */
:root[data-theme="light"] {
  --bg: #f7f3f6;
  --bg-card: #ffffff;
  --bg-card-hover: #f2ecf0;
  --surface: #1a1218;
  --surface-muted: #6b5d66;
  --primary-light: #7a1f61;
  --accent: #2f7a68;
  --accent-light: #2f7a68;

  --kind-camera: #0284c7;
  --state-healthy: #059669;
  --state-degraded: #d97706;
  --evt-vehicle: #9333ea;

  --hairline: color-mix(in srgb, var(--surface) 11%, transparent);
  --hairline-strong: color-mix(in srgb, var(--primary) 30%, transparent);
  --glass: color-mix(in srgb, #ffffff 78%, transparent);
  --glow-primary: color-mix(in srgb, var(--primary) 26%, transparent);
  --glow-accent: color-mix(in srgb, var(--accent) 22%, transparent);
  --orb-alpha: 0.13;
  --grid-alpha: 0.085;
  --noise-alpha: 0.02;
  --tile-vignette: rgba(26, 18, 24, .16);
  --tile-scene-alpha: .72;
  --hub-glow-alpha: .5;

  --shadow-card: 0 1px 2px rgba(26, 18, 24, .06), 0 12px 28px -14px rgba(26, 18, 24, .18);
  --shadow-lift: 0 2px 6px rgba(26, 18, 24, .08), 0 26px 54px -24px rgba(26, 18, 24, .26);
}

/* Native form controls, scrollbars and the mobile address bar follow along. */
:root { color-scheme: dark; }
@media (prefers-color-scheme: light) { :root:not([data-theme="dark"]) { color-scheme: light; } }
:root[data-theme="light"] { color-scheme: light; }

/* ── Reset ───────────────────────────────────────────────────────────────
   Minimal and deliberate. Note this page does NOT copy the viewer's
   `* { padding: 0 }` reset: that one is unlayered specifically to defeat
   Tailwind utilities, a constraint this page does not have, and copying it
   here would only mean fighting it back section by section. */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html {
  /* Anchor the ROOT canvas, not just body: with color-scheme:light the UA
     paints its own near-white behind the document for the first frame, which
     shows as a pale band under the address bar before body's colour settles. */
  background-color: var(--bg);
  scroll-behavior: smooth;
  /* Anchor targets clear the sticky header instead of hiding under it. */
  scroll-padding-top: 5.5rem;
  -webkit-text-size-adjust: 100%;
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background-color: var(--bg);
  color: var(--surface);
  font-family: var(--font);
  font-size: var(--t-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* The ambient layers are fixed and full-bleed; without this a wide element
     could open a horizontal scrollbar and drag them out of alignment. */
  overflow-x: hidden;
}

img, svg, video, canvas { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button, input, textarea, select { font: inherit; color: inherit; }

h1, h2, h3, h4 {
  /* Display type only reads as "designed" when the tracking tightens as the
     size grows; at body sizes the same tracking just looks cramped. */
  line-height: 1.08;
  letter-spacing: -0.025em;
  font-weight: 650;
  text-wrap: balance;
}
p { text-wrap: pretty; }

:focus-visible {
  outline: 2px solid var(--primary-light);
  outline-offset: 3px;
  border-radius: 4px;
}

::selection { background: color-mix(in srgb, var(--primary) 45%, transparent); color: var(--surface); }

/* ── Layout primitives ──────────────────────────────────────────────────── */
.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: clamp(1.15rem, 0.6rem + 2.4vw, 2.5rem);
}

section { position: relative; }
.section { padding-block: var(--sp-section); }

/* Visible only to screen readers — used for the skip link and for the
   section landmarks whose visible heading is decorative. */
.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;
}
.skip-link {
  position: fixed; top: 0; left: 50%; translate: -50% -120%;
  z-index: 200; padding: .7rem 1.2rem;
  background: var(--primary); color: #fff;
  border-radius: 0 0 var(--r-sm) var(--r-sm);
  transition: translate .2s var(--ease);
}
.skip-link:focus-visible { translate: -50% 0; }

/* ── Ambient background ──────────────────────────────────────────────────
   Three stacked fixed layers behind everything: a perspective grid, two
   colour orbs, and a grain. All are `pointer-events:none` and live at
   z-index 0 with content at z-index 1.

   Every one of them reads its strength from a token (--grid-alpha,
   --orb-alpha, --noise-alpha) rather than a literal, because the same bloom
   that reads as atmosphere on #1a1218 reads as a printing defect on #f7f3f6. */
.ambient {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  contain: strict;
}

.ambient__grid {
  position: absolute;
  inset: -20% -20% 0;
  background-image:
    linear-gradient(to right, color-mix(in srgb, var(--primary) 100%, transparent) 1px, transparent 1px),
    linear-gradient(to bottom, color-mix(in srgb, var(--primary) 100%, transparent) 1px, transparent 1px);
  background-size: 54px 54px;
  opacity: var(--grid-alpha);
  /* A mild recession, not a floor. At the steeper angle this started out at
     (58deg), the converging verticals stopped reading as a grid and became a
     handful of stray diagonal scratches across the copy. */
  transform: perspective(900px) rotateX(34deg) scale(1.5);
  transform-origin: 50% 0;
  /* Fade every edge so the grid has no hard border anywhere. */
  -webkit-mask-image: radial-gradient(ellipse 92% 68% at 50% 4%, #000 0%, transparent 82%);
  mask-image: radial-gradient(ellipse 92% 68% at 50% 4%, #000 0%, transparent 82%);
}

.ambient__orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(90px);
  opacity: var(--orb-alpha);
  will-change: transform;
}
.ambient__orb--1 {
  width: 46vw; height: 46vw; min-width: 320px; min-height: 320px;
  top: -14vw; left: -10vw;
  background: radial-gradient(circle, var(--gradient-1), transparent 68%);
  animation: drift-a 34s var(--ease) infinite alternate;
}
.ambient__orb--2 {
  width: 40vw; height: 40vw; min-width: 280px; min-height: 280px;
  top: 8vw; right: -12vw;
  background: radial-gradient(circle, var(--net-3), transparent 68%);
  animation: drift-b 41s var(--ease) infinite alternate;
}
.ambient__orb--3 {
  width: 34vw; height: 34vw; min-width: 240px; min-height: 240px;
  top: 62%; left: 34%;
  background: radial-gradient(circle, var(--accent), transparent 70%);
  animation: drift-a 47s var(--ease) infinite alternate-reverse;
}

@keyframes drift-a {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(6vw, 5vh, 0) scale(1.16); }
}
@keyframes drift-b {
  from { transform: translate3d(0, 0, 0) scale(1.1); }
  to   { transform: translate3d(-7vw, 7vh, 0) scale(0.92); }
}

/* Grain. An inline SVG feTurbulence rather than a PNG: no extra request, and
   it resolution-scales, which matters because the flat gradients underneath
   would otherwise band visibly on a large display. */
.ambient__noise {
  position: absolute;
  inset: 0;
  opacity: var(--noise-alpha);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ── Motion policy ───────────────────────────────────────────────────────
   One global switch. Anything that loops, drifts or travels is stopped;
   scroll-reveals are neutralised by forcing the revealed state so content
   can never be left invisible (the failure mode of reveal-on-scroll under
   reduced motion). Hover/colour transitions are kept — they are feedback,
   not decoration, and they do not trigger vestibular symptoms. */
@media (prefers-reduced-motion: reduce) {
  .ambient__orb { animation: none !important; }
  .reveal { opacity: 1 !important; transform: none !important; transition: none !important; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
  }
}

/* ── Scroll reveal ───────────────────────────────────────────────────────
   Applied by site.js via IntersectionObserver. The initial hidden state is
   set in JS (not here) so that with JS disabled the content is simply
   visible — a CSS-only `opacity:0` would hide the whole page from a
   no-JS visitor and from any crawler that renders but blocks scripts. */
.reveal {
  transition: opacity .7s var(--ease-out), transform .7s var(--ease-out);
}

/* ═══ Reusable pieces ═══════════════════════════════════════════════════ */

/* ── Header ──────────────────────────────────────────────────────────────
   Sticky, transparent over the hero, and it grows a glass background only
   once the page has scrolled (site.js toggles .is-stuck). Starting opaque
   would cut a hard line across the hero's ambient glow. */
.hdr {
  position: sticky;
  top: 0;
  z-index: 100;
  transition: background .35s var(--ease), border-color .35s var(--ease), backdrop-filter .35s var(--ease);
  border-bottom: 1px solid transparent;
}
.hdr.is-stuck {
  background: var(--glass);
  border-bottom-color: var(--hairline);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
  backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
}
.hdr__bar {
  display: flex;
  align-items: center;
  gap: 1rem;
  min-height: 4.25rem;
}
.hdr__brand { display: flex; align-items: center; flex: 0 0 auto; }
.brand-host {
  font-size: 1.06rem; font-weight: 700; letter-spacing: -.022em;
  /* Brand magenta, not body text: it is the only wordmark up here now, so it
     carries the identity alone. --primary-light is the right token rather
     than --primary because it is already theme-resolved — #d599bf on the dark
     ground, #7a1f61 on the light one — so it clears AA on both. Hand-rolling
     the two branches here (the first attempt) painted pale pink on white,
     because "no data-theme attribute" means SYSTEM, not dark. */
  color: var(--primary-light);
}
.hdr__nav { display: flex; align-items: center; gap: .25rem; margin-inline-start: auto; }
.hdr__link {
  padding: .5rem .85rem;
  border-radius: var(--r-pill);
  font-size: var(--t-small);
  color: var(--surface-muted);
  transition: color .18s var(--ease), background .18s var(--ease);
  white-space: nowrap;
}
.hdr__link:hover { color: var(--surface); background: color-mix(in srgb, var(--surface) 7%, transparent); }
.hdr__actions { display: flex; align-items: center; gap: .5rem; flex: 0 0 auto; }
.hdr__prefs { display: flex; align-items: center; gap: .5rem; }
.hdr__gear {
  display: none;
  align-items: center; justify-content: center;
  width: 38px; height: 38px;
  border-radius: var(--r-pill);
  border: 1px solid var(--hairline-strong);
  background: color-mix(in srgb, var(--bg-card) 60%, transparent);
  color: var(--surface-muted);
  cursor: pointer;
  transition: color .16s var(--ease), border-color .16s var(--ease);
}
.hdr__gear svg { width: 18px; height: 18px; }
.hdr__gear:hover, .hdr__gear[aria-expanded="true"] { color: var(--primary-light); border-color: var(--hairline-strong); }
.hdr__signin { padding: .55rem 1.05rem; }

/* ── Narrow: preferences fold into a popover ────────────────────────────
   Sign in is the one control that must never disappear - it is what a
   returning customer came for, and on a phone it was previously the FIRST
   thing dropped. Language and theme are preferences, so they are the ones
   that fold away. */
@media (max-width: 760px) {
  .hdr__gear { display: inline-flex; }
  .hdr__prefs {
    position: absolute;
    top: calc(100% + .5rem);
    right: clamp(1.15rem, 0.6rem + 2.4vw, 2.5rem);
    flex-direction: column;
    align-items: stretch;
    gap: .75rem;
    padding: .9rem;
    border-radius: var(--r-md);
    border: 1px solid var(--hairline-strong);
    background: var(--glass);
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
    backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
    box-shadow: var(--shadow-lift);
    max-width: calc(100vw - 2 * clamp(1.15rem, 0.6rem + 2.4vw, 2.5rem));
    /* Hidden by default and revealed by [data-open]; `display:none` rather
       than opacity so the toggles are not tabbable while collapsed. */
    display: none;
  }
  .hdr__prefs[data-open="true"] { display: flex; }
  .hdr__prefs .seg { justify-content: center; }
}

/* Below the nav breakpoint the in-page links collapse; the actions
   (sign-in, language, theme) stay, because those are the ones a returning
   visitor actually came for.

   The auto margin has to move WITH the collapse. It lives on .hdr__nav, and a
   `display: none` element contributes no margins at all — so once the nav went
   away nothing pushed the actions right and they sat packed against the brand
   with ~100px of dead space beside them. Re-homing it here rather than putting
   `margin-inline-start: auto` on both: with two auto margins the free space
   splits between them, which would drag the desktop nav in from its current
   position for no reason. */
/* 1024, not 860. At 860 the nav and the actions did not actually fit beside
   each other: Sign in was clipped off the right edge and the page picked up
   real horizontal scroll from ~925px down in English — and from ~1009px down
   in BULGARIAN, whose nav labels ("Възможности", "Приложения", "Контакти")
   run about 40% longer than the English ones. The breakpoint has to clear the
   worst language, not the one it was eyeballed in. */
@media (max-width: 1024px) {
  .hdr__nav { display: none; }
  .hdr__actions { margin-inline-start: auto; }
}

/* ── Buttons ─────────────────────────────────────────────────────────── */
.btn {
  --btn-bg: transparent;
  --btn-fg: var(--surface);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  padding: .78rem 1.4rem;
  border-radius: var(--r-pill);
  border: 1px solid var(--hairline-strong);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-size: var(--t-small);
  font-weight: 600;
  letter-spacing: .01em;
  cursor: pointer;
  white-space: nowrap;
  transition: transform .18s var(--ease), box-shadow .25s var(--ease),
              background .2s var(--ease), border-color .2s var(--ease);
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }

.btn--primary {
  --btn-fg: #fff;
  border-color: transparent;
  /* The brand's own generic ramp (magenta → rose → coral), reused verbatim
     from sconnect.bg's .bg-gradient-generic so the CTA matches the parent
     site's buttons. */
  background-image: linear-gradient(100deg, var(--gradient-1), var(--gradient-2) 55%, var(--gradient-3));
  background-size: 180% 100%;
  box-shadow: 0 8px 26px -10px var(--glow-primary);
}
.btn--primary:hover {
  background-position: 100% 0;
  box-shadow: 0 14px 34px -10px var(--glow-primary);
}
.btn--ghost { background: color-mix(in srgb, var(--surface) 5%, transparent); }
.btn--ghost:hover { background: color-mix(in srgb, var(--surface) 10%, transparent); }
.btn--lg { padding: .95rem 1.8rem; font-size: 1rem; }

/* ── Icon-button group (theme + language toggles) ───────────────────────
   Same visual grammar as the viewer's ThemeToggle so the two do not look
   like different products. */
.seg {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  border-radius: var(--r-pill);
  border: 1px solid var(--hairline-strong);
  background: color-mix(in srgb, var(--bg-card) 60%, transparent);
}
.seg__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .35rem;
  min-width: 2rem;
  padding: .35rem .6rem;
  border: 0;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--surface-muted);
  font-size: var(--t-micro);
  font-weight: 600;
  cursor: pointer;
  transition: background .16s var(--ease), color .16s var(--ease);
}
.seg__btn:hover { color: var(--surface); }
.seg__btn[aria-pressed="true"], .seg__btn[aria-current="true"] {
  background: var(--primary);
  color: #fff;
}
.seg__btn svg { width: 15px; height: 15px; }

/* ── Eyebrow / pill label ────────────────────────────────────────────── */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .35rem .85rem .35rem .6rem;
  border-radius: var(--r-pill);
  border: 1px solid var(--hairline-strong);
  background: color-mix(in srgb, var(--primary) 12%, transparent);
  color: var(--primary-light);
  font-size: var(--t-micro);
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.eyebrow__dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
  animation: pulse-dot 2.4s var(--ease) infinite;
}
@keyframes pulse-dot {
  0%, 100% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent); }
  50%      { box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent) 0%, transparent); }
}

/* ── Section heading block ──────────────────────────────────────────── */
.head { max-width: 46rem; margin-bottom: clamp(2.25rem, 1.5rem + 2.5vw, 3.75rem); }
.head--center { margin-inline: auto; text-align: center; }
.head__title { font-size: var(--t-h2); margin-top: .9rem; }
.head__sub {
  margin-top: 1rem;
  font-size: var(--t-lead);
  color: var(--surface-muted);
  line-height: 1.55;
  letter-spacing: -0.01em;
}

/* Gradient text. -webkit-background-clip still needs the prefix for Safari;
   the fallback `color` matters because if the clip fails the text would
   otherwise render transparent and vanish entirely. */
.grad {
  color: var(--primary-light);
  background-image: linear-gradient(100deg, var(--gradient-2), var(--gradient-1) 45%, var(--net-3));
  -webkit-background-clip: text;
  background-clip: text;
}
@supports (-webkit-background-clip: text) or (background-clip: text) {
  .grad { -webkit-text-fill-color: transparent; color: transparent; }
}

/* ── Card ────────────────────────────────────────────────────────────── */
.card {
  position: relative;
  padding: clamp(1.35rem, 1rem + 1vw, 1.9rem);
  border-radius: var(--r-lg);
  border: 1px solid var(--hairline);
  background: var(--glass);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  box-shadow: var(--shadow-card);
  overflow: hidden;
  transition: transform .3s var(--ease), border-color .3s var(--ease), box-shadow .3s var(--ease);
}
/* Top inner highlight — the single detail that most separates a "glass" card
   from a flat rectangle, because it implies a lit edge. */
.card::before {
  content: '';
  position: absolute;
  inset: 0 0 auto;
  height: 1px;
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--surface) 30%, transparent) 30%,
              color-mix(in srgb, var(--surface) 30%, transparent) 70%, transparent);
  opacity: .7;
}
.card:hover {
  transform: translateY(-3px);
  border-color: var(--hairline-strong);
  box-shadow: var(--shadow-lift);
}
.card__icon {
  display: grid;
  place-items: center;
  width: 44px; height: 44px;
  margin-bottom: 1.1rem;
  border-radius: var(--r-sm);
  border: 1px solid var(--hairline-strong);
  background: color-mix(in srgb, var(--primary) 14%, transparent);
  color: var(--primary-light);
}
.card__icon svg { width: 22px; height: 22px; }
.card__title { font-size: var(--t-h3); letter-spacing: -0.015em; }
.card__body { margin-top: .55rem; color: var(--surface-muted); font-size: var(--t-small); line-height: 1.6; }

/* ── Grids ───────────────────────────────────────────────────────────── */
.grid { display: grid; gap: var(--sp-gap); }
/* auto-fit + minmax gives a natural 3 → 2 → 1 collapse with no breakpoints
   of its own; min() keeps the track from exceeding the viewport on <340px
   phones, where a fixed 280px minimum would otherwise force a scrollbar. */
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(275px, 100%), 1fr)); }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(360px, 100%), 1fr)); }

/* ── Footer ──────────────────────────────────────────────────────────── */
.foot {
  position: relative;
  z-index: 1;
  border-top: 1px solid var(--hairline);
  background: color-mix(in srgb, var(--bg-card) 45%, transparent);
  padding-block: clamp(2.5rem, 2rem + 2vw, 4rem) 2rem;
}
.foot__grid {
  display: grid;
  gap: 2rem;
  align-items: start;
  /* Explicit steps rather than auto-fit + minmax. A grid track never shrinks
     below its minmax MINIMUM, so `minmax(220px,…) + minmax(150px,…)` needed
     402px including the gap and simply overflowed a 390px phone — which is
     what let the whole page be dragged sideways. */
  grid-template-columns: 1fr;
}
@media (min-width: 560px) { .foot__grid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 900px) { .foot__grid { grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr) minmax(0, 1fr); } }
/* "cam2.cloud by [S Connect]" — the signature the header dropped. Baseline
   alignment keeps the three parts on one optical line; the logo's own box sits
   lower than the text baseline, hence the nudge. */
.foot__lockup { display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap; }
.foot__host { font-size: 1.05rem; font-weight: 700; letter-spacing: -.022em; color: var(--surface); }
.foot__by { font-size: .82rem; color: var(--surface-muted); }
.foot__brand .foot__logo { height: 22px; width: auto; color: var(--primary-light); transform: translateY(3px); }
.foot__tag { margin-top: .9rem; color: var(--surface-muted); font-size: var(--t-small); max-width: 30ch; }
.foot__h {
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: .09em;
  color: var(--surface-muted);
  margin-bottom: .85rem;
  font-weight: 700;
}
.foot__list { list-style: none; padding: 0; display: grid; gap: .5rem; }
.foot__list a { font-size: var(--t-small); color: var(--surface-muted); transition: color .16s var(--ease); }
.foot__list a:hover { color: var(--primary-light); }
.foot__bottom {
  margin-top: 2.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--hairline);
  display: flex;
  flex-wrap: wrap;
  gap: .75rem 1.5rem;
  align-items: center;
  justify-content: space-between;
  color: var(--surface-muted);
  font-size: var(--t-micro);
}
.foot__social { display: flex; gap: .6rem; }
.foot__social a {
  display: grid; place-items: center;
  width: 36px; height: 36px;
  border-radius: 50%;
  border: 1px solid var(--hairline-strong);
  color: var(--surface-muted);
  transition: color .18s var(--ease), border-color .18s var(--ease), background .18s var(--ease);
}
.foot__social a:hover { color: #fff; background: var(--primary); border-color: transparent; }
.foot__social svg { width: 17px; height: 17px; }

/* ═══ Hero ══════════════════════════════════════════════════════════════ */
.hero { position: relative; z-index: 1; padding-block: clamp(2rem, 1.2rem + 3vw, 3.75rem) clamp(3rem, 2rem + 4vw, 5.5rem); }
.hero__grid {
  display: grid;
  gap: clamp(2.5rem, 1.5rem + 4vw, 4.5rem);
  align-items: center;
  /* The console needs real width to stay legible, so it only earns its own
     column once there is room for both; below that the copy gets the full
     measure rather than being squeezed into half of a tablet. */
  grid-template-columns: 1fr;
}
@media (min-width: 1000px) {
  .hero__grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1.08fr); }
}

.hero__title { font-size: var(--t-display); margin-top: 1.15rem; font-weight: 700; }
.hero__lead {
  margin-top: 1.35rem;
  max-width: 40ch;
  font-size: var(--t-lead);
  color: var(--surface-muted);
  line-height: 1.55;
}
.hero__cta { display: flex; flex-wrap: wrap; gap: .75rem; margin-top: 2rem; }
.btn__arrow { width: 17px; height: 17px; transition: transform .22s var(--ease); }
.btn:hover .btn__arrow { transform: translateX(3px); }

.hero__points {
  list-style: none; padding: 0;
  margin-top: 2.25rem;
  display: flex; flex-wrap: wrap; gap: .55rem 1.5rem;
  font-size: var(--t-small); color: var(--surface-muted);
}
.hero__points li { display: flex; align-items: center; gap: .45rem; }
.hero__points svg { width: 15px; height: 15px; color: var(--accent); flex: 0 0 auto; }

/* ═══ Console (hero visual) ═════════════════════════════════════════════ */
.console {
  position: relative;
  border-radius: var(--r-xl);
  border: 1px solid var(--hairline-strong);
  background: var(--glass);
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
  box-shadow: var(--shadow-lift);
  overflow: hidden;
  /* A slight 3D settle so the panel reads as an object in the scene rather
     than a flat screenshot pasted on top of it. Removed on small screens,
     where perspective on a near-full-width panel just looks skewed. */
  transform: perspective(1600px) rotateY(-3deg) rotateX(1.5deg);
}
@media (max-width: 999px) { .console { transform: none; } }

.console::after {
  /* Lit top edge, same trick as .card::before but stronger: this is the
     brightest object on the page and needs a defined rim. */
  content: '';
  position: absolute; inset: 0 0 auto; height: 1px;
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--primary-light) 70%, transparent), transparent);
}
.console__chrome {
  display: flex; align-items: center; gap: 1rem; min-width: 0;
  padding: .7rem 1rem;
  border-bottom: 1px solid var(--hairline);
  background: color-mix(in srgb, var(--bg-card) 55%, transparent);
}
.console__dots { display: flex; gap: 6px; }
.console__dots i {
  width: 9px; height: 9px; border-radius: 50%;
  background: color-mix(in srgb, var(--surface) 22%, transparent);
}
.console__tabs { display: flex; gap: .25rem; min-width: 0; flex-wrap: wrap; }
.console__tab {
  padding: .3rem .7rem;
  border: 0;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--surface-muted);
  font-size: var(--t-micro);
  font-weight: 600;
  cursor: pointer;
  transition: background .18s var(--ease), color .18s var(--ease);
}
.console__tab:hover { color: var(--surface); }
.console__tab[aria-selected="true"] {
  background: color-mix(in srgb, var(--primary) 22%, transparent);
  color: var(--primary-light);
}

.console__body { padding: .85rem; }

/* The stage holds all three panels stacked. Reserving the aspect ratio on the
   CONTAINER is what stops the hero jumping when a pill is pressed: the panels
   are absolutely positioned, so the container's height cannot depend on which
   one happens to be visible. */
.console__stage { position: relative; aspect-ratio: 560 / 260; }
.console__panel {
  position: absolute;
  inset: 0;
  opacity: 1;
  transition: opacity .28s var(--ease);
}
.console__panel[hidden] {
  /* `hidden` alone would remove it from the a11y tree instantly and kill the
     crossfade, so keep it rendered-but-inert and fade it out. */
  display: block;
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
}
@media (prefers-reduced-motion: reduce) { .console__panel { transition: none; } }

.console__map, .console__stage-svg {
  width: 100%; height: 100%;
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--bg) 55%, transparent);
}

/* ── Live + playback stages ─────────────────────────────────────────── */
.console__stage-svg { --video-stage-fill: color-mix(in srgb, var(--bg) 78%, transparent); }
.stage__scene { fill: none; stroke: var(--surface-muted); stroke-width: 1.5;
                stroke-linecap: round; stroke-linejoin: round; opacity: .5; }
.stage__vignette rect { fill: none; stroke: var(--tile-vignette); stroke-width: 26; opacity: .55; }

/* ── Stage OSD ──────────────────────────────────────────────────────────
   HTML on top of the stage SVG, not SVG <text>. SVG has no way to size a box
   to its own text, so the old fixed-width pill fitted "LIVE" and spilled
   "НА ЖИВО" straight out of its background. Here the pill is just a padded
   inline-flex and fits whatever any language puts in it.

   Positions are percentages so they track the SVG as it scales, and the type
   uses container-query units where available so the label grows with the
   panel rather than with the window. The plain px value is the floor for
   anything without cq support (iOS < 16). */
.stage { position: relative; width: 100%; height: 100%; container-type: inline-size; }

.stage__badge, .stage__clock, .stage__name {
  position: absolute;
  z-index: 2;
  pointer-events: none;
  white-space: nowrap;
  line-height: 1.35;
}
.stage__badge {
  top: 5.5%; left: 3.5%;
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 8px 3px 7px;
  border-radius: var(--r-pill);
  background: rgba(0, 0, 0, .5);
  color: #fff;
  font-size: 9px; font-weight: 700; letter-spacing: .07em;
}
.stage__badge--pb { background: color-mix(in srgb, var(--net-3) 55%, rgba(0,0,0,.45)); }
.stage__badge i {
  width: 5px; height: 5px; border-radius: 50%; flex: 0 0 auto;
  background: var(--gradient-3);
  animation: blink 2s steps(1) infinite;
}
.stage__clock {
  top: 5.5%; right: 3.5%;
  color: var(--surface-muted);
  font: 500 10px/1.35 var(--font-mono);
}
.stage__name {
  bottom: 4.5%; left: 3.5%;
  color: var(--surface); opacity: .85;
  font-size: 11px; font-weight: 600;
}
@supports (width: 1cqw) {
  .stage__badge { font-size: clamp(8px, 2.1cqw, 12px); }
  .stage__clock { font-size: clamp(8px, 2.2cqw, 13px); }
  .stage__name  { font-size: clamp(9px, 2.5cqw, 14px); }
}
/* The playback OSD sits inside the drawn frame, not at the panel edge. */
.stage__badge--pb { top: 10%; left: 6%; }
.stage__clock--pb { top: 10%; right: 6%; }

.pb__frame rect { fill: color-mix(in srgb, var(--bg-card) 55%, transparent); stroke: var(--hairline); }
.pb__track { fill: color-mix(in srgb, var(--surface) 8%, transparent); }
/* Same tokens the real scrubber paints with, so the illustration is not
   inventing a colour language the product does not use. */
.band--cont    { fill: var(--accent); opacity: .55; }
.band--motion  { fill: var(--primary); }
.band--human   { fill: var(--state-degraded); }
.band--vehicle { fill: var(--evt-vehicle); }
.band--gap     { fill: var(--surface-muted); opacity: .32; }
.pb__ticks path { stroke: var(--surface-muted); stroke-width: 1; opacity: .45; }
.pb__ticks text { fill: var(--surface-muted); font: 500 9px/1 var(--font-mono); }
.pb__head path { stroke: var(--surface); stroke-width: 1.6; }
.pb__headChip { fill: var(--primary); }
.pb__headT { fill: #fff; font: 700 10px/1 var(--font-mono); }
.pb__legend text { fill: var(--surface-muted); font: 500 10px/1 var(--font); }

.map__grid path { stroke: var(--surface); stroke-opacity: .1; stroke-width: 1; fill: none; }
.map__river { stroke: var(--net-3); stroke-opacity: .28; stroke-width: 5; fill: none; stroke-linecap: round; }

/* Connector lines from pins to the cluster: dashed and slowly travelling, so
   the eye reads flow without anything actually moving position. */
.link {
  fill: none;
  stroke: url(#linkFade);
  stroke-width: 1.4;
  stroke-dasharray: 4 7;
  animation: dash 2.6s linear infinite;
  animation-delay: calc(var(--i) * -.5s);
  opacity: .85;
}
@keyframes dash { to { stroke-dashoffset: -22; } }

.pin__dot { fill: var(--state-healthy); }
.pin__halo { fill: var(--state-healthy); opacity: .22; transform-origin: center; transform-box: fill-box;
             animation: ping 3.2s var(--ease) infinite; animation-delay: calc(var(--i) * .42s); }
.pin--warn .pin__dot, .pin--warn .pin__halo { fill: var(--state-degraded); }
.pin--bad  .pin__dot, .pin--bad  .pin__halo { fill: var(--state-unhealthy); }
.pin__dot { stroke: var(--bg); stroke-width: 2; }
@keyframes ping {
  0%   { transform: scale(.45); opacity: .38; }
  70%  { transform: scale(1.25); opacity: 0; }
  100% { transform: scale(1.25); opacity: 0; }
}

.cluster__halo { fill: url(#clusterGlow); opacity: .5; }
.cluster__ring { fill: color-mix(in srgb, var(--bg-card) 92%, transparent); stroke: var(--primary); stroke-width: 1.6; }
.cluster__n { fill: var(--surface); font: 700 15px/1 var(--font); letter-spacing: -.02em; }
.cluster__l { fill: var(--surface-muted); font: 500 7.5px/1 var(--font); letter-spacing: .04em; }

.console__legend { display: flex; flex-wrap: wrap; gap: .3rem .9rem; padding: .7rem .35rem .2rem; font-size: var(--t-micro); color: var(--surface-muted); }
.lg { display: inline-flex; align-items: center; gap: .38rem; }
.lg::before { content: ''; width: 7px; height: 7px; border-radius: 50%; background: var(--state-healthy); }
.lg--warn::before { background: var(--state-degraded); }
.lg--bad::before  { background: var(--state-unhealthy); }

/* ── Camera tiles ──────────────────────────────────────────────────────
   Line-art scenes, not fake photographs. Each tile carries a different hue
   from the network ramp so the strip reads as four distinct views. */
.console__tiles { display: grid; grid-template-columns: repeat(4, 1fr); gap: .5rem; margin-top: .6rem; }
@media (max-width: 480px) { .console__tiles { grid-template-columns: repeat(2, 1fr); } }

.tile {
  position: relative;
  margin: 0;
  aspect-ratio: 4 / 3;
  border-radius: var(--r-sm);
  border: 1px solid var(--hairline);
  overflow: hidden;
  background:
    radial-gradient(ellipse 120% 90% at 50% 0%, color-mix(in srgb, var(--tile-hue) 30%, transparent), transparent 72%),
    linear-gradient(180deg, color-mix(in srgb, var(--bg-card) 90%, transparent), var(--bg));
  color: color-mix(in srgb, var(--tile-hue) 68%, var(--surface));
}
.tile--a { --tile-hue: var(--net-1); }
.tile--b { --tile-hue: var(--net-3); }
.tile--c { --tile-hue: var(--net-5); }
.tile--d { --tile-hue: var(--net-2); }

.tile__scene { position: absolute; inset: 12% 8% 20%; width: auto; height: auto; opacity: var(--tile-scene-alpha); }
/* Lens vignette — sells "camera" more cheaply than any amount of detail. */
.tile::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(ellipse 78% 78% at 50% 45%, transparent 40%, var(--tile-vignette));
}
.tile__scan {
  position: absolute; inset-inline: 0; height: 34%;
  background: linear-gradient(180deg, transparent, color-mix(in srgb, var(--tile-hue) 16%, transparent), transparent);
  animation: scan 5.5s linear infinite;
  pointer-events: none;
}
.tile--b .tile__scan { animation-delay: -1.4s; }
.tile--c .tile__scan { animation-delay: -2.8s; }
.tile--d .tile__scan { animation-delay: -4.1s; }
@keyframes scan { from { transform: translateY(-120%); } to { transform: translateY(320%); } }

.tile__cap {
  position: absolute; inset: auto 0 0; z-index: 2;
  padding: .55rem .5rem .35rem;
  font-size: 9.5px; letter-spacing: .02em;
  color: #fff;
  background: linear-gradient(180deg, transparent, rgba(0,0,0,.72));
}
/* min-width:0 is what actually lets the ellipsis engage — a flex item's
   default min-width:auto refuses to shrink below its text and the label
   collides with the LIVE badge instead of truncating. */
.tile__name { display: block; opacity: .95; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Status badge, top-right of each tile like a real camera OSD. Renamed from
   .tile__live when the strip started carrying recorded cameras too — the CSS
   lagged the markup for one build and the labels rendered as unstyled 16px
   text spilling out of the tile. */
.tile__badge {
  position: absolute; top: 5px; right: 5px; z-index: 3;
  display: inline-flex; align-items: center; gap: 3px;
  padding: 2px 5px 2px 4px;
  border-radius: var(--r-pill);
  background: rgba(0,0,0,.55);
  color: #fff;
  font-size: 8px; font-weight: 700; letter-spacing: .06em;
  line-height: 1.4;
}
.tile__badge i { width: 5px; height: 5px; border-radius: 50%; flex: 0 0 auto; }
/* Live blinks (something is happening now); recorded does not (it already
   happened) — the difference is the point of showing both. */
.tile__badge--live i { background: var(--gradient-3); animation: blink 2s steps(1) infinite; }
.tile__badge--pb i { background: var(--net-4); }
@keyframes blink { 0%, 60% { opacity: 1; } 61%, 100% { opacity: .25; } }

/* ═══ How it works ══════════════════════════════════════════════════════ */
.netdiag__wrap {
  padding: clamp(.75rem, .3rem + 2vw, 2rem) clamp(.5rem, 0rem + 2vw, 1.5rem);
  border-radius: var(--r-xl);
  border: 1px solid var(--hairline);
  background: var(--glass);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}
/* Two diagrams, one shown at a time. The wide one keeps its horizontal
   left-to-right story; the stacked one turns the same four nodes top-to-
   bottom for a portrait phone.
   This REPLACED an `overflow-x: auto` scroller around the wide diagram. That
   let the 660-unit SVG push the document 31px wider than the viewport, so the
   whole PAGE could be dragged sideways on a phone to reveal nothing. */
.netdiag { width: 100%; height: auto; display: block; }
.netdiag--stacked { display: none; max-width: 320px; margin-inline: auto; }
@media (max-width: 720px) {
  .netdiag { display: none; }
  .netdiag--stacked { display: block; }
}
.hub__ring--v { transform-origin: 150px 244px; }
.hub__ring--v2 { transform-origin: 150px 244px; }

.wire { fill: none; stroke: url(#wireGrad); stroke-width: 1.6; stroke-opacity: .55; }
/* The stacked diagram declares its own top-to-bottom gradient (wireGradV) and
   must paint with THAT one. Inheriting the rule above pointed its wires at a
   gradient declared inside the OTHER svg: the reference resolved document-wide
   so nothing errored, but a paint server from a different SVG's coordinate
   space produced no visible stroke — the diagram rendered as floating dots
   with no wires between them. */
.netdiag--stacked .wire { stroke: url(#wireGradV); }
.packet { fill: var(--accent-light); filter: drop-shadow(0 0 5px var(--glow-accent)); offset-rotate: 0deg;
          animation: flow 3.4s linear infinite; animation-delay: calc(var(--i) * -.85s); }
@keyframes flow { from { offset-distance: 0%; } to { offset-distance: 100%; } }
/* offset-path is the only non-universal feature on the page. Without it the
   packets would all pile up at the SVG origin, so hide them entirely and let
   the wires carry the diagram on their own. */
@supports not (offset-path: path('M0 0 L1 1')) { .packet { display: none; } }

.nd__box { fill: color-mix(in srgb, var(--bg-card) 78%, transparent); stroke: var(--hairline-strong); stroke-width: 1.2; }
.nd__ico { color: var(--primary-light); }
.nd--you .nd__ico { color: var(--accent); }
.nd__t { fill: var(--surface); font: 600 12.5px/1 var(--font); letter-spacing: -.01em; }
.nd__s { fill: var(--surface-muted); font: 400 10.5px/1 var(--font); }

/* Cameras inside the premises box, wired to the gateway chip. Deliberately
   dimmer than the gateway: they are context, the gateway is the point. */
.nd__cams { color: var(--surface-muted); opacity: .8; }
.hairwire { fill: none; stroke: var(--surface); stroke-opacity: .22; stroke-width: 1; stroke-dasharray: 2.5 3.5; }
.nd__chip { fill: color-mix(in srgb, var(--primary) 16%, transparent); stroke: var(--primary); stroke-width: 1.3; }
.nd__gw { color: var(--primary-light); }
.nd__chipT { fill: var(--primary-light); font: 600 9.5px/1 var(--font); letter-spacing: .02em; }
.nd__wireT { fill: var(--surface-muted); font: 500 10px/1 var(--font); letter-spacing: .02em; }
/* The direction arrow rides its own copy of the path: putting a marker on the
   gradient-stroked wire would tint the arrowhead by whatever stop it landed on. */
.wire--dir { stroke: none; }
.netdiag--stacked .hub__glow { fill: url(#hubGlowV); }

.hub__glow { fill: url(#hubGlow); opacity: var(--hub-glow-alpha); }
.hub__core { fill: color-mix(in srgb, var(--bg-card) 92%, transparent); stroke: var(--primary); stroke-width: 1.6; }
.hub__mark { color: var(--primary-light); }
.hub__ring {
  fill: none; stroke: var(--primary); stroke-opacity: .4; stroke-width: 1.1;
  stroke-dasharray: 3 6;
  transform-origin: 328px 120px;
  animation: spin 26s linear infinite;
}
.hub__ring--2 { stroke-opacity: .22; stroke-dasharray: 2 9; animation-duration: 40s; animation-direction: reverse; }
@keyframes spin { to { transform: rotate(360deg); } }

.how__points { margin-top: clamp(2rem, 1.4rem + 2vw, 3.25rem); }
.pt { padding-top: 1.25rem; border-top: 1px solid var(--hairline); }
.pt__n { font: 700 var(--t-micro)/1 var(--font-mono); color: var(--primary-light); letter-spacing: .1em; }
.pt__t { font-size: var(--t-h3); margin-top: .7rem; }
.pt__b { margin-top: .5rem; color: var(--surface-muted); font-size: var(--t-small); }

/* ═══ Use cases ═════════════════════════════════════════════════════════ */
.case__list { list-style: none; padding: 0; margin-top: 1.15rem; display: grid; gap: .6rem; }
.case__list li { display: flex; gap: .6rem; align-items: flex-start; font-size: var(--t-small); color: var(--surface-muted); line-height: 1.5; }
.case__list svg { width: 15px; height: 15px; color: var(--accent); flex: 0 0 auto; margin-top: .28em; }

/* ═══ FAQ ═══════════════════════════════════════════════════════════════
   Native <details>: keyboard-accessible, findable by in-page search, and it
   works with JS off. The only thing JS adds is closing the siblings. */
.faq__wrap { max-width: 860px; }
.faq { display: grid; gap: .7rem; }
.faq__item {
  border-radius: var(--r-md);
  border: 1px solid var(--hairline);
  background: var(--glass);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  overflow: hidden;
  transition: border-color .25s var(--ease), background .25s var(--ease);
}
.faq__item[open] { border-color: var(--hairline-strong); }
.faq__q {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: 1.05rem 1.25rem;
  cursor: pointer;
  font-weight: 600;
  font-size: 1.02rem;
  letter-spacing: -.012em;
  list-style: none;
}
.faq__q::-webkit-details-marker { display: none; }
.faq__q:hover { color: var(--primary-light); }
.faq__chev { width: 19px; height: 19px; flex: 0 0 auto; color: var(--surface-muted); transition: transform .3s var(--ease), color .2s var(--ease); }
.faq__item[open] .faq__chev { transform: rotate(180deg); color: var(--primary-light); }
.faq__a { padding: 0 1.25rem 1.15rem; color: var(--surface-muted); font-size: var(--t-small); line-height: 1.65; max-width: 68ch; }
/* Content-visibility keeps the closed answers out of layout entirely; the
   open animation is a plain fade+slide since height:auto is not animatable. */
.faq__item[open] .faq__a { animation: faqIn .28s var(--ease-out); }
@keyframes faqIn { from { opacity: 0; transform: translateY(-5px); } to { opacity: 1; transform: none; } }

/* ═══ Contact ═══════════════════════════════════════════════════════════ */
.contact { display: grid; gap: clamp(2rem, 1.2rem + 3vw, 3.5rem); align-items: start; }
@media (min-width: 900px) { .contact { grid-template-columns: 1fr 1fr; } }

.contact__intro .head__title { margin-top: .9rem; }
.contact__direct { margin-top: 2rem; display: grid; gap: .55rem; justify-items: start; }
.contact__directLabel { font-size: var(--t-micro); text-transform: uppercase; letter-spacing: .09em; color: var(--surface-muted); font-weight: 700; }
.contact__link {
  display: inline-flex; align-items: center; gap: .6rem;
  padding: .5rem .9rem .5rem .7rem;
  border-radius: var(--r-pill);
  border: 1px solid var(--hairline);
  background: color-mix(in srgb, var(--bg-card) 55%, transparent);
  font-size: var(--t-small); font-weight: 600;
  transition: border-color .2s var(--ease), color .2s var(--ease), transform .2s var(--ease);
}
.contact__link svg { width: 17px; height: 17px; color: var(--primary-light); }
.contact__link:hover { border-color: var(--hairline-strong); color: var(--primary-light); transform: translateX(2px); }
.contact__note { margin-top: 1.4rem; font-size: var(--t-small); color: var(--surface-muted); max-width: 34ch; }

.contact__form { display: grid; gap: 1rem; padding: clamp(1.35rem, 1rem + 1.4vw, 2rem); }
.contact__form:hover { transform: none; box-shadow: var(--shadow-card); border-color: var(--hairline); }
.field { display: grid; gap: .4rem; }
.field label { font-size: var(--t-micro); font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--surface-muted); }
.field input, .field textarea {
  width: 100%;
  padding: .72rem .9rem;
  border-radius: var(--r-sm);
  border: 1px solid var(--hairline);
  background: color-mix(in srgb, var(--bg) 55%, transparent);
  color: var(--surface);
  font-size: var(--t-small);
  transition: border-color .18s var(--ease), background .18s var(--ease);
}
.field textarea { resize: vertical; min-height: 6.5rem; line-height: 1.55; }
.field input::placeholder, .field textarea::placeholder { color: color-mix(in srgb, var(--surface-muted) 72%, transparent); }
.field input:focus, .field textarea:focus {
  outline: none;
  border-color: var(--primary);
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 20%, transparent);
}
.contact__submit { width: 100%; margin-top: .25rem; }
.contact__hint { font-size: var(--t-micro); color: var(--surface-muted); text-align: center; }

/* Honeypot. Off-screen rather than display:none or type=hidden - both of
   those are well-known enough that a chunk of form-filling bots skip them,
   which defeats the point. It still occupies no visual space. */
.hp {
  position: absolute !important;
  left: -9999px;
  width: 1px; height: 1px;
  overflow: hidden;
}

.contact__status {
  margin-top: .25rem;
  padding: .8rem .95rem;
  border-radius: var(--r-sm);
  font-size: var(--t-small);
  line-height: 1.5;
  border: 1px solid var(--hairline);
}
.contact__status.is-ok {
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--surface);
}
.contact__status.is-err {
  border-color: color-mix(in srgb, var(--state-degraded) 50%, transparent);
  background: color-mix(in srgb, var(--state-degraded) 12%, transparent);
  color: var(--surface);
}
.contact__status a { color: var(--primary-light); font-weight: 600; text-decoration: underline; }
.contact__submit[disabled] { opacity: .65; cursor: progress; }

/* ═══ Small-screen adjustments ══════════════════════════════════════════ */
/* Bulgarian tab labels are far longer than the English ones
   ("Възпроизвеждане" vs "Playback"), and at 320px they pushed the console 6px
   past the viewport. The decorative traffic-light dots are the cheapest thing
   to spend: they carry no information at all. */
@media (max-width: 430px) {
  .console__dots { display: none; }
  .console__chrome { gap: .5rem; padding: .6rem .7rem; }
  .console__tab { padding: .28rem .5rem; font-size: .72rem; }
}

@media (max-width: 560px) {
  .seg--theme .seg__btn { padding: .35rem .45rem; }
  .hero__points { gap: .5rem 1rem; }
  .console__body { padding: .6rem; }
}
/* NOTE: there used to be a `@media (max-width: 380px) { .seg--theme { display:
   none } }` here, from when both toggle groups sat inline in the header bar and
   genuinely could not both fit. They now live in a popover with room to spare,
   so that rule only served to delete the theme picker on a 360-375px phone
   (iPhone SE/mini, most budget Androids) — the settings panel opened with a
   language switch and nothing else. */

/* ═══ Mobile compositing budget ═════════════════════════════════════════
   Phones get the same design without the expensive parts of it.

   The page asks for 22 backdrop-filtered layers, three 90px-blurred orbs on
   infinite animations, and a position:sticky header that is itself
   backdrop-filtered — and the language link sits inside that sticky stack.
   Blink shrugs at all of it (measured: no FCP difference even at 6x CPU
   throttle) but WebKit composites blur very differently, and a sticky
   backdrop-filtered ancestor is a well-known source of repaint stalls on iOS.

   None of it is load-bearing. `--glass` is already a 72% color-mix, so making
   it near-opaque and dropping the filter looks all but identical on a phone,
   where nothing shows through a card anyway. The orbs are radial-gradients —
   already soft — so the extra blur() buys very little, and a 40-second drift
   is invisible on a 6-inch screen.

   Kept on desktop, where it costs nothing and the depth actually reads. */
@media (max-width: 900px) {
  :root {
    --glass: color-mix(in srgb, var(--bg-card) 94%, var(--bg));
  }
  .hdr.is-stuck, .console, .card, .faq__item, .netdiag__wrap, .hdr__prefs,
  .contact__form {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .ambient__orb {
    /* The gradient is the shape; blur only softened an already-soft edge. */
    filter: none;
    animation: none;
    opacity: calc(var(--orb-alpha) * 0.75);
  }
}
