/* =========================================================
   CT Insight - landing site design system

   Brand tokens from capturetech.com/brand/. Display face Mont, body face Roboto,
   both self-hosted from CaptureTech's brand font folder.

   The visual language is layered glass: translucent surfaces that blur and tint what
   is behind them, separated by depth rather than by borders. That only reads as glass
   if there is something to see through it, so the page sits on a soft brand-tinted
   light field (--field-*, painted by body::before) and every surface above it is
   semi-opaque. Hard 1px greys are used sparingly; a bright inner top edge plus a wide
   soft shadow does the same job with more depth.

   Rules of thumb when extending this:
     - Surfaces get --glass-*, not flat white.
     - Radii come from the --r-* scale. Nothing square, nothing fully round but pills.
     - Motion is transform and opacity only, and every animation is switched off under
       prefers-reduced-motion. Translucency is switched off under
       prefers-reduced-transparency, which is a real setting on Apple platforms.
   ========================================================= */

@font-face{font-family:'Mont';src:url('../fonts/landing/mont-regular.woff2') format('woff2');font-weight:300 500;font-style:normal;font-display:swap;}
@font-face{font-family:'Mont';src:url('../fonts/landing/mont-bold.woff2') format('woff2');font-weight:600 900;font-style:normal;font-display:swap;}
@font-face{font-family:'Roboto';src:url('../fonts/landing/roboto-regular.woff2') format('woff2');font-weight:400;font-style:normal;font-display:swap;}
@font-face{font-family:'Roboto';src:url('../fonts/landing/roboto-italic.woff2') format('woff2');font-weight:400;font-style:italic;font-display:swap;}
@font-face{font-family:'Roboto';src:url('../fonts/landing/roboto-medium.woff2') format('woff2');font-weight:500;font-style:normal;font-display:swap;}
@font-face{font-family:'Roboto';src:url('../fonts/landing/roboto-bold.woff2') format('woff2');font-weight:700;font-style:normal;font-display:swap;}

:root{
  /* Brand */
  --cyan:#00baff;
  --cyan-dark:#0092c8;
  --blue:#0064cd;
  --red:#f03c5a;
  --red-dark:#d42846;

  /* Ink and neutrals, cool-tinted so they sit correctly on the field */
  --ink:#0a0f17;
  --ink-2:#2b3542;
  --ink-3:#5c6878;
  --ink-4:#8b97a6;
  --line:rgba(10,25,50,0.09);
  --line-soft:rgba(10,25,50,0.055);

  /* The light field everything floats on */
  --field:#f4f8fc;
  --field-warm:rgba(255,255,255,0.9);

  /* Glass materials. Alpha carries the hierarchy: the further forward a surface is,
     the more opaque it gets, so stacked panels stay legible. */
  --glass-thin:rgba(255,255,255,0.46);
  --glass:rgba(255,255,255,0.62);
  --glass-thick:rgba(255,255,255,0.78);
  --glass-edge:rgba(255,255,255,0.85);
  --glass-blur:saturate(170%) blur(22px);
  --glass-blur-lg:saturate(180%) blur(40px);

  /* Dark glass, for the closing banner and the dark band */
  --glass-dark:rgba(12,18,28,0.72);
  --glass-dark-edge:rgba(255,255,255,0.12);

  /* Depth. Two shadows per surface: a tight contact shadow and a wide ambient one. */
  --shadow-sm:0 1px 2px rgba(10,25,50,0.05), 0 4px 14px -6px rgba(10,25,50,0.12);
  --shadow-md:0 1px 2px rgba(10,25,50,0.05), 0 16px 44px -16px rgba(10,25,50,0.20);
  --shadow-lg:0 2px 4px rgba(10,25,50,0.06), 0 32px 80px -24px rgba(10,25,50,0.28);
  --inner-top:inset 0 1px 0 rgba(255,255,255,0.75);

  /* Radii */
  --r-sm:12px;
  --r:18px;
  --r-lg:24px;
  --r-xl:32px;

  --maxw:1180px;
  --header-h:72px;

  --font-head:'Mont', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-body:'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

  --ease:cubic-bezier(0.32, 0.72, 0, 1);
}

/* ---------- Reset and base ---------- */
*{box-sizing:border-box;}

html{
  scroll-behavior:smooth;
  -webkit-text-size-adjust:100%;
  /* The pages still scroll; the bar itself is hidden so the scene runs edge to
     edge. scrollbar-width leaves the scroll container alone, unlike an overflow
     clamp (see the note below), so the sticky header is unaffected. */
  scrollbar-width:none;
}
/* Older WebKit ignores scrollbar-width and needs the pseudo-element instead. */
html::-webkit-scrollbar{ width:0; height:0; display:none; }

/* No overflow-x clamp on html or body: it turns the body into a scroll container and
   silently breaks position:sticky on the header. Anything that would overhang is
   clipped by its own section instead. */
body{
  margin:0;
  font-family:var(--font-body);
  font-size:16px;
  line-height:1.62;
  color:var(--ink-2);
  background:var(--field);
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  position:relative;
  min-height:100vh;
}

/* The field. Fixed so it does not scroll with content: the glass then samples a
   stable backdrop and surfaces read as panes over a scene rather than as tinted
   boxes. Sits at -1 so every section paints above it. */
body::before{
  content:'';
  position:fixed;
  inset:0;
  z-index:-1;
  background:
    radial-gradient(58% 44% at 12% 4%, rgba(0,186,255,0.10), transparent 70%),
    radial-gradient(46% 38% at 92% 12%, rgba(0,100,205,0.08), transparent 72%),
    radial-gradient(52% 40% at 78% 88%, rgba(240,60,90,0.05), transparent 70%),
    linear-gradient(180deg, #ffffff 0%, var(--field) 46%, #eef4fa 100%);
}

img{max-width:100%;height:auto;display:block;}
a{color:inherit;text-decoration:none;}

:focus-visible{
  outline:2px solid var(--cyan-dark);
  outline-offset:3px;
  border-radius:4px;
}

.container{max-width:var(--maxw);margin:0 auto;padding:0 24px;}

section{padding:clamp(48px, 7vw, 92px) 0;position:relative;}

/* The sticky header would otherwise park over the heading a jump link scrolls to. Exactly
   the header height, no more: the extra 28px this used to carry left a sliver of whatever
   sits above the target visible under the header after the jump. Sections bring their own
   generous padding-top, so flush against the header still leaves the heading room. */
section[id], div[id]{scroll-margin-top:var(--header-h);}

/* ---------- Typography ----------
   Fluid from the small end up, so there is no breakpoint where the display sizes
   jump. Tracking tightens as size grows, which is what keeps large Mont from looking
   loose. */
h1,h2,h3,h4{
  font-family:var(--font-head);
  font-weight:700;
  color:var(--ink);
  margin:0 0 16px;
  text-wrap:balance;
}
h1{font-size:clamp(34px, 5.4vw, 60px);line-height:1.04;letter-spacing:-0.028em;}
h2{font-size:clamp(26px, 3.4vw, 40px);line-height:1.12;letter-spacing:-0.022em;}
h3{font-size:clamp(19px, 1.6vw, 23px);line-height:1.26;letter-spacing:-0.012em;}
h4{font-size:16px;line-height:1.3;letter-spacing:-0.008em;}

p{margin:0 0 16px;}
p:last-child{margin-bottom:0;}

.lede{
  font-size:clamp(17px, 1.35vw, 19.5px);
  line-height:1.58;
  color:var(--ink-3);
  max-width:60ch;
}

strong,b{color:var(--ink);font-weight:700;}
em{font-style:italic;color:var(--ink-3);}

code{
  font-family:ui-monospace, 'SF Mono', 'Roboto Mono', monospace;
  font-size:0.88em;
  background:rgba(0,186,255,0.09);
  border:1px solid rgba(0,186,255,0.16);
  color:var(--blue);
  padding:1px 7px;
  border-radius:7px;
  white-space:nowrap;
}

/* Small caps label above a heading. The dot is the one piece of decoration kept
   throughout: it reads as a live indicator rather than an icon. */
.eyebrow{
  display:inline-flex;align-items:center;gap:9px;
  font-family:var(--font-head);font-weight:700;
  font-size:12px;letter-spacing:0.13em;text-transform:uppercase;
  color:var(--cyan-dark);
  margin-bottom:16px;
}
.eyebrow .dot{
  width:7px;height:7px;border-radius:50%;
  background:var(--cyan);
  box-shadow:0 0 0 3px rgba(0,186,255,0.18);
  flex-shrink:0;
}

.section-head{max-width:66ch;margin:0 auto clamp(32px, 4vw, 52px);text-align:center;}
.section-head.left{margin-left:0;margin-right:0;text-align:left;}
.section-head .lede{margin-left:auto;margin-right:auto;}
.section-head.left .lede{margin-left:0;}

.text-center{text-align:center;}

/* ---------- Grid ---------- */
.grid{display:grid;gap:22px;}
.grid-2{grid-template-columns:repeat(2,minmax(0,1fr));}
.grid-3{grid-template-columns:repeat(3,minmax(0,1fr));}
.grid-5{grid-template-columns:repeat(5,minmax(0,1fr));}

/* ---------- Buttons ----------
   Deliberately the plainest thing on the page. The glass language belongs to surfaces
   that have something worth seeing through them; a button is a target you press, and
   dressing one in a blur, a three-stop gradient, a specular top edge and a coloured
   glow made the click target read as decoration. So: a solid fill, one modest shadow
   to lift it off the field, a moderate radius rather than a full pill, and no
   backdrop-filter at all. Panels stay glass, controls stay solid.

   Hover is a small lift and a slightly deeper colour, in that order of prominence. */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:8px;
  font-family:var(--font-head);font-weight:700;
  font-size:15px;line-height:1;
  padding:13px 24px;
  border-radius:10px;
  border:1px solid transparent;
  cursor:pointer;
  white-space:nowrap;
  transition:transform .2s var(--ease), box-shadow .2s var(--ease),
             background-color .18s ease, border-color .18s ease, color .18s ease;
}
.btn:hover{transform:translateY(-1px);}
.btn:active{transform:translateY(0);transition-duration:.08s;}

/* Flat brand red. The gradient it replaces spanned #ff5871 to --red-dark, which put a
   highlight across the top of every primary button on the site. */
.btn-primary{
  background:var(--red);
  color:#fff;
  box-shadow:0 1px 2px rgba(150,20,45,0.2), 0 4px 10px -4px rgba(150,20,45,0.28);
}
.btn-primary:hover{
  background:var(--red-dark);
  box-shadow:0 1px 2px rgba(150,20,45,0.22), 0 7px 16px -6px rgba(150,20,45,0.32);
}

/* Solid white with a real hairline, not translucent white with a bright glass edge. */
.btn-ghost{
  background:#fff;
  color:var(--ink);
  border-color:var(--line);
  box-shadow:0 1px 2px rgba(10,25,50,0.06);
}
.btn-ghost:hover{
  border-color:rgba(10,25,50,0.16);
  box-shadow:0 2px 6px rgba(10,25,50,0.09);
}

/* On the dark banner, where a white fill would glare. */
.btn-ghost-invert{
  background:rgba(255,255,255,0.08);
  color:#fff;
  border-color:rgba(255,255,255,0.3);
}
.btn-ghost-invert:hover{background:rgba(255,255,255,0.15);border-color:rgba(255,255,255,0.45);}

.btn-sm{padding:9px 16px;font-size:13.5px;border-radius:8px;}
.btn-block{width:100%;}

/* Inline "read more" link with a brand underline that grows on hover. */
.link-more{
  font-family:var(--font-head);font-weight:700;font-size:14.5px;
  color:var(--ink);
  padding-bottom:3px;
  background-image:linear-gradient(var(--cyan), var(--cyan));
  background-size:100% 2px;
  background-position:0 100%;
  background-repeat:no-repeat;
  transition:background-size .3s var(--ease), color .24s ease;
}
.link-more:hover{color:var(--cyan-dark);background-size:100% 3px;}

/* ---------- Header ----------
   Transparent over the hero, then frosts on scroll: landing.js adds .is-scrolled past
   40px. The blur is always mounted and only the tint, hairline and shadow animate,
   because transitioning backdrop-filter itself stutters. */
header.site{
  position:sticky;top:0;z-index:60;
  background:transparent;
}
/* The glass lives on a pseudo-element rather than on the header itself.
   backdrop-filter blurs its backdrop even when the element's own background is fully
   transparent, so a filter mounted on the header painted a hard-edged blurred
   rectangle over the top of the hero at rest: a visible band and a seam along the
   header's bottom edge before you had scrolled anything. Fading a pseudo-element's
   opacity from 0 leaves nothing painted at all until it is wanted, and opacity is
   cheaper to animate than the filter it carries. */
header.site::before{
  content:'';
  position:absolute;inset:0;
  -webkit-backdrop-filter:var(--glass-blur-lg);
  backdrop-filter:var(--glass-blur-lg);
  background:rgba(255,255,255,0.72);
  border-bottom:1px solid var(--line);
  box-shadow:0 1px 0 rgba(255,255,255,0.6) inset, 0 8px 32px -14px rgba(10,25,50,0.22);
  opacity:0;
  transition:opacity .45s var(--ease);
  pointer-events:none;
}
header.site.is-scrolled::before{opacity:1;}
header.site > *{position:relative;}

.nav-wrap{
  display:flex;align-items:center;justify-content:space-between;gap:18px;
  max-width:var(--maxw);margin:0 auto;
  padding:14px 24px;
  min-height:var(--header-h);
  transition:padding .4s var(--ease);
}
header.site.is-scrolled .nav-wrap{padding-top:10px;padding-bottom:10px;}

.brand{
  display:flex;align-items:center;gap:9px;
  font-family:var(--font-head);font-weight:700;font-size:20px;
  letter-spacing:-0.02em;
  flex-shrink:0;
}
.brand-mark{height:26px;width:auto;}
.brand .word{color:var(--ink);}
.brand .sub{
  color:var(--ink-4);font-weight:400;font-size:10.5px;
  letter-spacing:0.08em;text-transform:uppercase;
  margin-left:5px;padding-left:9px;
  border-left:1px solid var(--line);
  white-space:nowrap;
}

nav.main{display:flex;align-items:center;gap:4px;}
nav.main a{
  font-family:var(--font-head);font-weight:700;
  font-size:14px;color:var(--ink-3);
  padding:8px 13px;border-radius:999px;
  transition:color .24s ease, background-color .24s ease;
}
nav.main a:hover{color:var(--ink);background:rgba(255,255,255,0.6);}
nav.main a.active{color:var(--ink);background:var(--glass-thick);box-shadow:var(--shadow-sm);}

.nav-cta{display:flex;align-items:center;gap:10px;}

.lang-switch{
  display:flex;align-items:center;gap:2px;
  padding:3px;border-radius:999px;
  background:var(--glass-thin);
  border:1px solid var(--glass-edge);
}
.lang-switch a{
  font-family:var(--font-head);font-weight:700;font-size:11.5px;
  color:var(--ink-3);
  padding:5px 10px;border-radius:999px;
  transition:color .2s ease, background-color .2s ease;
}
.lang-switch a:hover:not(.lang-active){color:var(--ink);}
.lang-switch a.lang-active{
  background:var(--glass-thick);
  color:var(--ink);
  box-shadow:var(--inner-top), var(--shadow-sm);
}

.nav-toggle{
  display:none;
  width:40px;height:40px;
  align-items:center;justify-content:center;
  flex-direction:column;gap:5px;
  background:var(--glass);
  border:1px solid var(--glass-edge);
  border-radius:12px;
  cursor:pointer;
  box-shadow:var(--shadow-sm);
}
.nav-toggle span{
  display:block;width:17px;height:1.5px;border-radius:2px;
  background:var(--ink);
  transition:transform .34s var(--ease), opacity .2s ease;
}
.nav-toggle.is-open span:nth-child(1){transform:translateY(6.5px) rotate(45deg);}
.nav-toggle.is-open span:nth-child(2){opacity:0;}
.nav-toggle.is-open span:nth-child(3){transform:translateY(-6.5px) rotate(-45deg);}

/* ---------- Hero ----------
   The animated header. Three brand-coloured blobs drift behind the content on long,
   offset loops, so the movement never repeats visibly and never pulls the eye off the
   headline. Blur is heavy enough that the shapes read as light, not as objects. */
/* The hero fills the first screen exactly, so the fold lands on its own bottom edge.
   Before this it ran 82px past an 800px viewport, which cut the hero image 22px short
   of its bottom and left the next band peeking on taller windows: the page looked like
   it had been cropped rather than composed. min-height rather than height, so long
   copy or a narrow column can still push it taller. svh, not vh, so a phone's
   collapsing address bar does not resize it mid-scroll. */
.hero{
  padding:clamp(32px, 4vw, 56px) 0 clamp(32px, 4vw, 56px);
  min-height:calc(100svh - var(--header-h));
  display:grid;
  align-content:center;
  overflow:hidden;
  isolation:isolate;
}
@supports not (height: 100svh){
  .hero{min-height:calc(100vh - var(--header-h));}
}
.hero::before,
.hero::after{
  content:'';
  position:absolute;
  z-index:-1;
  border-radius:50%;
  filter:blur(70px);
  will-change:transform;
  pointer-events:none;
}
.hero::before{
  width:52vw;height:52vw;max-width:760px;max-height:760px;
  top:-22%;left:-14%;
  background:radial-gradient(circle at 40% 40%, rgba(0,186,255,0.5), rgba(0,186,255,0) 68%);
  animation:aurora-a 26s var(--ease) infinite alternate;
}
.hero::after{
  width:46vw;height:46vw;max-width:660px;max-height:660px;
  top:-8%;right:-12%;
  background:radial-gradient(circle at 60% 40%, rgba(0,100,205,0.4), rgba(0,100,205,0) 68%);
  animation:aurora-b 32s var(--ease) infinite alternate;
}

@keyframes aurora-a{
  0%{transform:translate3d(0,0,0) scale(1);}
  50%{transform:translate3d(14%, 8%, 0) scale(1.12);}
  100%{transform:translate3d(4%, 16%, 0) scale(0.96);}
}
@keyframes aurora-b{
  0%{transform:translate3d(0,0,0) scale(1.04);}
  50%{transform:translate3d(-12%, 12%, 0) scale(0.94);}
  100%{transform:translate3d(-4%, 4%, 0) scale(1.1);}
}

.hero-grid{
  display:grid;grid-template-columns:1.06fr 0.94fr;
  gap:clamp(32px, 4vw, 56px);align-items:center;
}
.hero-grid.reverse{grid-template-columns:0.94fr 1.06fr;}
.hero-grid > *{min-width:0;}

.hero-actions{
  display:flex;gap:14px;align-items:center;flex-wrap:wrap;
  margin-top:30px;
}

/* The hero screenshot, presented as a pane of glass holding an image. */
.hero-visual{position:relative;}
/* Sizing only. The frame lives on .shot now, which wraps every screenshot: see the
   "Screenshots" block. */
.hero-visual img{
  /* Bounded by the viewport, not just by its column, so a tall portrait screenshot
     cannot push the hero past the fold it is supposed to fit inside. Height is the
     constraint and width follows, which keeps the aspect ratio exact. */
  max-height:calc(100svh - var(--header-h) - 140px);
  width:auto;
  margin-inline:auto;
}
@supports not (height: 100svh){
  .hero-visual img{max-height:calc(100vh - var(--header-h) - 140px);}
}
@supports not (height: 42svh){
  @media (max-width:1000px){ .hero-visual img{max-height:42vh;} }
}
/* Third blob, this one anchored to the image so it grounds it on the field. */
.hero-visual .glow{
  position:absolute;inset:-14% -10%;
  z-index:-1;
  border-radius:50%;
  background:radial-gradient(closest-side, rgba(0,186,255,0.34), rgba(240,60,90,0.1) 62%, transparent 78%);
  filter:blur(50px);
  will-change:transform;
  animation:aurora-c 24s var(--ease) infinite alternate;
  pointer-events:none;
}
@keyframes aurora-c{
  0%{transform:translate3d(0,0,0) scale(1);}
  100%{transform:translate3d(-6%, -8%, 0) scale(1.14);}
}

/* ---------- Trust bar ---------- */
/* ---------- Trust line ----------
   A single line of credibility copy, not a band. It used to have its own translucent
   background and a hairline top and bottom, which put a 101px full-width strip flush
   against the bottom of a full-height hero: it read as a stray toolbar or a cookie
   notice rather than as part of the page. No chrome now, generous space above and below,
   and the emphasised phrases carry it.

   It appears in two roles - after the hero on the home page, and between two sections on
   the product pages - and a chrome-less centred line works for both. */
.trust{
  padding:clamp(34px, 4.5vw, 58px) 0;
}
.trust p{
  text-align:center;
  color:var(--ink-3);
  font-size:14px;
  line-height:1.65;
  letter-spacing:0.005em;
  margin:0 auto;
  max-width:74ch;
  padding:0 24px;
}
.trust strong{color:var(--ink-2);font-weight:500;}

/* ---------- Badges ---------- */
.badge{
  display:inline-flex;align-items:center;gap:6px;
  font-family:var(--font-head);font-weight:700;
  font-size:10.5px;letter-spacing:0.07em;text-transform:uppercase;
  padding:5px 11px;border-radius:999px;
  border:1px solid transparent;
  white-space:nowrap;
}
.badge-live{background:rgba(0,186,255,0.12);color:var(--cyan-dark);border-color:rgba(0,186,255,0.24);}
.badge-flagship{background:rgba(240,60,90,0.1);color:var(--red-dark);border-color:rgba(240,60,90,0.22);}
.badge-soon{background:rgba(10,25,50,0.05);color:var(--ink-3);border-color:var(--line);}

/* ---------- Glass cards ---------- */
.card{
  background:var(--glass);
  -webkit-backdrop-filter:var(--glass-blur);
  backdrop-filter:var(--glass-blur);
  border:1px solid var(--glass-edge);
  border-radius:var(--r-lg);
  padding:28px;
  box-shadow:var(--inner-top), var(--shadow-sm);
  transition:transform .38s var(--ease), box-shadow .38s var(--ease), background-color .3s ease;
}
.card:hover{
  transform:translateY(-4px);
  background:var(--glass-thick);
  box-shadow:var(--inner-top), var(--shadow-md);
}
.card h3{margin-bottom:10px;}
.card p{color:var(--ink-3);font-size:14.5px;}

.module-card{display:flex;flex-direction:column;}
.module-card .badge{align-self:flex-start;margin-bottom:14px;}
.module-card .price-tag{
  margin-top:auto;padding-top:16px;
  border-top:1px solid var(--line-soft);
  font-size:13.5px;color:var(--ink-3);
}
.module-card .price-tag b{color:var(--ink);font-size:17px;font-family:var(--font-head);}
.module-card a.more{
  display:inline-flex;align-items:center;gap:6px;
  margin-top:16px;
  font-family:var(--font-head);font-weight:700;font-size:14px;
  color:var(--cyan-dark);
  transition:gap .26s var(--ease);
}
.module-card a.more:hover{gap:11px;}

/* ---------- Feature rows ---------- */
.feature-row{
  display:grid;grid-template-columns:1fr 1fr;
  gap:clamp(32px, 4.5vw, 60px);align-items:center;
  padding:clamp(36px, 4.5vw, 60px) 0;
  border-top:1px solid var(--line-soft);
}
.feature-row > *{min-width:0;}
.feature-row:first-of-type{border-top:none;padding-top:0;}
.feature-row.reverse{direction:rtl;}
.feature-row.reverse > *{direction:ltr;}
.feature-row h3{font-size:clamp(20px, 1.8vw, 25px);}
.feature-row ul{margin:20px 0 0;padding:0;list-style:none;}
.feature-row ul li{
  position:relative;padding-left:28px;margin-bottom:11px;
  color:var(--ink-2);font-size:15px;
}
.feature-row ul li::before{
  content:'';
  position:absolute;left:0;top:0.55em;
  width:15px;height:8px;
  border-left:2px solid var(--cyan-dark);
  border-bottom:2px solid var(--cyan-dark);
  border-radius:1px;
  transform:rotate(-45deg) translateY(-2px);
}

/* ---------- Steps ---------- */
.steps{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:26px;margin-top:8px;}
.step{
  background:var(--glass-thin);
  -webkit-backdrop-filter:var(--glass-blur);
  backdrop-filter:var(--glass-blur);
  border:1px solid var(--glass-edge);
  border-radius:var(--r);
  padding:24px;
  box-shadow:var(--inner-top), var(--shadow-sm);
}
.step .num{
  font-family:var(--font-head);font-weight:700;font-size:14px;
  color:#fff;
  background:linear-gradient(160deg, var(--cyan) 0%, var(--blue) 100%);
  width:32px;height:32px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  margin-bottom:16px;
  box-shadow:0 4px 12px -3px rgba(0,100,205,0.45), var(--inner-top);
}
.step h4{font-family:var(--font-head);font-weight:700;font-size:16.5px;margin:0 0 8px;color:var(--ink);}
.step p{color:var(--ink-3);font-size:14.5px;}

/* ---------- Section bands ----------
   Assigned in document order by the porter, so no two neighbours share a tint. Grey
   is a translucent band that lets the field through; dark is deep glass. */
.section-grey{
  background:linear-gradient(180deg, rgba(255,255,255,0.55) 0%, rgba(255,255,255,0.3) 100%);
  -webkit-backdrop-filter:var(--glass-blur);
  backdrop-filter:var(--glass-blur);
  border-top:1px solid var(--line-soft);
  border-bottom:1px solid var(--line-soft);
}
.section-dark{
  background:linear-gradient(160deg, #0d1521 0%, #131f2f 100%);
  color:rgba(255,255,255,0.76);
}
.section-dark h2,.section-dark h3,.section-dark h4{color:#fff;}
.section-dark p{color:rgba(255,255,255,0.72);}
.section-dark .lede{color:rgba(255,255,255,0.66);}
.section-dark .eyebrow{color:var(--cyan);}
.section-dark strong,.section-dark b{color:#fff;}

/* ---------- Closing CTA banner ---------- */
.cta-banner{
  position:relative;
  overflow:hidden;
  background:linear-gradient(150deg, #0c1420 0%, #16243a 58%, #1b2c46 100%);
  border-radius:var(--r-xl);
  padding:clamp(32px, 4.5vw, 56px);
  display:flex;align-items:center;justify-content:space-between;
  gap:28px;flex-wrap:wrap;
  box-shadow:var(--shadow-lg);
}
/* A single soft brand highlight so the slab is not flat. */
.cta-banner::before{
  content:'';
  position:absolute;
  width:60%;padding-bottom:60%;
  top:-30%;right:-12%;
  border-radius:50%;
  background:radial-gradient(closest-side, rgba(0,186,255,0.3), transparent 72%);
  filter:blur(40px);
  pointer-events:none;
}
.cta-banner > *{position:relative;z-index:1;}
.cta-banner h2{color:#fff;margin-bottom:10px;font-size:clamp(23px, 2.4vw, 31px);}
.cta-banner p{color:rgba(255,255,255,0.72);margin-bottom:0;}

/* ---------- Pricing ---------- */
.pricing-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:22px;align-items:stretch;}
.price-card{
  position:relative;
  display:flex;flex-direction:column;
  background:var(--glass);
  -webkit-backdrop-filter:var(--glass-blur);
  backdrop-filter:var(--glass-blur);
  border:1px solid var(--glass-edge);
  border-radius:var(--r-lg);
  /* Room reserved for the badge on every card, badged or not, so the three headings
     line up across the row. */
  padding:58px 30px 30px;
  box-shadow:var(--inner-top), var(--shadow-sm);
  transition:transform .38s var(--ease), box-shadow .38s var(--ease);
}
.price-card:hover{transform:translateY(-4px);box-shadow:var(--inner-top), var(--shadow-md);}
.price-card.featured{
  border-color:rgba(0,186,255,0.5);
  box-shadow:var(--inner-top), 0 0 0 4px rgba(0,186,255,0.12), var(--shadow-md);
}
.price-card .badge{position:absolute;top:26px;left:30px;}
.price-card h3{margin-bottom:4px;}
.price-card .tagline{color:var(--ink-3);font-size:13.5px;margin-bottom:22px;}
.price-line{
  display:flex;justify-content:space-between;align-items:baseline;gap:12px;
  padding:11px 0;
  border-top:1px solid var(--line-soft);
  font-size:14px;color:var(--ink-3);
}
.price-line:first-of-type{border-top:none;}
.price-line b{color:var(--ink);font-size:17px;font-family:var(--font-head);font-weight:700;}
.price-card ul{list-style:none;padding:0;margin:22px 0 0;flex:1;}
.price-card ul li{
  position:relative;padding-left:26px;margin-bottom:10px;
  font-size:14px;color:var(--ink-2);
}
.price-card ul li::before{
  content:'';
  position:absolute;left:0;top:0.5em;
  width:13px;height:7px;
  border-left:2px solid var(--cyan-dark);
  border-bottom:2px solid var(--cyan-dark);
  transform:rotate(-45deg) translateY(-2px);
}
.price-card .btn{margin-top:24px;}

.tbd-box{
  border:1px dashed rgba(10,25,50,0.18);
  border-radius:var(--r-sm);
  background:rgba(255,255,255,0.4);
  padding:18px;text-align:center;margin-bottom:6px;
}
.tbd-box .tbd{font-family:var(--font-head);font-weight:700;font-size:22px;color:var(--cyan-dark);}
.tbd-box .tbd-note{font-size:12.5px;color:var(--ink-3);margin-top:3px;}

/* ---------- Why / how / what ---------- */
.whw{
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1px;
  background:var(--line-soft);
  border:1px solid var(--glass-edge);
  border-radius:var(--r-lg);
  overflow:hidden;
  box-shadow:var(--inner-top), var(--shadow-sm);
}
.whw-col{
  padding:30px;
  background:var(--glass);
  -webkit-backdrop-filter:var(--glass-blur);
  backdrop-filter:var(--glass-blur);
}
.whw-tag{
  display:inline-block;margin-bottom:12px;
  font-family:var(--font-head);font-weight:700;
  font-size:11px;letter-spacing:0.11em;text-transform:uppercase;
  color:var(--cyan-dark);
}
.whw-col h3{font-size:19px;margin-bottom:10px;}
.whw-col p{color:var(--ink-3);font-size:14.5px;}

/* ---------- Incident readout ---------- */
.scenario{
  background:var(--glass-thick);
  -webkit-backdrop-filter:var(--glass-blur);
  backdrop-filter:var(--glass-blur);
  border:1px solid var(--glass-edge);
  border-radius:var(--r-lg);
  overflow:hidden;
  box-shadow:var(--inner-top), var(--shadow-md);
}
.scenario-head{
  background:linear-gradient(160deg, #0d1521 0%, #16243a 100%);
  color:#fff;
  padding:18px 26px;
  display:flex;align-items:center;justify-content:space-between;
  gap:12px;flex-wrap:wrap;
}
.scenario-head .tag{
  font-family:var(--font-head);font-weight:700;
  font-size:10.5px;letter-spacing:0.1em;text-transform:uppercase;
  color:var(--cyan);
}
.scenario-head .ts{
  font-family:ui-monospace, 'SF Mono', 'Roboto Mono', monospace;
  font-size:12.5px;color:rgba(255,255,255,0.62);
}
.scenario-body{padding:24px 26px 8px;}
.scenario-line{
  display:flex;gap:16px;
  padding:14px 0;
  border-top:1px solid var(--line-soft);
}
.scenario-line:first-child{border-top:none;}
.scenario-line .dot{
  width:9px;height:9px;border-radius:50%;
  background:var(--cyan);
  box-shadow:0 0 0 3px rgba(0,186,255,0.16);
  margin-top:8px;flex-shrink:0;
}
.scenario-line .dot.warn{background:var(--red);box-shadow:0 0 0 3px rgba(240,60,90,0.16);}
.scenario-line .dot.ok{background:#18a463;box-shadow:0 0 0 3px rgba(24,164,99,0.16);}
.scenario-line p{margin:0;font-size:14.5px;color:var(--ink-2);}
.scenario-result{
  margin:16px 26px 26px;
  padding:20px 22px;
  border-radius:var(--r-sm);
  background:rgba(0,186,255,0.07);
  border:1px solid rgba(0,186,255,0.2);
}
.scenario-result .label{
  display:block;margin-bottom:6px;
  font-family:var(--font-head);font-weight:700;
  font-size:11px;letter-spacing:0.08em;text-transform:uppercase;
  color:var(--cyan-dark);
}
.scenario-result p{margin:0;font-size:15px;color:var(--ink);font-weight:500;}

/* ---------- Without / with ---------- */
.compare{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:28px;}
.compare-col{
  border-radius:var(--r);
  padding:26px;
  border:1px solid var(--glass-edge);
  -webkit-backdrop-filter:var(--glass-blur);
  backdrop-filter:var(--glass-blur);
  box-shadow:var(--inner-top), var(--shadow-sm);
}
.compare-col.without{background:rgba(255,255,255,0.42);}
.compare-col.with{background:rgba(0,186,255,0.08);border-color:rgba(0,186,255,0.26);}
.compare-col .clock{font-family:var(--font-head);font-weight:700;font-size:27px;margin-bottom:6px;letter-spacing:-0.02em;}
.compare-col.without .clock{color:var(--ink-4);}
.compare-col.with .clock{color:var(--cyan-dark);}
.compare-col h4{
  font-family:var(--font-head);font-weight:700;font-size:13px;
  margin:0 0 10px;text-transform:uppercase;letter-spacing:0.06em;color:var(--ink-3);
}
.compare-col p{font-size:14.5px;color:var(--ink-2);}

/* ---------- Persona cards ---------- */
.persona-split{display:grid;grid-template-columns:1fr 1fr;gap:22px;}
.persona-card{
  background:var(--glass);
  -webkit-backdrop-filter:var(--glass-blur);
  backdrop-filter:var(--glass-blur);
  border:1px solid var(--glass-edge);
  border-radius:var(--r-lg);
  padding:30px;
  box-shadow:var(--inner-top), var(--shadow-sm);
}
.persona-card .role{margin-bottom:18px;padding-bottom:16px;border-bottom:1px solid var(--line-soft);}
.persona-card .role h4{margin:0;font-size:17px;font-family:var(--font-head);font-weight:700;color:var(--ink);}
.persona-card .role span{display:block;font-size:12.5px;color:var(--ink-4);margin-top:2px;}
.persona-card ul{list-style:none;padding:0;margin:0;}
.persona-card ul li{
  position:relative;padding-left:26px;margin-bottom:13px;
  font-size:14.5px;color:var(--ink-2);
}
.persona-card ul li:last-child{margin-bottom:0;}
.persona-card ul li::before{
  content:'';
  position:absolute;left:0;top:0.55em;
  width:13px;height:7px;
  border-left:2px solid var(--cyan-dark);
  border-bottom:2px solid var(--cyan-dark);
  transform:rotate(-45deg) translateY(-2px);
}

/* ---------- Data callout ---------- */
.data-callout{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:20px;margin-top:8px;}
.data-stat{
  text-align:center;padding:26px 18px;
  background:var(--glass-thin);
  -webkit-backdrop-filter:var(--glass-blur);
  backdrop-filter:var(--glass-blur);
  border:1px solid var(--glass-edge);
  border-radius:var(--r);
  box-shadow:var(--inner-top), var(--shadow-sm);
}
.data-stat .num{
  font-family:var(--font-head);font-weight:700;font-size:clamp(28px, 3vw, 36px);
  color:var(--ink);letter-spacing:-0.03em;line-height:1.05;
}
.data-stat .num span{
  background:linear-gradient(160deg, var(--cyan) 0%, var(--blue) 100%);
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;color:var(--cyan-dark);
}
.data-stat .lbl{font-size:13px;color:var(--ink-3);margin-top:6px;}
.source-note{font-size:12px;color:var(--ink-4);text-align:center;margin-top:16px;}

/* ---------- KPI catalogue ---------- */
.kpi-groups{display:flex;flex-direction:column;gap:clamp(34px, 4vw, 52px);}
.kpi-group-head{margin-bottom:20px;max-width:72ch;}
.kpi-group-head h3{font-size:19px;margin-bottom:5px;}
.kpi-group-head p{margin:0;color:var(--ink-3);font-size:14px;}
.kpi-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px;}
.kpi-card{
  background:var(--glass);
  -webkit-backdrop-filter:var(--glass-blur);
  backdrop-filter:var(--glass-blur);
  border:1px solid var(--glass-edge);
  border-radius:var(--r);
  padding:22px;
  box-shadow:var(--inner-top), var(--shadow-sm);
  transition:transform .34s var(--ease), box-shadow .34s var(--ease);
}
.kpi-card:hover{transform:translateY(-3px);box-shadow:var(--inner-top), var(--shadow-md);}
.kpi-card h4{font-size:15px;margin:0 0 8px;font-family:var(--font-head);font-weight:700;color:var(--ink);}
.kpi-card > p{font-size:14px;margin:0 0 14px;color:var(--ink-2);}
.kpi-card .best{font-size:13.5px;padding-top:13px;border-top:1px solid var(--line-soft);}
.kpi-card .best .lbl{
  display:block;margin-bottom:5px;
  font-family:var(--font-head);font-weight:700;
  font-size:10px;letter-spacing:0.09em;text-transform:uppercase;color:var(--ink-4);
}
.kpi-card .best.priced .lbl{color:var(--cyan-dark);}
.kpi-card .best p{margin:0;font-size:13.5px;color:var(--ink-3);}
.kpi-card .best b{color:var(--ink);}

/* ---------- FAQ ---------- */
.faq-list{max-width:820px;}
.faq-item{
  background:var(--glass-thin);
  -webkit-backdrop-filter:var(--glass-blur);
  backdrop-filter:var(--glass-blur);
  border:1px solid var(--glass-edge);
  border-radius:var(--r);
  padding:4px 22px;
  margin-bottom:10px;
  box-shadow:var(--shadow-sm);
  transition:background-color .3s ease, box-shadow .3s ease;
}
.faq-item[open]{background:var(--glass-thick);box-shadow:var(--inner-top), var(--shadow-md);}
.faq-item summary{
  font-family:var(--font-head);font-weight:700;font-size:16px;
  color:var(--ink);
  cursor:pointer;list-style:none;
  display:flex;justify-content:space-between;align-items:center;gap:16px;
  padding:18px 0;
}
.faq-item summary::-webkit-details-marker{display:none;}
/* Plus that rotates into a minus, rather than swapping glyphs. */
.faq-item summary::after{
  content:'';
  flex-shrink:0;
  width:13px;height:13px;
  background:
    linear-gradient(var(--cyan-dark), var(--cyan-dark)) center/13px 2px no-repeat,
    linear-gradient(var(--cyan-dark), var(--cyan-dark)) center/2px 13px no-repeat;
  transition:transform .34s var(--ease);
}
.faq-item[open] summary::after{transform:rotate(90deg);background-size:13px 2px, 0 0;}
.faq-item p{margin:0 0 20px;color:var(--ink-3);font-size:14.5px;max-width:70ch;}

/* ---------- Footer ---------- */
footer.site{
  margin-top:0;
  padding:40px 0 22px;
  background:linear-gradient(170deg, #0b1220 0%, #101a2a 100%);
  color:rgba(255,255,255,0.66);
}
.footer-grid{
  display:grid;grid-template-columns:1.6fr 1fr 1fr;
  gap:24px 32px;margin-bottom:26px;
}
.footer-grid h4{
  color:#fff;font-size:11.5px;text-transform:uppercase;letter-spacing:0.09em;
  margin-bottom:12px;font-family:var(--font-head);font-weight:700;
}
.footer-grid a{
  display:block;color:rgba(255,255,255,0.6);font-size:13.5px;margin-bottom:8px;
  transition:color .2s ease;
}
.footer-grid a:hover{color:var(--cyan);}
.footer-brand .brand{margin-bottom:10px;}
.footer-brand .brand .word{color:#fff;}
.footer-brand p{color:rgba(255,255,255,0.45);font-size:13px;max-width:34ch;margin:0;}
.footer-bottom{
  border-top:1px solid rgba(255,255,255,0.1);
  padding-top:18px;
  display:flex;justify-content:space-between;flex-wrap:wrap;gap:12px;
  font-size:12.5px;color:rgba(255,255,255,0.4);
}

/* ---------- Scroll reveal ----------
   Only armed once landing.js has added .has-js to <html>, so with scripting blocked
   every section is simply visible. The observer then adds .in-view. */
.has-js .reveal{
  opacity:0;
  transform:translateY(18px);
  transition:opacity .7s var(--ease), transform .7s var(--ease);
}
.has-js .reveal.in-view{opacity:1;transform:none;}

/* =========================================================
   Hero scene

   The view down a warehouse aisle, drawn rather than photographed: two banks of pallet
   racking receding to a vanishing point, roof lights, linked access points, live scan
   blips, and CaptureTech's C in front. Structure and the projection maths are in
   Components/Shared/HeroScene.razor; every per-element position arrives as a custom
   property so this stays generic.

   No card. The scene has no background, border or shadow of its own; it fades out at
   its edges into the hero, so it reads as part of the header rather than as a panel
   dropped into it.

   --u is the scene's own unit, one percent of its width. Every size is a multiple of it,
   so the whole composition scales as one piece and no part needs a media query.
   ========================================================= */
.hero-scene{
  --u:1px;
  position:relative;
  width:100%;
  /* Bounded by the height available above the fold as well as by its column. With a
     fixed aspect ratio the height cap has to be expressed as a width, or the ratio and
     the cap fight each other. */
  max-width:min(560px, calc((100svh - var(--header-h) - 130px) / 1.02));
  aspect-ratio:1 / 1.02;
  margin-inline:auto;
  container-type:inline-size;
}

/* --u lives on the stage, not on .hero-scene: an element cannot respond to its own
   container query, and cqw resolves against the nearest container ancestor. Setting it
   one level down is what makes 1cqw mean "one percent of the scene's width" here. The
   px value is the fallback for a browser without container query units, tuned to the
   scene's desktop width so it degrades to the right proportions rather than to dust. */
.scene-stage{
  --u:5.6px;
  position:absolute;inset:0;
  isolation:isolate;
  /* The edge fade is what removes the card. Three layers intersected, so the racking and
     the floor dissolve into the hero on every side instead of stopping at a border.

     There is a dedicated horizontal layer because the radial alone could not do it. At
     115% wide its horizontal radius exceeded the box, so the left and right edges sat at
     0.5/1.15 = 0.435 of the radius, inside the solid 46% stop: the sides were fully
     opaque and the nearest bay in each bank was sliced off with a hard vertical edge.
     The radial is narrower now and only softens the corners.

     mask-repeat is explicit and load-bearing. It defaults to repeat, which tiles the
     gradients outside the box, and the floor bleeds 40% past each side: it was picking up
     a second copy of the fade and banding at the edges. no-repeat leaves everything
     outside the box unpainted, which is the clean clip this wants. */
  -webkit-mask:
    linear-gradient(90deg, transparent 0%, #000 11%, #000 89%, transparent 100%),
    radial-gradient(96% 104% at 50% 44%, #000 40%, transparent 90%),
    linear-gradient(180deg, transparent 0%, #000 10%, #000 88%, transparent 100%);
  -webkit-mask-repeat:no-repeat;
  -webkit-mask-composite:source-in;
  mask:
    linear-gradient(90deg, transparent 0%, #000 11%, #000 89%, transparent 100%),
    radial-gradient(96% 104% at 50% 44%, #000 40%, transparent 90%),
    linear-gradient(180deg, transparent 0%, #000 10%, #000 88%, transparent 100%);
  mask-repeat:no-repeat;
  mask-composite:intersect;
}
@supports (width: 1cqw){
  .scene-stage{--u:1cqw;}
}

/* ---------- Materials ----------
   Two registers, kept apart. The building is physical, so it is drawn in galvanised
   steel, concrete, softwood and cardboard, all desaturated. The network over it is
   digital, so it keeps brand cyan, and the scans keep brand red. An earlier version drew
   the racking in the same saturated cyan as the network and the result read as a diagram
   of a warehouse rather than as a warehouse: nothing had a material, so nothing had
   weight, and the only depth cue left was scale. */
.scene-stage{
  --steel:#5f7288;         /* galvanised upright, in shade */
  --steel-lit:#8fa2b6;     /* the edge of it facing the roof lights */
  --steel-beam:#54687f;    /* box-section beam, a shade darker than the frames */
  --wood:#7e7669;
  --wood-lit:#968d7e;
  --carton:#c9c0b0;        /* default; the tone classes override it per pallet */
}

/* ---------- Floor ----------
   Polished concrete, laid back in perspective and meeting the racking at the same
   vanishing point. The joints sit on an inner element twice the height and are translated
   by exactly one cell, so the loop is seamless and the animation is a transform rather
   than a background-position.

   Coarse and grey, not a fine cyan mesh: a warehouse floor is saw-cut slabs a couple of
   metres across, and the fine glowing grid was the most synthetic thing in the frame. */
.scene-floor{
  position:absolute;
  left:-40%;right:-40%;bottom:0;
  height:62%;
  perspective:calc(var(--u) * 40);
  perspective-origin:50% 0%;
  -webkit-mask:linear-gradient(180deg, transparent, #000 26%, #000 82%, transparent);
  mask:linear-gradient(180deg, transparent, #000 26%, #000 82%, transparent);
}
.scene-floor-grid{
  position:absolute;
  left:0;top:0;width:100%;height:200%;
  transform-origin:50% 0%;
  background-image:
    linear-gradient(90deg, rgba(70,92,116,0.20) 0 1px, transparent 1px),
    linear-gradient(180deg, rgba(70,92,116,0.14) 0 1px, transparent 1px);
  background-size:calc(var(--u) * 11) calc(var(--u) * 11);
  animation:scene-floor-drift 15s linear infinite;
  will-change:transform;
}
@keyframes scene-floor-drift{
  from{transform:rotateX(64deg) translateY(0);}
  to{transform:rotateX(64deg) translateY(calc(var(--u) * 11));}
}

/* The aisle: a band of floor between the two banks, narrowing to the vanishing point.
   Drawn as a trapezoid so the convergence is exact rather than implied. What is painted
   on it is the sheen of the roof fittings on sealed concrete, which is why it is warm and
   brightest down the centre line rather than an even cyan wash. */
.scene-aisle{
  position:absolute;
  left:0;right:0;top:40%;bottom:0;
  clip-path:polygon(20% 100%, 80% 100%, 52.5% 0%, 47.5% 0%);
  background:
    linear-gradient(90deg, transparent 34%, rgba(255,250,240,0.3) 50%, transparent 66%),
    linear-gradient(0deg, rgba(150,168,188,0.26), rgba(150,168,188,0.05) 72%, transparent);
}
/* Painted aisle markings. Every racked aisle has them, they are the clearest single cue
   that this is a floor being worked rather than a gradient, and because they converge on
   the same vanishing point they reinforce the perspective for free. Trapezoids rather
   than rotated rules, so the convergence is exact and needs no trigonometry. */
.scene-aisle::before,
.scene-aisle::after{
  content:"";
  position:absolute;inset:0;
  background:linear-gradient(0deg, rgba(226,196,120,0.5), rgba(226,196,120,0.16) 60%, transparent 88%);
}
.scene-aisle::before{clip-path:polygon(24.5% 100%, 26.3% 100%, 49.6% 2%, 49.2% 2%);}
.scene-aisle::after{clip-path:polygon(73.7% 100%, 75.5% 100%, 50.8% 2%, 50.4% 2%);}

/* ---------- Roof ----------
   Lattice trusses spanning the aisle, with the light fittings hung under them. The
   trusses are offset half a step from the lights so the two do not merge into one
   object. */
.scene-roof{position:absolute;inset:0;}

/* Longitudinal roof rails, running away from the viewer to the same vanishing point the
   floor markings use. These are what make the roof a roof. Without them the cross
   trusses were a column of horizontal bars of decreasing width stacked in empty space,
   centred and unconnected, and they read as skeleton-loading placeholders rather than as
   structure. Given two converging rails, the same bars read as rungs receding, and the
   ceiling snaps into the same space as the aisle. Trapezoids for the same reason the
   floor markings are trapezoids: the convergence is then exact. */
.scene-roof::before,
.scene-roof::after{
  content:"";
  position:absolute;inset:0;
  background:linear-gradient(180deg, var(--steel) 0%, rgba(95,114,136,0.3) 55%, transparent 88%);
  opacity:0.34;
}
.scene-roof::before{clip-path:polygon(15% 1%, 16.6% 1%, 48.6% 39%, 48.1% 39%);}
.scene-roof::after{clip-path:polygon(83.4% 1%, 85% 1%, 51.9% 39%, 51.4% 39%);}

.truss{
  position:absolute;
  left:var(--x);top:var(--y);
  width:var(--w);
  height:calc(var(--u) * 1.7 * var(--s));
  /* A lit top chord over a shaded web. The web members started out as a diagonal repeat,
     for the same reason the frames have one, but a truss is 2.4px tall at the far end and
     the members came out sub-pixel: they dithered instead of drawing, and eight of them
     stacked up read as a barcode across the roof. A two-tone bar is what a purlin looks
     like at this size anyway.

     The opacity ramp starts below zero so the far trusses fade out entirely rather than
     crowding the vanishing point with hairlines. */
  background-image:linear-gradient(180deg, var(--steel-lit) 0 38%, var(--steel) 38%);
  opacity:calc(-0.06 + 0.3 * var(--s));
}

.light{
  position:absolute;
  left:var(--x);top:var(--y);
  width:var(--w);
  height:calc(var(--u) * 1.1 * var(--s));
  border-radius:calc(var(--u) * 0.15);
  /* A warm-white linear fitting with dark end caps, not a cyan neon tube. The bloom is a
     third of what it was: high-bay lighting reads as bright, not as glowing. */
  background:
    linear-gradient(90deg, rgba(90,105,125,0.9) 0 4%, rgba(255,250,238,0.96) 9%,
                    rgba(255,250,238,0.96) 91%, rgba(90,105,125,0.9) 96%);
  box-shadow:0 0 calc(var(--u) * 1.6 * var(--s)) rgba(255,242,214,0.6);
  opacity:calc(0.35 + 0.5 * var(--s));
  animation:scene-light 6s var(--ease) infinite;
  animation-delay:var(--d);
}
@keyframes scene-light{
  0%, 100%{opacity:calc(0.32 + 0.46 * var(--s));}
  50%{opacity:calc(0.42 + 0.54 * var(--s));}
}

/* ---------- Racking ----------
   One bay is an upright frame with two posts carrying three beam levels, each level
   holding two pallets. Position, size and convergence all come from the one scale factor
   the component computed, so a bay never has to be nudged by hand.

   Opacity rises with scale: near bays are solid, far bays wash out into the depth. */
.scene-racks{position:absolute;inset:0;}
.bay{
  position:absolute;
  left:var(--x);bottom:var(--y);
  width:var(--w);height:var(--h);
  opacity:calc(0.34 + 0.62 * var(--s));
}

/* Frame bracing: the diagonal steel between the front and rear uprights of one frame.
   Two opposing diagonal repeats give the diamond lattice of the real thing out of one
   element. It sits behind the stock, so it shows through the gaps between levels and
   through the empty slots, which is exactly where you see it down a real aisle.

   Near bays only, and the members are deliberately fat. Scaling the pitch by --s put the
   far bays on a 3.6px pitch with a 0.4px member, and a sub-pixel stripe on a pitch that
   fine does not render as steel: it dithers, and the whole rack picked up a speckled
   texture that looked like image noise. Below reading distance there is nothing to draw. */
.is-near .bay-frame{
  position:absolute;
  left:calc(var(--u) * 0.9 * var(--s));right:calc(var(--u) * 0.9 * var(--s));
  top:0;bottom:0;
  /* One direction, not two. Crossing both diagonals draws the diamond lattice the real
     frame has, but at this size the diamonds close up into a fine screen: the racking
     came out looking like it had a mesh panel bolted across it, worst of all in the open
     band above the top beam where nothing covers it. A single run of parallel diagonals
     reads as bracing at any size. It mirrors with the bank so both sides lean the way the
     perspective says they should. */
  background-image:
    repeating-linear-gradient(64deg,
      transparent 0 calc(var(--u) * 4.4 * var(--s)),
      var(--steel) calc(var(--u) * 4.4 * var(--s)) calc(var(--u) * 4.95 * var(--s)));
  opacity:0.26;
}
.bay-right .bay-frame{
  background-image:
    repeating-linear-gradient(-64deg,
      transparent 0 calc(var(--u) * 4.4 * var(--s)),
      var(--steel) calc(var(--u) * 4.4 * var(--s)) calc(var(--u) * 4.95 * var(--s)));
}

/* The two uprights. Frames lean very slightly with the perspective, which is enough to
   suggest the depth of the frame without modelling it. The posts are the second and third
   children now: .bay-frame is first, so it paints behind everything. */
.bay-post{
  position:absolute;top:0;bottom:0;
  width:calc(var(--u) * 1.1 * var(--s));
  /* A lit edge facing the aisle and a shaded one away from it. A flat fill leaves the
     posts looking like drawn lines rather than like folded steel sections. */
  background:linear-gradient(90deg, var(--steel-lit) 0 26%, var(--steel) 60%, rgba(46,58,72,0.9));
  border-radius:calc(var(--u) * 0.15);
}
.bay-post:nth-child(2){left:0;}
.bay-post:nth-child(3){right:0;}
.bay-left .bay-post:nth-child(3){transform:scaleY(0.94);transform-origin:bottom;}
.bay-right .bay-post:nth-child(2){transform:scaleY(0.94);transform-origin:bottom;}

/* The rear upright of the same frame, seen past the front one. Free depth: no extra
   element, and it is what makes a frame read as a frame rather than as a post. */
.bay-post::after{
  content:"";
  position:absolute;top:calc(var(--u) * 0.6 * var(--s));bottom:0;
  left:calc(var(--u) * 1.5 * var(--s));
  width:calc(var(--u) * 0.75 * var(--s));
  background:var(--steel);
  opacity:0.5;
}
.bay-right .bay-post::after{left:auto;right:calc(var(--u) * 1.5 * var(--s));}

/* Baseplate. Uprights are bolted to the slab through a wider footplate, and the small
   flare at floor level is what stops the frames looking like they are hovering. */
.bay-post::before{
  content:"";
  position:absolute;bottom:0;left:calc(var(--u) * -0.45 * var(--s));
  width:calc(var(--u) * 2 * var(--s));
  height:calc(var(--u) * 0.5 * var(--s));
  background:var(--steel);
  border-radius:calc(var(--u) * 0.1);
}

/* A beam level: the beam itself, plus the pallets standing on it. Box-section beams have
   a rolled top lip that catches the light and a shadowed underside, so the fill runs light
   to dark top to bottom rather than left to right. */
.bay-level{
  position:absolute;
  left:0;right:0;
  bottom:var(--lv);
  height:calc(var(--u) * 0.85 * var(--s));
  background:linear-gradient(180deg, var(--steel-lit) 0 22%, var(--steel-beam) 55%, rgba(40,52,66,0.85));
  border-radius:calc(var(--u) * 0.1);
  display:flex;align-items:flex-end;justify-content:space-between;
  padding:0 calc(var(--u) * 0.9 * var(--s));
}
/* Rack location label, on the near bays only. A small light tag on the beam at the aisle
   end, which is where they are in every warehouse and is a detail the eye recognises
   before it can name it. */
.is-near .bay-level::after{
  content:"";
  position:absolute;
  bottom:calc(var(--u) * -0.5 * var(--s));
  width:calc(var(--u) * 2.4 * var(--s));
  height:calc(var(--u) * 1.2 * var(--s));
  background:linear-gradient(180deg, rgba(255,255,255,0.9), rgba(214,222,232,0.85));
  border-radius:calc(var(--u) * 0.1);
  box-shadow:0 0 0 1px rgba(60,78,98,0.35);
}
.bay-left.is-near .bay-level::after{right:calc(var(--u) * 1.2 * var(--s));}
.bay-right.is-near .bay-level::after{left:calc(var(--u) * 1.2 * var(--s));}

/* ---------- Stock ----------
   Detail lives here rather than in the structure. Racking is manufactured, so every frame
   is identical; stock is not, so every pallet gets its own width, load height, load type
   and carton tone from the component. That asymmetry is most of the difference between a
   warehouse and a pattern.

   A pallet is a softwood base carrying a load. Both are drawn, because a load floating
   with no pallet under it was the tell the old flat blue blocks could not shake. */
.pallet{
  position:relative;
  display:block;
  align-self:flex-end;
  width:var(--pw);
  height:calc(var(--u) * var(--lh) * var(--s));
  margin-bottom:calc(var(--u) * 0.85 * var(--s));
}
/* Held in the layout rather than removed, so the stock beside it keeps its place. */
.pallet.is-empty{visibility:hidden;height:0;}

/* The softwood pallet. The two dark slots are the fork openings between its three bearer
   blocks; at this size they are the whole of what says "pallet". */
.skid{
  position:absolute;
  left:-4%;right:-4%;bottom:0;
  height:calc(var(--u) * 1.5 * var(--s));
  background-color:var(--wood);
  background-image:
    linear-gradient(180deg, var(--wood-lit) 0 26%, transparent 26%),
    linear-gradient(90deg,
      transparent 0 27%, rgba(28,20,12,0.55) 27% 37%,
      transparent 37% 63%, rgba(28,20,12,0.55) 63% 73%, transparent 73%);
}

/* The load. Lit across the top from the roof fittings, shaded down the side away from the
   aisle, which is what gives a rectangle a third dimension. */
.load{
  position:absolute;
  left:0;right:0;top:0;
  bottom:calc(var(--u) * 1.5 * var(--s));
  background-color:var(--carton);
  background-image:
    linear-gradient(180deg, rgba(255,255,255,0.26) 0, rgba(255,255,255,0.08) 14%, transparent 28%),
    linear-gradient(90deg, transparent 60%, rgba(30,42,58,0.17) 100%);
  border-radius:calc(var(--u) * 0.08);
  box-shadow:inset 0 0 0 1px rgba(46,62,82,0.18);
}
/* The shading flips with the bank, so both sides are lit from the aisle. */
.bay-right .load{
  background-image:
    linear-gradient(180deg, rgba(255,255,255,0.26) 0, rgba(255,255,255,0.08) 14%, transparent 28%),
    linear-gradient(270deg, transparent 60%, rgba(30,42,58,0.17) 100%);
}

/* Shipping label, on the face turned towards the aisle. */
.load::before{
  content:"";
  position:absolute;
  bottom:22%;
  width:34%;height:20%;
  background:rgba(255,255,255,0.5);
  border-radius:calc(var(--u) * 0.06);
}
.bay-left .load::before{right:12%;}
.bay-right .load::before{left:12%;}

/* Carton tones. Four, because a bank of identically coloured cases is the same mistake as
   a bank of identical pallets, one register up. */
.tone-1{--carton:#c7d0da;}
.tone-2{--carton:#bac4cf;}
.tone-3{--carton:#d1d9e2;}
.tone-4{--carton:#b0bac6;}

/* Stacked cases: the seam where the upper layer sits on the lower one. */
.load-stack .load::after{
  content:"";
  position:absolute;
  left:0;right:0;top:46%;
  height:calc(var(--u) * 0.22 * var(--s));
  background:rgba(40,54,72,0.24);
}
/* One large case: no seam, a strap around it instead. */
.load-case .load::after{
  content:"";
  position:absolute;
  left:32%;top:0;bottom:0;
  width:calc(var(--u) * 0.3 * var(--s));
  background:rgba(40,54,72,0.2);
}
/* Shrink-wrapped: a diagonal sheen off the film, and the corners pulled in at the top the
   way a wrapped stack tapers. */
/* Shrink-wrapped. The film used to get a bright diagonal sheen across it, and at this size
   that was the glariest thing in the scene: a hard white streak over a quarter of the loads.
   The tapered corners are enough to say "wrapped" on their own. */
.load-wrapped .load{
  border-radius:calc(var(--u) * 0.3) calc(var(--u) * 0.3) 0 0;
}
.load-wrapped .load::before{opacity:0.55;}

/* Scan markers sit on the stock itself: a marker on the case being read, pulsing on its
   own cadence, rather than dots on an empty floor. Scaled by the bay's own --s, which it
   inherits, so a scan on a near pallet is bigger than one further down the aisle. */
.scan{
  position:absolute;
  left:50%;top:34%;
  width:calc(var(--u) * 1.9 * var(--s));
  height:calc(var(--u) * 1.9 * var(--s));
  margin:calc(var(--u) * -0.95 * var(--s)) 0 0 calc(var(--u) * -0.95 * var(--s));
  border-radius:50%;
  background:var(--red);
  box-shadow:0 0 calc(var(--u) * 1.6 * var(--s)) rgba(240,60,90,0.9);
  animation:scene-scan 4.8s var(--ease) infinite;
  animation-delay:var(--d);
}
/* The read itself: a ring leaving the marker as the case is counted. */
.scan::after{
  content:"";
  position:absolute;inset:0;
  border-radius:50%;
  border:1px solid rgba(240,60,90,0.8);
  animation:scene-scan-ring 4.8s var(--ease) infinite;
  animation-delay:var(--d);
}
@keyframes scene-scan{
  0%, 4%{opacity:0;transform:scale(0.5);}
  10%{opacity:1;transform:scale(1);}
  40%{opacity:1;transform:scale(1);}
  52%, 100%{opacity:0;transform:scale(0.7);}
}
@keyframes scene-scan-ring{
  0%, 8%{opacity:0;transform:scale(0.6);}
  14%{opacity:0.9;transform:scale(1);}
  46%, 100%{opacity:0;transform:scale(4);}
}

/* ---------- Depth haze ----------
   Air, dust and distance wash out the far end of any real aisle. Without it the bays near
   the vanishing point stay as crisp as the ones in front, and nothing on a real floor
   does that. Sits over the racking and under the network, so the far bays dissolve into
   the field while the overlay stays sharp. */
.scene-haze{
  position:absolute;inset:0;
  pointer-events:none;
  background:radial-gradient(34% 26% at 50% 40%,
    rgba(244,248,252,0.9) 0%, rgba(244,248,252,0.5) 46%, rgba(244,248,252,0) 78%);
}

/* ---------- Network ---------- */
.scene-net{position:absolute;inset:0;}

.link{
  position:absolute;
  left:var(--x);top:var(--y);
  width:var(--len);height:1px;
  transform:rotate(var(--rot));
  transform-origin:left center;
  background:linear-gradient(90deg, rgba(0,186,255,0.8), rgba(0,186,255,0.2));
}
/* The travelling packet, sized in the scene unit so it stays a dot rather than
   stretching into a dash as the scene scales. */
.link::after{
  content:"";
  position:absolute;top:50%;left:0;
  width:calc(var(--u) * 2);height:calc(var(--u) * 2);
  margin-top:calc(var(--u) * -1);
  border-radius:50%;
  background:var(--cyan);
  box-shadow:0 0 0 calc(var(--u) * 1.3) rgba(0,186,255,0.25);
  animation:scene-packet 2.8s var(--ease) infinite;
  animation-delay:var(--d);
}
@keyframes scene-packet{
  0%{left:0;opacity:0;}
  12%{opacity:1;}
  82%{opacity:1;}
  100%{left:100%;opacity:0;}
}

.node{
  position:absolute;
  left:var(--x);top:var(--y);
  width:calc(var(--u) * 4.6 * var(--s));
  height:calc(var(--u) * 4.6 * var(--s));
  margin:calc(var(--u) * -2.3 * var(--s)) 0 0 calc(var(--u) * -2.3 * var(--s));
  border-radius:28%;
  background:linear-gradient(160deg, var(--cyan), var(--blue));
  box-shadow:
    0 0 0 calc(var(--u) * 0.6) rgba(255,255,255,0.8),
    0 calc(var(--u) * 1) calc(var(--u) * 3) rgba(0,100,205,0.4);
}
.node-ring{
  position:absolute;inset:0;
  border-radius:50%;
  border:1px solid rgba(0,186,255,0.7);
  animation:scene-ring 3.4s var(--ease) infinite;
  animation-delay:var(--d);
}
/* Second ring half a period behind, so each node reads as a steady beacon rather than a
   single blink. */
.node-ring:nth-child(2){animation-delay:calc(var(--d) + 1.7s);}
@keyframes scene-ring{
  0%{transform:scale(1);opacity:0.85;}
  70%, 100%{transform:scale(4.6);opacity:0;}
}

/* ---------- The mark ----------
   The C alone. Proportions from
   01 Logo/02 Beeldmerk los/PNG/CaptureTech_Beeldmerk_Blauw.png, which is 714 x 768; the
   crescent occupies x 0-290, y 8-760 of that, so its own box is 290 x 752.

   The crescent is the left slice of a ring. Fitting a circle to the outer arc puts its
   centre at x 521.8, y 384 with radius 521.8, so the ring's bounding box is 1043.6
   square: 359.9% of the crescent's width, sitting 19.4% of its height above the top
   edge. Ring thickness is 185/1043.6, or 17.7% of the diameter.

   A real border rather than a masked gradient on purpose: it gives a crisp inner and
   outer edge at any size, and the mark is a flat brand cyan anyway. clip-path then cuts
   the ring back to the left slice, and its two angled segments form the pointed tips.
   Those six points are measured off the file, not eyeballed. */
.ct-mark{
  /* Width as a share of the scene. The C alone is a tall shape - 752 units high for
     290 wide - so a width that sounds modest fills the frame vertically: at 38 it came
     to 97% of the scene height. 26 leaves the aisle visible above and below it. */
  --mark-w:26;
  position:absolute;
  left:50%;top:50%;
  width:calc(var(--u) * var(--mark-w));
  aspect-ratio:290 / 752;
  /* Centred with translate, not transform, so transform is free for the scroll
     animation below. The two are separate properties and compose predictably. */
  translate:-50% -50%;
  filter:drop-shadow(0 calc(var(--u) * 2.5) calc(var(--u) * 5) rgba(0,100,205,0.3));
}

.ct-c{
  position:absolute;display:block;
  left:0;top:-19.4%;
  width:359.9%;
  aspect-ratio:1;
  box-sizing:border-box;
  border:calc(var(--u) * var(--mark-w) * 3.599 * 0.177) solid var(--cyan);
  border-radius:50%;
  clip-path:polygon(0% 0%, 15.3% 14.0%, 28.8% 26.1%, 28.8% 73.9%, 15.3% 86.0%, 0% 100%);
}

/* ---------- Scroll-driven motion on the mark ----------
   The C swings open and drifts as the first screen scrolls past. Driven by the scroll
   position itself rather than by a timer, so it tracks the reader's gesture exactly and
   costs nothing on the main thread.

   scroll(root block) with a range in viewport heights, not view(), because the mark is
   already fully on screen at load: a view() timeline would start partway through its
   own range and the animation would begin mid-pose. No fallback animation on browsers
   without scroll timelines - the mark simply sits still, which is a fine resting state
   for a decorative flourish. */
@supports (animation-timeline: scroll()){
  .ct-mark{
    /* Longhands, not the animation shorthand. The shorthand sets animation-duration to
       0s, and a scroll-driven animation with a zero duration never advances: the mark
       stayed at transform:none through the whole scroll. duration:auto is what tells it
       to take its progress from the timeline instead of from a clock. */
    animation-name:ct-scroll;
    animation-duration:auto;
    animation-timing-function:linear;
    animation-fill-mode:both;
    animation-timeline:scroll(root block);
    animation-range:0 100vh;
  }
}
@keyframes ct-scroll{
  from{transform:rotate(-12deg) scale(0.92);}
  to{transform:rotate(58deg) scale(1.12) translate(calc(var(--u) * 6), calc(var(--u) * -5));}
}

@media (max-width:1000px){
  .hero-scene{max-width:400px;}
  .ct-mark{--mark-w:24;}
}

/* =========================================================
   Network scene

   The Network page hero: a plan view of a warehouse under a live WiFi coverage map.
   Structure and the coverage field are in Components/Shared/NetworkScene.razor; every
   position arrives as a custom property so this stays generic.

   A plan rather than the aisle view the home page uses, so the two heroes do not repeat,
   and because a plan is what the product itself shows on this page further down.

   Same rules as the hero scene: no card, --u as the scene's own unit, and the physical
   floor kept muted so the data laid over it is the only thing with colour. The heatmap
   keeps green/amber/red because that is the product's scale for signal strength, and it
   is the one place on the site where colour is carrying information rather than brand.
   ========================================================= */
.net-scene{
  --u:1px;
  position:relative;
  width:100%;
  max-width:min(560px, calc((100svh - var(--header-h) - 130px) / 1.02));
  aspect-ratio:1 / 1.02;
  margin-inline:auto;
  container-type:inline-size;
}

/* --u on the stage, not on .net-scene: an element cannot respond to its own container
   query, and cqw resolves against the nearest container ancestor. The px value is the
   fallback for a browser without container query units. */
.ns-stage{
  --u:5.6px;
  --ns-steel:#7c8ea3;
  --ns-steel-soft:#a8b6c6;
  position:absolute;inset:0;
  isolation:isolate;
  /* Same three-layer edge fade as the hero scene, and mask-repeat is explicit for the
     same reason: it defaults to repeat, which tiles the gradients outside the box. */
  -webkit-mask:
    linear-gradient(90deg, transparent 0%, #000 9%, #000 91%, transparent 100%),
    radial-gradient(98% 100% at 50% 48%, #000 46%, transparent 92%),
    linear-gradient(180deg, transparent 0%, #000 9%, #000 90%, transparent 100%);
  -webkit-mask-repeat:no-repeat;
  -webkit-mask-composite:source-in;
  mask:
    linear-gradient(90deg, transparent 0%, #000 9%, #000 91%, transparent 100%),
    radial-gradient(98% 100% at 50% 48%, #000 46%, transparent 92%),
    linear-gradient(180deg, transparent 0%, #000 9%, #000 90%, transparent 100%);
  mask-repeat:no-repeat;
  mask-composite:intersect;
}
@supports (width: 1cqw){
  .ns-stage{--u:1cqw;}
}

/* ---------- Slab ----------
   Poured concrete with saw-cut expansion joints. Coarse and grey for the same reason the
   aisle floor in the other scene is: a fine glowing mesh is the most synthetic thing you
   can put under a warehouse. */
/* The building. A survey is of somewhere, and without walls the coverage field spilled
   past the racking on all four sides and the whole thing read as a pastel cloud rather
   than as a floor. The envelope also gives the field something to be clipped to. */
.ns-walls{
  position:absolute;
  inset:5% 6%;
  border:1px solid rgba(70,92,116,0.3);
  border-radius:calc(var(--u) * 0.4);
  background:rgba(255,255,255,0.35);
}
/* The dock doors, which is the one thing that tells you which side is the front. */
.ns-walls::after{
  content:"";
  position:absolute;
  left:14%;right:58%;bottom:calc(var(--u) * -0.35);
  height:calc(var(--u) * 0.7);
  background:repeating-linear-gradient(90deg,
    rgba(70,92,116,0.5) 0 calc(var(--u) * 1.6),
    transparent calc(var(--u) * 1.6) calc(var(--u) * 2.6));
}

.ns-slab{
  position:absolute;inset:5% 6%;
  background-image:
    linear-gradient(90deg, rgba(70,92,116,0.13) 0 1px, transparent 1px),
    linear-gradient(180deg, rgba(70,92,116,0.10) 0 1px, transparent 1px);
  background-size:calc(var(--u) * 12.5) calc(var(--u) * 12.5);
}

/* ---------- Racking, from above ----------
   A run of bays either side of a spine. From this angle a bay is a rectangle with a
   frame line down each side, which is all a plan view of racking ever is. */
.ns-racks{position:absolute;inset:0;}
.ns-row{
  position:absolute;
  left:var(--x);top:var(--y);
  width:var(--w);height:var(--h);
  display:flex;
  gap:calc(var(--u) * 0.45);
}
/* The spine of a double-sided run: two rows of bays stand back to back against it. */
.ns-row::after{
  content:"";
  position:absolute;
  left:0;right:0;top:calc(50% - 0.5px);
  height:1px;
  background:rgba(70,92,116,0.34);
}
.ns-bay{
  flex:1;
  background:
    linear-gradient(180deg, var(--ns-steel-soft) 0 14%, rgba(124,142,163,0.34) 14% 86%, var(--ns-steel) 86%);
  border-radius:calc(var(--u) * 0.2);
  box-shadow:inset 0 0 0 1px rgba(70,92,116,0.16);
}

/* ---------- Coverage field ----------
   Fifteen readings as radial gradients on one element, so the overlaps blend into a
   continuous surface. Multiply keeps it sitting on the slab as a survey overlay rather
   than floating above it as paint, which is exactly how the product renders it. */
.ns-heat{
  position:absolute;inset:0;
  clip-path:inset(5% 6%);
  mix-blend-mode:multiply;
  opacity:0.92;
  animation:ns-breathe 9s ease-in-out infinite;
  will-change:opacity;
}
@keyframes ns-breathe{
  0%, 100%{opacity:0.84;}
  50%{opacity:0.98;}
}

/* ---------- Dead zone ----------
   Its own element so it can pulse independently of the field. This is the one thing on
   the page a reader is meant to notice, because finding it in days instead of after a
   two-week survey is the argument the whole page makes. */
.ns-dead{
  position:absolute;
  left:var(--x);top:var(--y);
  width:calc(var(--r) * 2);
  aspect-ratio:1;
  margin-left:calc(var(--r) * -1);
  margin-top:calc(var(--r) * -1);
  border-radius:50%;
  background:radial-gradient(circle, rgba(226,58,58,0.34) 0%, rgba(226,58,58,0.16) 46%, transparent 74%);
  pointer-events:none;
  animation:ns-dead-pulse 3.6s ease-in-out infinite;
}
/* A ring drawn around it, the way an alert marks a region on a map. */
.ns-dead::after{
  content:"";
  position:absolute;
  inset:26%;
  border-radius:50%;
  border:1px dashed rgba(214,44,44,0.5);
  animation:ns-dead-ring 3.6s ease-in-out infinite;
}
@keyframes ns-dead-pulse{
  0%, 100%{opacity:0.6;transform:scale(0.96);}
  50%{opacity:1;transform:scale(1.04);}
}
@keyframes ns-dead-ring{
  0%, 100%{opacity:0.35;transform:scale(0.94);}
  50%{opacity:0.85;transform:scale(1.06);}
}

/* ---------- Access points ---------- */
.ns-aps{position:absolute;inset:0;}
.ns-ap{
  position:absolute;
  left:var(--x);top:var(--y);
  width:calc(var(--u) * 3.9);height:calc(var(--u) * 3.9);
  margin:calc(var(--u) * -1.95) 0 0 calc(var(--u) * -1.95);
}
.ns-ap-body{
  position:absolute;inset:0;
  border-radius:32%;
  background:linear-gradient(160deg, var(--cyan), var(--blue));
  box-shadow:
    0 0 0 calc(var(--u) * 0.5) rgba(255,255,255,0.9),
    0 calc(var(--u) * 0.7) calc(var(--u) * 2) rgba(0,100,205,0.34);
}
/* Two antennas, angled out of the top corners. A rounded blue square with rings coming off
   it could be any kind of sensor; the antennas are what make it an access point at a glance,
   and they are the one part of the shape people recognise without being told. */
.ns-ap-body::before,
.ns-ap-body::after{
  content:"";
  position:absolute;
  bottom:62%;
  width:calc(var(--u) * 0.55);
  height:calc(var(--u) * 2.6);
  border-radius:calc(var(--u) * 0.3);
  background:linear-gradient(180deg, var(--cyan-dark), var(--blue));
  box-shadow:0 0 0 calc(var(--u) * 0.3) rgba(255,255,255,0.85);
}
.ns-ap-body::before{left:8%;transform:rotate(-24deg);transform-origin:bottom center;}
.ns-ap-body::after{right:8%;transform:rotate(24deg);transform-origin:bottom center;}
/* Coverage going out. Two waves half a period apart, so an access point reads as
   radiating steadily rather than blinking. */
.ns-ap-wave{
  position:absolute;inset:0;
  border-radius:50%;
  border:1px solid rgba(0,150,215,0.55);
  animation:ns-wave 4.2s var(--ease) infinite;
  animation-delay:var(--d);
}
.ns-ap-wave:nth-child(2){animation-delay:calc(var(--d) + 2.1s);}
@keyframes ns-wave{
  0%{transform:scale(1);opacity:0.75;}
  70%, 100%{transform:scale(7.5);opacity:0;}
}

/* ---------- Readings ----------
   The pips the picker leaves behind, coming up in walk order and staying up. Colour is
   the band the reading fell in, which is the whole point: the survey is the walk. */
.ns-reads{position:absolute;inset:0;}
.ns-read{
  position:absolute;
  left:var(--x);top:var(--y);
  width:calc(var(--u) * 1.5);height:calc(var(--u) * 1.5);
  margin:calc(var(--u) * -0.75) 0 0 calc(var(--u) * -0.75);
  border-radius:50%;
  box-shadow:0 0 0 calc(var(--u) * 0.28) rgba(255,255,255,0.85);
  opacity:0;
  animation:ns-read-in 18s linear infinite;
  animation-delay:var(--d);
}
.ns-read.is-good{background:#2eba78;}
.ns-read.is-weak{background:#f2b240;}
.ns-read.is-bad{background:#ea4a4a;}
/* Appears as the picker reaches it, then holds until the route restarts. 18s is the walk
   and 16 readings at 1125ms fill exactly one lap, so a pip never lands out of step. */
@keyframes ns-read-in{
  0%{opacity:0;transform:scale(0.2);}
  2%{opacity:1;transform:scale(1);}
  86%{opacity:1;transform:scale(1);}
  94%, 100%{opacity:0;transform:scale(1);}
}

/* ---------- The picker ----------
   Three aisles and back out, in --u so the route scales with the scene. Steps rather
   than a curve: someone walking a rack aisle goes down it, across the cross aisle, and
   down the next, and a smooth arc through the racking would be nonsense.

   The route is authored here rather than in the component because it is motion, and it
   has to agree with the reading delays: 16 readings x 1125ms = 18s, one lap. */
.ns-walker{
  position:absolute;
  left:0;top:0;
  width:calc(var(--u) * 2.4);height:calc(var(--u) * 2.4);
  margin:calc(var(--u) * -1.2) 0 0 calc(var(--u) * -1.2);
  border-radius:50%;
  background:var(--ink-2);
  box-shadow:0 0 0 calc(var(--u) * 0.4) rgba(255,255,255,0.9);
  animation:ns-walk 18s linear infinite;
  will-change:translate;
}
/* The scan going off as they walk. */
.ns-walker-ping{
  position:absolute;inset:0;
  border-radius:50%;
  border:1px solid rgba(43,53,66,0.5);
  animation:ns-ping 1.125s ease-out infinite;
}
@keyframes ns-ping{
  0%{transform:scale(1);opacity:0.7;}
  100%{transform:scale(3.4);opacity:0;}
}
/* Every offset is in --u, which is one percent of the scene's width, because a percentage
   in `translate` is a share of the element's own box rather than of its parent. Written as
   percentages the entire route collapsed into about three pixels in the top-left corner
   and the walker never moved. The 1.02 on the vertical is the scene's aspect ratio, so a
   y offset means the same share of the height that an x offset means of the width. */
@keyframes ns-walk{
  /* Aisle 1, left to right */
  0%   {translate:calc(var(--u) * 14) calc(var(--u) * 27 * 1.02);}
  22%  {translate:calc(var(--u) * 44) calc(var(--u) * 27 * 1.02);}
  /* Down into aisle 2 */
  27%  {translate:calc(var(--u) * 48) calc(var(--u) * 46 * 1.02);}
  /* Aisle 2, on to the right, into the weak corner */
  50%  {translate:calc(var(--u) * 80) calc(var(--u) * 46 * 1.02);}
  /* Down into aisle 3 */
  55%  {translate:calc(var(--u) * 76) calc(var(--u) * 65 * 1.02);}
  /* Aisle 3, back to the left */
  76%  {translate:calc(var(--u) * 32) calc(var(--u) * 65 * 1.02);}
  /* Down into aisle 4 and back out east */
  82%  {translate:calc(var(--u) * 26) calc(var(--u) * 82 * 1.02);}
  98%  {translate:calc(var(--u) * 78) calc(var(--u) * 82 * 1.02);}
  100% {translate:calc(var(--u) * 78) calc(var(--u) * 82 * 1.02);}
}

/* Under reduced motion the global reset zeroes every duration, which leaves the walker
   and the pips wherever their first keyframe puts them. The pips start at opacity 0, so
   the readings would vanish and the scene would lose the point it is making. Pin them
   visible and park the walker at the start of its route. */
@media (prefers-reduced-motion:reduce){
  .ns-read{opacity:1;animation:none;}
  .ns-walker{animation:none;translate:calc(var(--u) * 14) calc(var(--u) * 27 * 1.02);}
  .ns-heat{animation:none;opacity:0.92;}
}

/* Translucency off: the heatmap is the content here, so it stays. What goes is the
   multiply blend, which needs something to blend with. */
@media (prefers-reduced-transparency:reduce){
  .ns-heat{mix-blend-mode:normal;}
}

@media (max-width:1000px){
  .net-scene{max-width:400px;}
}

/* =========================================================
   Screenshots

   The product pages carry eleven real screenshots of the portal and real photographs of
   Zebra hardware running the app. They are not redrawn and they are not going to be:
   they are the only evidence on the site that the product exists and looks like
   anything, and a drawn approximation of a UI is a claim about the product rather than a
   record of it. What is animated is the presentation around them.

   Two treatments, chosen in the porter by filename. Portal shots get a window frame,
   because they are a web app. Hardware photos get no frame, because the device in the
   photograph already is one; they get a lift and a slow drift instead. The screens in those
   photographs are captures with no bezel, so build-identity.py draws the handheld around
   them - see docs/landing/README.md.

   Nothing passes over them. There used to be a soft band crossing every figure every nine
   seconds, slow and low-contrast on purpose, and it was still glare: a moving highlight
   over the one thing on the page the reader is trying to read. The hardware shots also had
   a cyan glow behind them, which turned into a halo once the handheld body was drawn into
   the figure itself. Both are gone, and what is left is a frame, a hover lift, and the
   float.
   ========================================================= */
.shot{
  position:relative;
  display:block;
  width:fit-content;
  max-width:100%;
  margin-inline:auto;
  border-radius:var(--r-lg);
  border:1px solid var(--glass-edge);
  box-shadow:var(--inner-top), var(--shadow-md);
  /* Clips the scan pass and rounds the image with the frame, which is why the radius
     moved off the img and onto here. */
  overflow:hidden;
  transition:transform .45s var(--ease), box-shadow .45s var(--ease);
}
.hero-visual .shot{box-shadow:var(--inner-top), var(--shadow-lg);}

/* The frame owns the border now, so the image must not draw its own. */
.shot img{border:0;border-radius:0;box-shadow:none;}

.shot:hover{
  transform:translateY(-3px);
  box-shadow:var(--inner-top), var(--shadow-lg);
}

/* ---------- Window frame ----------
   Three dots on a frosted bar. No address bar and no live indicator: a URL that is not
   the one you are on, or a live light beside a still image, is a small lie told for
   decoration, and these images are here precisely because they are not that. */
.shot-bar{
  display:flex;align-items:center;gap:6px;
  padding:0 12px;
  height:26px;
  background:rgba(240,245,250,0.9);
  border-bottom:1px solid var(--line-soft);
}
.shot-bar i{
  width:8px;height:8px;
  border-radius:50%;
  background:rgba(10,25,50,0.14);
}

.shot-media{
  position:relative;
  display:block;
}

/* ---------- Hardware photos ----------
   No frame and no fill: these are cut-out photographs on a transparent background, so a
   border would draw a box around empty space. A soft glow grounds them on the field and
   a long drift keeps them from sitting dead on the page. */
.shot-device{
  border:0;
  box-shadow:none;
  overflow:visible;
  animation:shot-float 11s ease-in-out infinite;
}
.hero-visual .shot-device{box-shadow:none;}
.shot-device:hover{transform:translateY(-3px);box-shadow:none;}
.shot-device .shot-media{overflow:hidden;border-radius:var(--r-lg);}
@keyframes shot-float{
  0%, 100%{translate:0 0;}
  50%{translate:0 -9px;}
}

@media (prefers-reduced-motion:reduce){
  .shot-device{animation:none;}
  .shot,.shot-device{transition:none;}
}

@media (prefers-reduced-transparency:reduce){
  .shot-bar{background:#eef3f8;}
}

/* =========================================================
   Identity scene

   The Identity page hero: a rugged handheld running the login it actually does, with the
   roster beside it resolving to the person now holding it, and a drop registered at the
   end. Structure is Components/Shared/IdentityScene.razor.

   One 12s cycle drives everything, and every animation-duration in here is 12s so the
   states cannot drift apart. The windows within it:
     0    - 3.4s   splash
     3.5s - 8.0s   PIN entry, keys down at 4.3 / 5.1 / 5.9 / 6.7
     8.1s - 12s    signed in, roster resolves
     9.6s - 12s    a drop registers
   The key delays are in the component and have to agree with the second window. If you
   move one, move both.

   Same rules as the other two scenes: no card, --u as the scene's own unit, physical
   things muted, the digital layer in brand cyan.
   ========================================================= */
.id-scene{
  --u:1px;
  position:relative;
  width:100%;
  max-width:min(560px, calc((100svh - var(--header-h) - 130px) / 1.02));
  aspect-ratio:1 / 1.02;
  margin-inline:auto;
  container-type:inline-size;
}

.ids-stage{
  --u:5.6px;
  --ids-shell:#3d454e;
  --ids-shell-lit:#5c6672;
  --ids-bezel:#15181c;
  position:absolute;inset:0;
  isolation:isolate;
  /* Same three-layer edge fade as the other scenes, mask-repeat explicit for the same
     reason: it defaults to repeat, which tiles the gradients outside the box. */
  -webkit-mask:
    linear-gradient(90deg, transparent 0%, #000 8%, #000 92%, transparent 100%),
    radial-gradient(100% 100% at 50% 48%, #000 48%, transparent 94%),
    linear-gradient(180deg, transparent 0%, #000 8%, #000 91%, transparent 100%);
  -webkit-mask-repeat:no-repeat;
  -webkit-mask-composite:source-in;
  mask:
    linear-gradient(90deg, transparent 0%, #000 8%, #000 92%, transparent 100%),
    radial-gradient(100% 100% at 50% 48%, #000 48%, transparent 94%),
    linear-gradient(180deg, transparent 0%, #000 8%, #000 91%, transparent 100%);
  mask-repeat:no-repeat;
  mask-composite:intersect;
}
@supports (width: 1cqw){
  .ids-stage{--u:1cqw;}
}

/* ---------- The roster ----------
   Three holders down the left. Each is an avatar and two lines standing in for a name,
   which is the shape of a row in a user list without being a fake name in one language.
   The one who signs in resolves from grey to brand as the device accepts the credential;
   the other two stay unresolved, because only one person is holding it. */
.ids-roster{position:absolute;left:2%;top:0;bottom:0;width:30%;}
.ids-holder{
  position:absolute;
  left:0;right:0;top:var(--y);
  display:flex;align-items:center;
  gap:calc(var(--u) * 1.6);
  opacity:0.5;
}
.ids-avatar{
  width:calc(var(--u) * 5.4);height:calc(var(--u) * 5.4);
  flex:none;
  border-radius:50%;
  background:linear-gradient(160deg, #cfd8e2, #aab6c4);
  box-shadow:inset 0 0 0 1px rgba(70,92,116,0.2);
}
/* A head and shoulders, which is enough to read as a person at this size. */
.ids-avatar::after{
  content:"";
  position:absolute;
  left:50%;top:52%;
  width:58%;height:58%;
  translate:-50% -50%;
  border-radius:50% 50% 42% 42%;
  background:rgba(70,92,116,0.34);
}
.ids-avatar{position:relative;overflow:hidden;}
.ids-lines{display:flex;flex-direction:column;gap:calc(var(--u) * 0.9);flex:1;min-width:0;}
.ids-lines i{
  height:calc(var(--u) * 1.15);
  border-radius:calc(var(--u) * 0.6);
  background:rgba(70,92,116,0.26);
}
.ids-lines i:first-child{width:76%;}
.ids-lines i:last-child{width:48%;background:rgba(70,92,116,0.16);}

/* The holder resolves once the sign-in lands. */
.ids-holder.is-holder{
  animation:ids-resolve 12s var(--ease) infinite;
}
.ids-holder.is-holder .ids-avatar{
  background:linear-gradient(160deg, var(--cyan), var(--blue));
}
.ids-holder.is-holder .ids-avatar::after{background:rgba(255,255,255,0.85);}
.ids-holder.is-holder .ids-lines i{background:rgba(0,100,205,0.45);}
.ids-holder.is-holder .ids-lines i:last-child{background:rgba(0,100,205,0.24);}
@keyframes ids-resolve{
  0%, 64%{opacity:0.5;transform:none;}
  70%{opacity:1;transform:translateX(calc(var(--u) * 1.4));}
  96%{opacity:1;transform:translateX(calc(var(--u) * 1.4));}
  100%{opacity:0.5;transform:none;}
}

/* ---------- The device ----------
   A generic rugged handheld: the proportions of the real thing, the scan trigger where
   the real one has it, and no vendor badge on it. It is not a photograph and should not
   pretend to be one. */
.ids-device{
  position:absolute;
  left:56%;top:50%;
  width:calc(var(--u) * 30);
  aspect-ratio:30 / 56;
  translate:-50% -50%;
  border-radius:calc(var(--u) * 3.4);
  background:
    linear-gradient(100deg, var(--ids-shell-lit) 0%, var(--ids-shell) 22%, #2b323a 78%, #1d2228 100%);
  box-shadow:
    inset 0 0 0 1px rgba(255,255,255,0.12),
    0 calc(var(--u) * 2.4) calc(var(--u) * 5) rgba(16,24,34,0.34);
  animation:ids-hold 12s ease-in-out infinite;
}
/* A hand does not hold anything perfectly still. Two degrees is enough to keep the device
   from looking pasted on, and small enough that the screen stays readable. */
@keyframes ids-hold{
  0%, 100%{rotate:-1.6deg;translate:-50% -50%;}
  50%{rotate:1.2deg;translate:-50% calc(-50% - var(--u) * 0.8);}
}

.ids-speaker{
  position:absolute;
  left:50%;top:calc(var(--u) * 2.1);
  width:26%;height:calc(var(--u) * 0.7);
  translate:-50% 0;
  border-radius:calc(var(--u) * 0.4);
  background:rgba(0,0,0,0.42);
}

.ids-screen{
  position:absolute;
  left:8%;right:8%;
  top:calc(var(--u) * 4.4);
  /* Stops short of the keypad at 41.5u. Taller than this and the two overlap. */
  height:62%;
  border-radius:calc(var(--u) * 0.6);
  background:var(--ids-bezel);
  overflow:hidden;
  box-shadow:inset 0 0 0 1px rgba(0,0,0,0.6);
}

/* Each state fills the screen and takes its turn. Same duration on all three, so the
   handover is exact. */
.ids-state{
  position:absolute;inset:0;
  display:flex;align-items:center;justify-content:center;
  flex-direction:column;
  gap:calc(var(--u) * 2);
  opacity:0;
}
.ids-splash{
  background:linear-gradient(165deg, var(--cyan) 0%, #009ad6 100%);
  animation:ids-splash 12s var(--ease) infinite;
}
.ids-auth{
  background:linear-gradient(180deg, #1b2028 0%, #12161c 100%);
  animation:ids-auth 12s var(--ease) infinite;
}
.ids-signed{
  background:linear-gradient(165deg, #0f7a4a 0%, #0b5f3a 100%);
  animation:ids-signed 12s var(--ease) infinite;
}
@keyframes ids-splash{
  0%{opacity:0;}
  3%, 26%{opacity:1;}
  30%, 100%{opacity:0;}
}
@keyframes ids-auth{
  0%, 28%{opacity:0;}
  31%, 65%{opacity:1;}
  68%, 100%{opacity:0;}
}
@keyframes ids-signed{
  0%, 66%{opacity:0;}
  69%, 96%{opacity:1;}
  100%{opacity:0;}
}

/* The mark on the splash. The same left-slice-of-a-ring construction as the home hero's,
   at the size this screen allows. */
.ids-mark{
  position:relative;
  width:34%;
  aspect-ratio:290 / 752;
}
.ids-mark::after{
  content:"";
  position:absolute;
  left:0;top:-19.4%;
  width:359.9%;
  aspect-ratio:1;
  box-sizing:border-box;
  border:calc(var(--u) * 30 * 0.34 * 3.599 * 0.177) solid #fff;
  border-radius:50%;
  clip-path:polygon(0% 0%, 15.3% 14.0%, 28.8% 26.1%, 28.8% 73.9%, 15.3% 86.0%, 0% 100%);
}

/* PIN entry. The dots fill as the keys go down, on the delays the component computed. */
.ids-dots{display:flex;gap:calc(var(--u) * 1.4);}
.ids-dots i{
  width:calc(var(--u) * 1.5);height:calc(var(--u) * 1.5);
  border-radius:50%;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,0.4);
  background:transparent;
  animation:ids-dot 12s steps(1, end) infinite;
  animation-delay:var(--d);
}
@keyframes ids-dot{
  0%, 99%{background:rgba(0,186,255,0.95);}
  100%{background:transparent;}
}

.ids-keys{
  display:grid;
  grid-template-columns:repeat(3, 1fr);
  gap:calc(var(--u) * 1);
  width:74%;
}
.ids-keys i{
  height:calc(var(--u) * 3.1);
  border-radius:calc(var(--u) * 0.5);
  background:rgba(255,255,255,0.10);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,0.06);
}
/* A key that goes down flashes once, at its moment, and is otherwise a key like any
   other. steps(1) so it is a press and not a glow. */
.ids-keys i.is-pressed{
  animation:ids-key 12s steps(1, end) infinite;
  animation-delay:var(--d);
}
@keyframes ids-key{
  0%{background:rgba(0,186,255,0.75);}
  4%, 100%{background:rgba(255,255,255,0.10);}
}

/* A drawn tick: two borders on a rotated box, which gives a real corner rather than a
   glyph that would depend on a font being loaded. */
.ids-tick{
  width:calc(var(--u) * 5);height:calc(var(--u) * 2.6);
  border-left:calc(var(--u) * 0.75) solid #fff;
  border-bottom:calc(var(--u) * 0.75) solid #fff;
  rotate:-45deg;
  border-radius:calc(var(--u) * 0.2);
  animation:ids-tick 12s var(--ease) infinite;
}
@keyframes ids-tick{
  0%, 68%{opacity:0;scale:0.4;}
  73%{opacity:1;scale:1;}
  94%{opacity:1;scale:1;}
  100%{opacity:0;scale:1;}
}

/* The hardware keypad. Two rows of four between the screen and the trigger, which is what
   makes the body read as a terminal rather than as a handset: a slab with a screen and one
   button on it is a phone. */
.ids-pad{
  position:absolute;
  left:14%;right:14%;
  top:calc(var(--u) * 41.5);
  display:grid;
  grid-template-columns:repeat(4, 1fr);
  gap:calc(var(--u) * 0.8);
}
.ids-pad i{
  height:calc(var(--u) * 2.2);
  border-radius:calc(var(--u) * 0.4);
  background:linear-gradient(180deg, rgba(255,255,255,0.16), rgba(0,0,0,0.22));
  box-shadow:inset 0 0 0 1px rgba(0,0,0,0.22);
}

.ids-trigger{
  position:absolute;
  left:50%;bottom:calc(var(--u) * 3.4);
  width:34%;height:calc(var(--u) * 2.6);
  translate:-50% 0;
  border-radius:calc(var(--u) * 1.3);
  background:linear-gradient(180deg, #f5c94a, #d9a521);
  box-shadow:inset 0 1px 0 rgba(255,255,255,0.5);
}
.ids-side{
  position:absolute;
  top:34%;
  width:calc(var(--u) * 0.9);height:calc(var(--u) * 6);
  border-radius:calc(var(--u) * 0.5);
  background:rgba(0,0,0,0.3);
}
.ids-side:nth-of-type(1){left:calc(var(--u) * -0.4);}
.ids-side:nth-of-type(2){right:calc(var(--u) * -0.4);}

/* Under reduced motion the global reset zeroes every duration, which parks each state at
   its first keyframe - and all three states start at opacity 0, so the screen would go
   blank. Pin the signed-in state, which is the one that makes the page's point, and show
   the resolved holder with it. */
@media (prefers-reduced-motion:reduce){
  .ids-splash,.ids-auth{opacity:0;animation:none;}
  .ids-signed{opacity:1;animation:none;}
  .ids-tick{opacity:1;scale:1;animation:none;}
  .ids-holder.is-holder{opacity:1;animation:none;}
  .ids-device{animation:none;rotate:0deg;}
}

@media (max-width:1000px){
  .id-scene{max-width:400px;}
}

/* =========================================================
   Operations scene

   The Operations page hero: demand measured up to a boundary, a forecast continuing past
   it, and the slotting move the forecast justifies. Structure and the series are in
   Components/Shared/OperationsScene.razor.

   Deliberately without numbers or axis labels. Operations ships Q1 2027, so specific
   figures in a hero would be inventing performance the module cannot yet have, and the
   component serves the English and the Dutch page from one source.
   ========================================================= */
.ops-scene{
  --u:1px;
  position:relative;
  width:100%;
  max-width:min(560px, calc((100svh - var(--header-h) - 130px) / 1.02));
  aspect-ratio:1 / 1.02;
  margin-inline:auto;
  container-type:inline-size;
}

.ops-stage{
  --u:5.6px;
  position:absolute;inset:0;
  isolation:isolate;
  display:flex;
  flex-direction:column;
  justify-content:center;
  gap:calc(var(--u) * 7);
  -webkit-mask:
    linear-gradient(90deg, transparent 0%, #000 7%, #000 93%, transparent 100%),
    linear-gradient(180deg, transparent 0%, #000 7%, #000 93%, transparent 100%);
  -webkit-mask-repeat:no-repeat;
  -webkit-mask-composite:source-in;
  mask:
    linear-gradient(90deg, transparent 0%, #000 7%, #000 93%, transparent 100%),
    linear-gradient(180deg, transparent 0%, #000 7%, #000 93%, transparent 100%);
  mask-repeat:no-repeat;
  mask-composite:intersect;
}
@supports (width: 1cqw){
  .ops-stage{--u:1cqw;}
}

/* ---------- The plot ---------- */
.ops-plot{
  position:relative;
  height:46%;
  margin:0 6%;
}
.ops-baseline{
  position:absolute;
  left:0;right:0;bottom:0;
  height:1px;
  background:rgba(70,92,116,0.28);
}

.ops-bar{
  position:absolute;
  left:var(--x);bottom:0;
  width:var(--w);
  height:var(--h);
  border-radius:calc(var(--u) * 0.4) calc(var(--u) * 0.4) 0 0;
  background:linear-gradient(180deg, var(--cyan) 0%, #0090cc 100%);
  transform-origin:bottom;
  animation:ops-grow 13s var(--ease) infinite;
  animation-delay:var(--d);
}
/* Past the boundary it is an expectation, not a measurement, so it is an outline. That
   distinction is the only idea this hero is carrying, so it is drawn as hard as possible:
   filled means it happened, hollow means it has not yet. */
.ops-bar.is-forecast{
  background:transparent;
  box-shadow:inset 0 0 0 1px rgba(0,150,215,0.65);
  border-radius:calc(var(--u) * 0.4);
}
@keyframes ops-grow{
  0%{scale:1 0;opacity:0;}
  8%{opacity:1;}
  20%, 82%{scale:1 1;opacity:1;}
  /* Back down before the loop point, so the restart is a continuation rather than a cut. */
  96%, 100%{scale:1 0;opacity:0;}
}

/* The boundary. A dotted upright, which is how every chart that has ever separated actual
   from forecast has drawn it. */
.ops-now{
  position:absolute;
  left:var(--x);top:-6%;bottom:0;
  width:1px;
  background:repeating-linear-gradient(180deg,
    rgba(43,53,66,0.5) 0 calc(var(--u) * 0.7),
    transparent calc(var(--u) * 0.7) calc(var(--u) * 1.5));
}
.ops-now::after{
  content:"";
  position:absolute;
  left:50%;top:calc(var(--u) * -1.4);
  width:calc(var(--u) * 1.5);height:calc(var(--u) * 1.5);
  translate:-50% 0;
  border-radius:50%;
  background:var(--ink-2);
}

/* The trend. Stroke widths are in viewBox units because the SVG is stretched by
   preserveAspectRatio:none, so a px width would come out oval. */
.ops-trend{
  position:absolute;inset:0;
  width:100%;height:100%;
  overflow:visible;
  fill:none;
}
.ops-trend-past{
  stroke:var(--ink-2);
  stroke-width:1.6;
  stroke-linejoin:round;
  stroke-linecap:round;
  vector-effect:non-scaling-stroke;
  /* Drawn in, left to right. pathLength is not set, so the dash values are in user units
     and 300 is comfortably longer than the path. */
  stroke-dasharray:300;
  animation:ops-draw 13s var(--ease) infinite;
}
.ops-trend-next{
  stroke:var(--cyan-dark);
  stroke-width:1.6;
  stroke-linejoin:round;
  stroke-linecap:round;
  vector-effect:non-scaling-stroke;
  stroke-dasharray:4 3.5;
  opacity:0;
  animation:ops-forecast 13s var(--ease) infinite;
}
@keyframes ops-draw{
  0%{stroke-dashoffset:300;}
  42%, 82%{stroke-dashoffset:0;}
  /* Un-draws the way it drew, left to right, instead of vanishing at the seam. */
  100%{stroke-dashoffset:300;}
}
@keyframes ops-forecast{
  0%, 44%{opacity:0;}
  58%, 82%{opacity:1;}
  96%, 100%{opacity:0;}
}

/* ---------- The slotting move ----------
   A run of locations, classed the way the product classes them, with one item lifting out
   of a slow slot and landing in a fast one. This is the action the forecast above pays
   for, which is why it sits under the plot rather than beside it. */
.ops-slots{
  position:relative;
  height:calc(var(--u) * 9);
  margin:0 6%;
}
.ops-slot{
  position:absolute;
  left:var(--x);top:0;bottom:0;
  width:var(--w);
  border-radius:calc(var(--u) * 0.4);
  box-shadow:inset 0 0 0 1px rgba(70,92,116,0.16);
}
/* The product's own three classes. Warm is the middle, so it is the neutral one. */
.ops-slot.is-fast{background:rgba(46,186,120,0.32);}
.ops-slot.is-mid{background:rgba(242,178,64,0.28);}
.ops-slot.is-slow{background:rgba(120,140,164,0.2);}

/* The item being moved. It leaves the far slow slot, arcs up over the run, and settles
   into the near fast one. */
.ops-mover{
  position:absolute;
  top:calc(var(--u) * 1.4);
  left:0;
  width:calc(var(--u) * 3.4);height:calc(var(--u) * 3.4);
  border-radius:calc(var(--u) * 0.5);
  background:linear-gradient(150deg, #d9d0c0, #b9b0a0);
  box-shadow:0 calc(var(--u) * 0.6) calc(var(--u) * 1.4) rgba(16,24,34,0.24);
  animation:ops-move 13s var(--ease) infinite;
}
/* translate rather than left, so it stays on the compositor, and in --u so the route
   scales with the scene: a percentage in translate is a share of the element's own box. */
/* The arc, with its midpoint split in two so the lift and the fall each get their own
   segment. Three keyframes made the top of the arc a corner. */
@keyframes ops-move{
  0%, 48%{translate:calc(var(--u) * 76) 0;opacity:0;}
  54%{translate:calc(var(--u) * 76) 0;opacity:1;}
  62%{translate:calc(var(--u) * 62) calc(var(--u) * -5.5);opacity:1;}
  70%{translate:calc(var(--u) * 40) calc(var(--u) * -7.5);opacity:1;}
  78%{translate:calc(var(--u) * 20) calc(var(--u) * -4);opacity:1;}
  84%, 90%{translate:calc(var(--u) * 9) 0;opacity:1;}
  97%{translate:calc(var(--u) * 9) 0;opacity:0;}
  /* Back to the slot it started from, while invisible, so 0% and 100% are the same state
     and the loop cannot flash a frame at the wrong position. */
  100%{translate:calc(var(--u) * 76) 0;opacity:0;}
}

@media (prefers-reduced-motion:reduce){
  /* Zeroing the durations parks the bars at scale 0 and the forecast line at opacity 0,
     which would leave an empty plot. Pin the finished state. */
  .ops-bar{animation:none;scale:1 1;opacity:1;}
  .ops-trend-past{animation:none;stroke-dashoffset:0;}
  .ops-trend-next{animation:none;opacity:1;}
  .ops-mover{animation:none;translate:calc(var(--u) * 9) 0;opacity:1;}
}

@media (max-width:1000px){
  .ops-scene{max-width:400px;}
}

/* =========================================================
   Digital Twin scene

   The Digital Twin page hero: rows of pallet racking receding into the frame, tinted by how
   busy each location is, with one bay selected and called out. Structure is in
   Components/Shared/DigitalTwinScene.razor.

   Drawn as elevations rather than as a plan. Two earlier versions looked straight down at
   extruded decks with the activity overlay on top, and the trouble with that view is not
   fixable by detail: from directly above, racking is rectangles. Uprights, beam levels and
   pallets sitting on beams are what make it legible, and all three of those are vertical.

   Structure is steel and stock carries the data, which is the split the home hero uses too:
   the building is physical and muted, the layer over it is the product.
   ========================================================= */
.dt-scene{
  --u:1px;
  position:relative;
  width:100%;
  max-width:min(560px, calc((100svh - var(--header-h) - 130px) / 1.02));
  aspect-ratio:1 / 1.02;
  margin-inline:auto;
  container-type:inline-size;
}

.dt-stage{
  --u:5.6px;
  --dt-steel:#66798f;
  --dt-steel-lit:#93a5b8;
  --dt-steel-dark:#43536655;
  position:absolute;inset:0;
  isolation:isolate;
  -webkit-mask:
    linear-gradient(90deg, transparent 0%, #000 7%, #000 93%, transparent 100%),
    linear-gradient(180deg, transparent 0%, #000 5%, #000 95%, transparent 100%);
  -webkit-mask-repeat:no-repeat;
  -webkit-mask-composite:source-in;
  mask:
    linear-gradient(90deg, transparent 0%, #000 7%, #000 93%, transparent 100%),
    linear-gradient(180deg, transparent 0%, #000 5%, #000 95%, transparent 100%);
  mask-repeat:no-repeat;
  mask-composite:intersect;
}
@supports (width: 1cqw){
  .dt-stage{--u:1cqw;}
}

/* ---------- Floor ----------
   Sealed concrete with saw-cut joints, sheared to the angle the rows recede at so the floor
   and the racking share one projection. */
.dt-floor{
  position:absolute;
  left:-6%;right:14%;top:10%;bottom:0;
  background-image:
    linear-gradient(90deg, rgba(70,92,116,0.10) 0 1px, transparent 1px),
    linear-gradient(180deg, rgba(70,92,116,0.07) 0 1px, transparent 1px);
  background-size:calc(var(--u) * 8) calc(var(--u) * 8);
  transform:skewY(-9deg);
  -webkit-mask:radial-gradient(78% 82% at 46% 52%, #000 52%, transparent 100%);
  mask:radial-gradient(78% 82% at 46% 52%, #000 52%, transparent 100%);
}

.dt-world{position:absolute;inset:0;}

/* ---------- A row of racking ----------
   One run seen face on and sheared, so the far end of the run rises and the row reads as
   going away from you. skewY and not a 3D rotate: the whole row including its stock has to
   shear together, and a shear is exactly what an axonometric projection of a flat face is.

   9 degrees, not more. At 14 the rows overlapped so heavily that the stock on the row behind
   was buried, and the frame stopped being readable. */
.dt-row{
  position:absolute;
  left:var(--x);top:var(--y);
  width:var(--w);height:var(--h);
  z-index:var(--z);
  transform:skewY(-9deg);
  /* The rows further back sit in more haze, so the building recedes. --dim is 0 at the
     front and 1 at the back. */
  opacity:calc(1 - 0.34 * var(--dim));
}

/* The frame. An upright at every bay boundary and a beam along the floor, in one element:
   the uprights are a repeating gradient whose period is one bay, so the count follows
   --bays without a per-upright element. */
.dt-frame{
  position:absolute;inset:0;
  background-image:
    /* Uprights. */
    repeating-linear-gradient(90deg,
      var(--dt-steel) 0 calc(var(--u) * 0.85),
      transparent calc(var(--u) * 0.85) calc(100% / var(--bays))),
    /* The last upright, which a repeating gradient starting at 0 never draws. */
    linear-gradient(90deg, transparent calc(100% - var(--u) * 0.85), var(--dt-steel) 0);
  /* The floor beam, grounding the frame. */
  box-shadow:inset 0 calc(var(--u) * -0.7) 0 var(--dt-steel);
}

/* A beam level, and the stock standing on it. */
.dt-level{
  position:absolute;
  left:0;right:0;
  bottom:var(--lv);
  height:calc(var(--u) * 0.7);
  background:linear-gradient(180deg, var(--dt-steel-lit) 0 40%, var(--dt-steel) 100%);
  /* A grid of one column per bay, not flex with space-between. Space-between spreads the
     slack evenly between the loads, so each one drifts a little further from its bay than
     the last and by the seventh the stock no longer lines up with the uprights at all. One
     column per bay pins every load to the frame it is standing in. */
  display:grid;
  grid-template-columns:repeat(var(--bays), 1fr);
  align-items:end;
  justify-items:center;
}

/* A pallet. Sits on the beam, so its box grows upwards out of the level's top edge. */
.dt-pallet{
  position:relative;
  width:var(--pw);
  height:calc(var(--u) * 4.2);
  margin-bottom:calc(var(--u) * 0.7);
  border-radius:calc(var(--u) * 0.15);
  /* Lit across the top from the roof, shaded down the right, which is what gives a
     rectangle a third dimension. */
  background-image:
    linear-gradient(180deg, rgba(255,255,255,0.5) 0, rgba(255,255,255,0.12) 16%, transparent 30%),
    linear-gradient(90deg, transparent 62%, rgba(30,42,58,0.18) 100%);
  box-shadow:inset 0 0 0 1px rgba(46,62,82,0.22);
}
/* The activity overlay: how busy this location is. This is the twin's whole point, so it is
   what carries the colour, and the structure stays steel around it. */
.dt-pallet.is-hot{background-color:#39b57a;}
.dt-pallet.is-warm{background-color:#9ed3b4;}
.dt-pallet.is-cold{background-color:#cdd7e1;}
/* An empty position. Nothing is drawn, so the frame behind shows through, and the gap is
   most of what separates racking from a bar chart. */
.dt-pallet.is-void{visibility:hidden;}

/* A location being scanned right now. */
.dt-scan{
  position:absolute;
  left:50%;top:38%;
  width:calc(var(--u) * 1.5);height:calc(var(--u) * 1.5);
  margin:calc(var(--u) * -0.75) 0 0 calc(var(--u) * -0.75);
  border-radius:50%;
  background:var(--red);
  box-shadow:0 0 calc(var(--u) * 1.4) rgba(240,60,90,0.85);
  animation:dt-scan 4.8s var(--ease) infinite;
  animation-delay:var(--d);
}
.dt-scan::after{
  content:"";
  position:absolute;inset:0;
  border-radius:50%;
  border:1px solid rgba(240,60,90,0.8);
  animation:dt-scan-ring 4.8s var(--ease) infinite;
  animation-delay:var(--d);
}
@keyframes dt-scan{
  0%, 6%{opacity:0;scale:0.5;}
  14%{opacity:1;scale:1;}
  44%{opacity:1;scale:1;}
  56%, 100%{opacity:0;scale:0.7;}
}
@keyframes dt-scan-ring{
  0%, 10%{opacity:0;scale:0.6;}
  18%{opacity:0.9;scale:1;}
  50%, 100%{opacity:0;scale:3.4;}
}

/* ---------- The selected bay ----------
   A box around one bay of one row, floor to top beam, which is the selection the floorplan
   makes when you click. Inside the row, so it shears with it. */
.dt-pick{
  position:absolute;
  left:var(--bx);
  width:var(--bw);
  top:calc(var(--u) * -1.4);
  bottom:0;
  z-index:4;
  border:1px solid var(--cyan);
  border-radius:calc(var(--u) * 0.3);
  background:rgba(0,186,255,0.1);
  box-shadow:0 0 calc(var(--u) * 2.2) rgba(0,186,255,0.45);
  animation:dt-pick 7s var(--ease) infinite;
}
@keyframes dt-pick{
  0%, 100%{opacity:0.55;}
  50%{opacity:1;}
}

/* ---------- Aisles ----------
   The floor between two rows, with a scan pulse running along it. Sheared with everything
   else so it lies on the floor rather than crossing the picture. */
.dt-aisle{
  position:absolute;
  left:var(--x);top:var(--y);
  width:var(--w);
  height:calc(var(--u) * 1.6);
  transform:skewY(-9deg);
  overflow:hidden;
  border-radius:calc(var(--u) * 0.3);
  background:linear-gradient(180deg, rgba(120,140,164,0.16), rgba(120,140,164,0.04));
}
.dt-aisle::after{
  content:"";
  position:absolute;
  top:0;bottom:0;
  width:22%;
  background:linear-gradient(90deg, transparent, rgba(0,186,255,0.5), transparent);
  animation:dt-sweep 7s linear infinite;
  animation-delay:var(--d);
}
@keyframes dt-sweep{
  0%{left:-24%;}
  100%{left:102%;}
}

/* ---------- The detail card ----------
   What the floorplan shows when a bay is clicked. Rows rather than labels, because this
   component serves two languages, and a face grid at the bottom because that is the one
   part of the real panel that is a picture rather than a table.

   Out to the right of the model. The rows stop at 60% of the scene, so there is air for it. */
.dt-card{
  position:absolute;
  right:1%;top:36%;
  width:27%;
  z-index:20;
  padding:calc(var(--u) * 2.2);
  display:flex;
  flex-direction:column;
  gap:calc(var(--u) * 1.5);
  border-radius:var(--r-md);
  background:rgba(255,255,255,0.88);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.9),
    0 calc(var(--u) * 2) calc(var(--u) * 5) rgba(16,24,34,0.16);
  -webkit-backdrop-filter:blur(10px);
  backdrop-filter:blur(10px);
  animation:dt-card-in 7s var(--ease) infinite;
}
@keyframes dt-card-in{
  0%{opacity:0;translate:calc(var(--u) * 2) 0;}
  10%, 92%{opacity:1;translate:0 0;}
  100%{opacity:1;translate:0 0;}
}
.dt-card-head{
  height:calc(var(--u) * 1.8);
  width:56%;
  border-radius:calc(var(--u) * 0.9);
  background:var(--cyan-dark);
}
.dt-card-row{
  height:calc(var(--u) * 1.15);
  width:var(--w);
  border-radius:calc(var(--u) * 0.6);
  background:rgba(70,92,116,0.22);
  animation:dt-row-in 7s var(--ease) infinite;
  animation-delay:var(--d);
}
@keyframes dt-row-in{
  0%{opacity:0;}
  16%, 92%{opacity:1;}
  100%{opacity:1;}
}
/* The selected bay's face: which of its positions are occupied. */
.dt-card-face{
  margin-top:calc(var(--u) * 0.8);
  display:grid;
  grid-template-columns:repeat(4, 1fr);
  gap:calc(var(--u) * 0.7);
}
.dt-card-face i{
  aspect-ratio:1;
  border-radius:calc(var(--u) * 0.25);
  background:rgba(70,92,116,0.12);
}
.dt-card-face i.is-filled{background:#9ed3b4;}

/* From the selected bay across to the card. The selected row sits at 5.5% and is 54.5%
   wide, and the picked bay centres at 35.7% of that, so the box's right edge lands at about
   28% of the scene; the row's own shear lifts that point to roughly 62% down. */
.dt-leader{
  position:absolute;
  left:29%;right:28%;top:62%;
  height:1px;
  z-index:19;
  background:linear-gradient(90deg, rgba(0,146,200,0.75), rgba(0,146,200,0.18));
}
.dt-leader::after{
  content:"";
  position:absolute;
  left:0;top:50%;
  width:calc(var(--u) * 1.3);height:calc(var(--u) * 1.3);
  translate:-50% -50%;
  border-radius:50%;
  background:var(--cyan-dark);
}

@media (prefers-reduced-motion:reduce){
  /* The card and its rows start at opacity 0, so a zeroed duration would hide them. */
  .dt-card,.dt-card-row{animation:none;opacity:1;translate:0 0;}
  .dt-pick{animation:none;opacity:1;}
  .dt-aisle::after{display:none;}
  .dt-scan{animation:none;opacity:1;scale:1;}
  .dt-scan::after{display:none;}
}

@media (prefers-reduced-transparency:reduce){
  .dt-card{
    background:#fff;
    -webkit-backdrop-filter:none;
    backdrop-filter:none;
  }
}

@media (max-width:1000px){
  .dt-scene{max-width:400px;}
}

/* ---------- Responsive ---------- */
/* Between the mobile-nav breakpoint and roughly 1140px the full desktop header does
   not fit: brand, five nav links and three controls come to 1113px of content, which
   put a 1024px laptop into horizontal scroll. Rather than drop to a hamburger that
   early, the header sheds what it can spare - the brand's byline and the demo button,
   both of which appear again lower down - and tightens the nav. */
@media (max-width:1200px){
  .brand .sub{display:none;}
  .nav-cta .btn-primary{display:none;}
  nav.main{gap:0;}
  nav.main a{padding:8px 10px;}
  .nav-cta{gap:8px;}
}

@media (max-width:1000px){
  .hero-grid,.hero-grid.reverse{grid-template-columns:1fr;}

  /* Stacked, the image gets a tighter cap so the hero stays a sensible height.
     min-height deliberately stays: it is a floor, so on a tall window it still pulls
     the hero down to the fold and stops the next band peeking, and on a phone where
     the content is already taller it simply does nothing. Removing it here was a
     mistake that let 58px of the trust bar show at 768x1024. */
  .hero-visual img{max-height:42svh;}
  .feature-row,.feature-row.reverse{grid-template-columns:1fr;direction:ltr;}
  .whw{grid-template-columns:1fr;}
  .grid-5{grid-template-columns:repeat(3,minmax(0,1fr));}
  .pricing-grid{grid-template-columns:1fr;}
  .steps{grid-template-columns:1fr;}
}

@media (max-width:900px){
  /* Nav collapses into a glass sheet under the header. */
  nav.main{
    position:fixed;top:var(--header-h);left:12px;right:12px;
    flex-direction:column;align-items:stretch;gap:2px;
    padding:12px;
    background:rgba(255,255,255,0.86);
    -webkit-backdrop-filter:var(--glass-blur-lg);
    backdrop-filter:var(--glass-blur-lg);
    border:1px solid var(--glass-edge);
    border-radius:var(--r-lg);
    box-shadow:var(--inner-top), var(--shadow-lg);
    max-height:calc(100vh - var(--header-h) - 24px);
    overflow-y:auto;
    opacity:0;
    visibility:hidden;
    transform:translateY(-10px) scale(0.98);
    transform-origin:top center;
    transition:opacity .3s var(--ease), transform .34s var(--ease), visibility .34s;
  }
  nav.main.open{opacity:1;visibility:visible;transform:none;}
  nav.main a{padding:12px 14px;font-size:15px;border-radius:var(--r-sm);}
  .nav-toggle{display:flex;}
  .persona-split,.compare,.kpi-grid,.data-callout,.grid-3,.grid-5{grid-template-columns:1fr;}
  .footer-grid{grid-template-columns:1fr 1fr;}
  .footer-brand{grid-column:1 / -1;}
}

@media (max-width:620px){
  /* The header cannot hold brand, language switch, portal login, demo button and the
     menu button on one row - measured 455px of content in a 375px viewport, which put
     the whole page into horizontal scroll. The demo button is the one that goes: it is
     repeated in the hero immediately below and again in the closing banner, whereas the
     portal login is the only route into the app. */
  .nav-cta .btn-primary{display:none;}

  .container{padding:0 18px;}
  .grid-2{grid-template-columns:1fr;}
  .cta-banner{flex-direction:column;align-items:flex-start;}
  .cta-banner .btn{width:100%;}
  .scenario-body,.scenario-head{padding-left:20px;padding-right:20px;}
  .scenario-result{margin-left:20px;margin-right:20px;}
  .hero-actions .btn{width:100%;}
  .hero-actions{gap:10px;}
}

@media (max-width:520px){
  .nav-wrap{padding:10px 16px;gap:8px;}
  .brand .sub{display:none;}
  .brand{font-size:17px;}
  .brand-mark{height:22px;}
  .nav-cta{gap:6px;}
  .lang-switch{padding:2px;}
  .lang-switch a{padding:4px 7px;font-size:11px;}
  .nav-cta .btn-sm{padding:8px 12px;font-size:12px;}
  .footer-grid{grid-template-columns:1fr;}
  .footer-bottom{justify-content:flex-start;}
}

/* ---------- Accessibility ----------
   Both of these are real user settings, and both are load-bearing for this design:
   the whole thing is built out of motion and translucency. */
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto;}
  *,*::before,*::after{
    animation-duration:0.01ms !important;
    animation-iteration-count:1 !important;
    transition-duration:0.01ms !important;
  }
  .has-js .reveal{opacity:1;transform:none;}
  /* A scroll-driven animation takes its progress from the scrollport, not from a clock,
     so zeroing animation-duration above does nothing to it. The timeline has to be
     detached by hand or the mark keeps tracking the scroll. */
  .ct-mark{animation-timeline:none;animation:none;}
}

@media (prefers-reduced-transparency:reduce){
  body::before{background:linear-gradient(180deg, #ffffff 0%, var(--field) 100%);}
  .hero::before,.hero::after,.hero-visual .glow,.cta-banner::before{display:none;}
  header.site,header.site.is-scrolled,
  .card,.step,.whw-col,.persona-card,.price-card,.kpi-card,.faq-item,
  .compare-col,.data-stat,.scenario,.section-grey,.lang-switch,
  .nav-toggle,nav.main,.scene-stage{
    -webkit-backdrop-filter:none;
    backdrop-filter:none;
  }
  /* The hero scene keeps its structure; only the pane behind it goes solid. */
  .scene-stage{background:#fff;}
  header.site{background:#fff;border-bottom-color:var(--line);}
  .card,.step,.whw-col,.persona-card,.price-card,.kpi-card,.faq-item,
  .scenario,.data-stat,.compare-col.without{background:#fff;}
  .section-grey{background:#f2f6fa;}
}

@media print{
  header.site,footer.site,.hero::before,.hero::after{display:none;}
  body::before{display:none;}
  body{background:#fff;}
}
