/* =========================================================
   WareVision - the public landing site

   Loaded after the design system's tokens (WvHead Kind=Site), and specific to this site,
   which is why it lives here and not in WareVision.Design. Every colour, face, radius and
   shadow is a token: there is not one colour literal below. Tints are color-mix() of a
   token with transparent.

   The look is the brand's LandingPage design (brand/design-reference): flat surfaces on
   Paper, hairlines instead of glass, Archivo throughout, one Night band per page. Sodium
   Amber is light, so it is only ever a fill (the marker behind the headline, one CTA, the
   step numbers) and never text on a light ground. Links and data are Signal Teal.

   Class names are the ones the pages and landing.js already use (header.site, nav.main,
   .nav-menu, .lang-switch, .reveal). No wv- prefix: those names belong to the design system's own sheets.
   ========================================================= */

:root{
  --maxw:1200px;
  --header-h:68px;
  --pad-x:24px;
}

/* ---------- Reset and base ---------- */
*,*::before,*::after{box-sizing:border-box;}

html{
  scroll-behavior:smooth;
  -webkit-text-size-adjust:100%;
}

/* No overflow-x clamp on html or body: it turns the body into a scroll container and
   breaks position:sticky on the header. Anything that would overhang is clipped by its
   own section instead. */
body{
  margin:0;
  font-family:var(--font-sans);
  font-size:16px;
  line-height:1.6;
  color:var(--ink);
  background:var(--surface);
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  text-rendering:optimizeLegibility;
}

img{max-width:100%;height:auto;display:block;}
a{color:inherit;text-decoration:none;}

:focus-visible{
  outline:2px solid var(--focus);
  outline-offset:3px;
  border-radius:var(--radius-xs);
}

.container{max-width:var(--maxw);margin:0 auto;padding:0 var(--pad-x);}

section{padding:clamp(56px, 7vw, 96px) 0;position:relative;}
section[id], div[id]{scroll-margin-top:var(--header-h);}

/* ---------- Type ---------- */
h1,h2,h3,h4{margin:0 0 .5em;color:var(--ink);text-wrap:balance;}
h1,h2{font-family:var(--font-display);font-weight:700;}
h1{font-size:clamp(38px, 5.2vw, 64px);line-height:1.02;letter-spacing:-.035em;}
h2{font-size:clamp(28px, 3.3vw, 40px);line-height:1.1;letter-spacing:-.03em;}
h3{font-family:var(--font-display);font-weight:600;font-size:clamp(18px, 1.6vw, 22px);line-height:1.25;letter-spacing:-.02em;}
h4{font-family:var(--font-sans);font-weight:600;font-size:16px;line-height:1.35;}

p{margin:0 0 16px;color:var(--ink-muted);}
p:last-child{margin-bottom:0;}

.lede{
  font-size:clamp(17px, 1.5vw, 19px);
  line-height:1.5;
  color:var(--ink-muted);
  max-width:34em;
}

strong,b{color:var(--ink);font-weight:600;}
em{font-style:italic;}

code{
  font-family:var(--font-sans);
  font-variant-numeric:tabular-nums;
  font-size:.92em;
  letter-spacing:.02em;
  padding:1px 6px;
  border-radius:var(--radius-xs);
  background:var(--surface-sunken);
  border:1px solid var(--line);
  color:var(--ink);
}

/* The section label: spaced capitals with a small amber square, the brand's "label" style. */
.eyebrow{
  display:inline-flex;align-items:center;gap:10px;
  margin-bottom:14px;
  font:var(--type-label);
  letter-spacing:var(--tracking-label);
  text-transform:uppercase;
  color:var(--ink-muted);
}
.eyebrow .dot{
  width:8px;height:8px;flex:none;
  border-radius:2px;
  background:var(--amber);
}

.section-head{max-width:46em;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;}
.section-head h2{max-width:18em;}
.section-head:not(.left) h2{margin-inline:auto;}

.text-center{text-align:center;}

/* The marker behind a headline's last words: Sodium Amber as a fill, the one place a
   headline carries the accent. */
.hl{
  font-style:normal;
  background:linear-gradient(transparent 60%, var(--amber) 60%, var(--amber) 92%, transparent 92%);
  padding:0 .04em;
  -webkit-box-decoration-break:clone;
  box-decoration-break:clone;
}

/* ---------- Grid ---------- */
.grid{display:grid;gap:var(--space-5);}
.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 ----------
   The brand's three: primary is Night (the everyday action), accent is Amber (only the
   demo request), secondary is White on a hairline. */
.btn{
  --h:var(--control-md);
  display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);
  min-height:var(--h);
  padding:0 var(--space-5);
  border-radius:var(--radius-sm);
  border:1px solid var(--line);
  background:var(--surface-raised);
  color:var(--ink);
  font:600 15px/1.2 var(--font-sans);
  letter-spacing:-.005em;
  white-space:nowrap;
  cursor:pointer;
  box-shadow:var(--shadow-1);
  transition:background-color .16s ease, border-color .16s ease, transform .16s ease;
}
.btn:hover{border-color:var(--line-strong);}
.btn:active{transform:translateY(1px);}
.btn-primary{background:var(--inverse);border-color:var(--inverse);color:var(--on-inverse);}
.btn-primary:hover{background:var(--inverse-hover);border-color:var(--inverse-hover);}
.btn-accent{background:var(--amber);border-color:var(--amber);color:var(--on-amber);}
.btn-accent:hover{background:var(--amber-hover);border-color:var(--amber-hover);}
.btn-ghost{background:var(--surface-raised);color:var(--ink);}
.btn-ghost-invert{background:transparent;border-color:var(--night-line);color:var(--on-night);box-shadow:none;}
.btn-ghost-invert:hover{border-color:var(--on-night-muted);}
.btn-sm{--h:var(--control-sm);padding:0 var(--space-3);font-size:13.5px;}
.btn-lg{--h:var(--control-lg);padding:0 var(--space-6);font-size:15px;border-radius:10px;}
.btn-block{width:100%;}

.link-more{
  display:inline-flex;align-items:center;gap:6px;
  font-weight:600;
  color:var(--signal);
  text-decoration:underline;
  text-decoration-color:color-mix(in srgb, var(--signal) 35%, transparent);
  text-underline-offset:4px;
  transition:text-decoration-color .16s ease;
}
.link-more:hover{text-decoration-color:var(--signal);}

/* ---------- Header ----------
   Paper, then a hairline once the page scrolls under it (landing.js adds .is-scrolled
   past 40px). Solid, never frosted. */
header.site{
  position:sticky;top:0;z-index:var(--z-pop);
  background:var(--surface);
  border-bottom:1px solid transparent;
  transition:border-color .2s ease, box-shadow .2s ease;
}
header.site.is-scrolled{border-bottom-color:var(--line);box-shadow:var(--shadow-1);}

.nav-wrap{
  display:flex;align-items:center;gap:var(--space-8);
  max-width:var(--maxw);margin:0 auto;
  padding:12px var(--pad-x);
  min-height:var(--header-h);
}

.brand{display:flex;align-items:center;flex-shrink:0;}
.brand-logo{height:21px;width:auto;}
.brand-mark-only{display:none;height:22px;width:auto;}

nav.main{display:flex;align-items:center;gap:var(--space-1);}
nav.main a{
  position:relative;
  font-weight:500;font-size:14.5px;
  color:var(--ink-muted);
  padding:8px 12px;
  border-radius:var(--radius-sm);
  transition:color .16s ease, background-color .16s ease;
}
nav.main a:hover{color:var(--ink);}
nav.main > a.active,
nav.main .nav-menu-trigger.active{color:var(--ink);}
/* The active marker, amber as a fill: the same idea as the portal's nav rail. */
nav.main > a.active::after,
nav.main .nav-menu-trigger.active::after{
  content:"";
  position:absolute;left:12px;right:12px;bottom:0;
  height:2px;border-radius:2px;
  background:var(--amber);
}

/* ---------- Features menu ----------
   Hover and :focus-within open it, so it needs no script; landing.js only adds a tap
   toggle for touch screens wide enough to get the desktop header. The panel hangs from an
   invisible bridge (the padding-top) so the pointer can cross from trigger to panel. */
.nav-menu{position:relative;}
nav.main .nav-menu-trigger{display:inline-flex;align-items:center;gap:7px;}
.nav-caret{
  width:6px;height:6px;
  border-right:1.6px solid currentColor;
  border-bottom:1.6px solid currentColor;
  rotate:45deg;
  translate:0 -2px;
  transition:rotate .2s var(--ease), translate .2s var(--ease);
}
.nav-menu-panel{
  position:absolute;
  top:100%;left:0;
  width:340px;
  padding:14px 0 0;
  display:flex;flex-direction:column;
  opacity:0;
  visibility:hidden;
  transform:translateY(-4px);
  transition:opacity .16s ease, transform .2s var(--ease), visibility .2s;
}
.nav-menu-panel::before{
  content:'';
  position:absolute;inset:6px 0 0;
  background:var(--surface-raised);
  border:1px solid var(--line);
  border-radius:var(--radius-md);
  box-shadow:var(--shadow-pop);
  z-index:-1;
}
.nav-menu:hover .nav-menu-panel,
.nav-menu:focus-within .nav-menu-panel,
.nav-menu.is-open .nav-menu-panel{opacity:1;visibility:visible;transform:none;}
.nav-menu:hover .nav-caret,
.nav-menu:focus-within .nav-caret,
.nav-menu.is-open .nav-caret{rotate:225deg;translate:0 2px;}

nav.main .nav-menu-item{
  display:flex;flex-direction:column;gap:2px;
  margin:0 6px;
  padding:10px 12px;
  border-radius:var(--radius-sm);
}
nav.main .nav-menu-item:first-child{margin-top:6px;}
nav.main .nav-menu-item:last-child{margin-bottom:6px;}
nav.main .nav-menu-item:hover{background:var(--surface);}
nav.main .nav-menu-item.active{background:var(--signal-soft);}
.nav-menu-label{font-weight:600;font-size:14px;color:var(--ink);}
.nav-menu-summary{font-weight:400;font-size:12.5px;line-height:1.4;color:var(--ink-muted);}

.nav-cta{display:flex;align-items:center;gap:10px;margin-left:auto;}

.lang-switch{
  display:flex;align-items:center;gap:2px;
  padding:2px;border-radius:var(--radius-sm);
  background:var(--surface-sunken);
  border:1px solid var(--line);
}
.lang-switch a{
  font-weight:600;font-size:12px;letter-spacing:.04em;
  color:var(--ink-muted);
  padding:4px 9px;border-radius:6px;
  transition:color .16s ease, background-color .16s ease;
}
.lang-switch a:hover:not(.lang-active){color:var(--ink);}
.lang-switch a.lang-active{background:var(--surface-raised);color:var(--ink);box-shadow:var(--shadow-1);}

.nav-toggle{
  display:none;
  width:40px;height:40px;
  align-items:center;justify-content:center;
  flex-direction:column;gap:5px;
  background:var(--surface-raised);
  border:1px solid var(--line);
  border-radius:var(--radius-sm);
  cursor:pointer;
}
.nav-toggle span{
  display:block;width:17px;height:1.5px;border-radius:2px;
  background:var(--ink);
  transition:transform .24s var(--ease), opacity .16s 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 ---------- */
.hero{
  padding:clamp(40px, 5vw, 72px) 0 clamp(56px, 7vw, 104px);
  overflow:hidden;
  isolation:isolate;
}
/* The dot grid of a floor plan, fading out to the left, under the product shot. */
.hero::before{
  content:"";
  position:absolute;inset:0;z-index:-1;
  background-image:radial-gradient(var(--line) 1.2px, transparent 1.2px);
  background-size:26px 26px;
  -webkit-mask-image:radial-gradient(ellipse 65% 70% at 78% 40%, var(--ink) 15%, transparent 72%);
  mask-image:radial-gradient(ellipse 65% 70% at 78% 40%, var(--ink) 15%, transparent 72%);
  pointer-events:none;
}

.hero-grid{
  display:grid;grid-template-columns:1fr 1fr;
  gap:clamp(32px, 5vw, 64px);align-items:center;
}
.hero-grid.reverse{grid-template-columns:1fr 1fr;}
.hero-grid > *{min-width:0;}
.hero h1{margin-bottom:0;}
.hero .lede{margin-top:22px;}

.hero-actions{
  display:flex;gap:var(--space-3);align-items:center;flex-wrap:wrap;
  margin-top:32px;
}

.hero-visual{position:relative;}
.hero-visual img{
  max-height:calc(100svh - var(--header-h) - 140px);
  width:auto;
  margin-inline:auto;
}

/* Home: the announcement pill above the headline. */
.announce{
  display:inline-flex;align-items:center;gap:10px;
  height:30px;
  padding:0 14px 0 4px;
  margin-bottom:24px;
  border-radius:var(--radius-full);
  border:1px solid var(--line);
  background:var(--surface-raised);
  box-shadow:var(--shadow-1);
  font-size:13px;
  color:var(--ink-muted);
  transition:border-color .16s ease;
}
.announce:hover{border-color:var(--line-strong);color:var(--ink);}
.announce b{
  height:22px;padding:0 9px;
  display:inline-grid;place-items:center;
  border-radius:var(--radius-full);
  background:var(--amber);color:var(--on-amber);
  font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
}
.hero-home h1{font-size:clamp(44px, 6vw, 72px);line-height:1;}
.hero-home .hero-grid{grid-template-columns:minmax(0,1fr) minmax(0,1.12fr);}

/* Home: three figures under the CTAs. */
.proof{
  display:flex;gap:var(--space-8);flex-wrap:wrap;
  margin:44px 0 0;padding-top:24px;
  border-top:1px solid var(--line);
}
.proof div{min-width:0;}
.proof dt{
  font-family:var(--font-display);font-weight:600;
  font-size:26px;line-height:30px;letter-spacing:-.03em;
  color:var(--ink);
}
.proof dd{margin:0;font-size:13px;color:var(--ink-muted);}

/* ---------- Trust line ---------- */
.trust{padding:clamp(28px, 4vw, 48px) 0;border-top:1px solid var(--line);}
.trust p{
  text-align:center;
  font-size:14.5px;
  line-height:1.65;
  margin:0 auto;
  max-width:74ch;
  padding:0 var(--pad-x);
}
.trust strong{font-weight:600;}

/* ---------- Badges ---------- */
.badge,.chip{
  display:inline-flex;align-items:center;gap:6px;
  font:var(--type-label);
  letter-spacing:var(--tracking-label);
  text-transform:uppercase;
  padding:4px 9px;border-radius:var(--radius-xs);
  border:1px solid transparent;
  white-space:nowrap;
}
.badge-live{background:var(--signal-soft);color:var(--signal);}
.badge-flagship{background:var(--amber-soft);color:var(--amber-text);}
.badge-soon{background:var(--surface-sunken);color:var(--ink-muted);border-color:var(--line);}

/* ---------- Surfaces ----------
   Every card is the same object: White on a hairline, the brand's panel. A hover lifts
   by 2px and deepens the shadow; nothing blurs. */
.card,.module,.persona-card,.step,.whw-col,.price-card,.kpi-card,.compare-col,.data-stat,.scenario,.faq-item{
  background:var(--surface-raised);
  border:1px solid var(--line);
  border-radius:var(--radius-md);
  box-shadow:var(--shadow-1);
}
.section-grey .card,.section-grey .module,.section-grey .persona-card,.section-grey .step,
.section-grey .whw-col,.section-grey .kpi-card,.section-grey .compare-col,.section-grey .data-stat,
.section-grey .scenario,.section-grey .faq-item{background:var(--surface);box-shadow:none;}
.card{padding:var(--space-6);transition:box-shadow .2s ease, transform .2s ease;}
.card:hover{box-shadow:var(--shadow-2);transform:translateY(-2px);}
.card h3{margin-bottom:10px;}
.card p{font-size:14.5px;}

/* ---------- Module cards (home) ---------- */
.modules{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--space-4);}
.module{
  display:flex;flex-direction:column;align-items:flex-start;gap:var(--space-3);
  padding:var(--space-5);
  min-width:0;
  transition:box-shadow .2s ease, transform .2s ease;
}
.module:hover{box-shadow:var(--shadow-2);transform:translateY(-2px);}
.module-lockup{width:100%;max-width:215px;height:auto;margin-bottom:var(--space-1);}
.module p{margin:0;font-size:14px;line-height:1.55;flex:1;}
.module .price-tag,.module-card .price-tag{
  width:100%;
  padding-top:var(--space-3);
  border-top:1px solid var(--line);
  font-size:13px;color:var(--ink-muted);
}
.module .price-tag b,.module-card .price-tag b{font-family:var(--font-display);font-size:16px;font-weight:600;color:var(--ink);}
.module .price-tag a,.module-card .price-tag a{color:var(--ink-muted);text-decoration:underline;text-underline-offset:2px;}
.module .price-tag a:hover,.module-card .price-tag a:hover{color:var(--ink);}
.module a.more,.module-card a.more{
  font-weight:600;font-size:14px;
  color:var(--signal);
  transition:gap .16s ease;
}
.module a.more:hover,.module-card a.more:hover{text-decoration:underline;text-underline-offset:4px;}

/* ---------- Night band ---------- */
.band,.section-dark{
  background:var(--night);
  color:var(--on-night);
  --focus:var(--signal-night);
}
.band h2,.band h3,.band h4,.section-dark h2,.section-dark h3,.section-dark h4{color:var(--on-night);}
.band p,.section-dark p,.section-dark .lede{color:var(--on-night-muted);}
.band strong,.band b,.section-dark strong,.section-dark b{color:var(--on-night);}
/* On Night the label is amber: light on dark is the one place amber may be read. */
.band .eyebrow,.section-dark .eyebrow{color:var(--amber);}
.section-dark .link-more{color:var(--signal-night);text-decoration-color:color-mix(in srgb, var(--signal-night) 40%, transparent);}

.band-grid{display:grid;grid-template-columns:1fr 1fr;gap:clamp(32px, 5vw, 64px);align-items:center;}
.band-grid h2{font-size:clamp(28px, 3vw, 36px);}
.band-grid p{font-size:17px;line-height:26px;max-width:30em;}
.band-steps{list-style:none;margin:0;padding:0;display:grid;gap:10px;}
.band-steps li{
  display:grid;grid-template-columns:34px 1fr;gap:14px;align-items:start;
  padding:14px 16px;
  border-radius:var(--radius-md);
  background:var(--night-raised);
  border:1px solid var(--night-line);
  font-size:15px;line-height:22px;font-weight:600;
  color:var(--on-night);
}
.band-steps li b{
  width:34px;height:34px;
  border-radius:9px;
  display:grid;place-items:center;
  background:var(--amber);color:var(--on-amber);
  font-family:var(--font-display);font-size:15px;
}
.band-steps li span{display:block;margin-top:2px;font-size:13.5px;line-height:19px;font-weight:400;color:var(--on-night-muted);}

/* ---------- Alternate band ---------- */
.section-grey{
  background:var(--surface-raised);
  border-top:1px solid var(--line);
  border-bottom:1px solid var(--line);
}

/* ---------- Feature rows ---------- */
.feature-row{
  display:grid;grid-template-columns:1fr 1fr;
  gap:clamp(32px, 5vw, 64px);align-items:center;
  padding:clamp(36px, 5vw, 64px) 0;
  border-top:1px solid var(--line);
}
.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.9vw, 26px);}
.feature-row ul{margin:20px 0 0;padding:0;list-style:none;}
.feature-row ul li{
  position:relative;
  padding-left:22px;margin-bottom:10px;
  font-size:15px;color:var(--ink-muted);
}
.feature-row ul li::before{
  content:"";
  position:absolute;left:2px;top:.62em;
  width:8px;height:8px;border-radius:2px;
  background:var(--signal);
}

/* ---------- Steps ---------- */
.steps{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-5);margin-top:8px;}
.step{padding:var(--space-6);}
.step .num{
  width:34px;height:34px;margin-bottom:14px;
  display:grid;place-items:center;
  border-radius:9px;
  background:var(--night);color:var(--on-night);
  font-family:var(--font-display);font-weight:600;font-size:15px;
}
.step h4{margin:0 0 8px;font-size:16.5px;}
.step p{font-size:14.5px;}

/* ---------- Closing CTA ---------- */
.cta-banner{
  display:flex;align-items:center;justify-content:space-between;gap:var(--space-8);
  padding:clamp(28px, 4vw, 48px);
  border-radius:var(--radius-lg);
  background:var(--night);
  color:var(--on-night);
  --focus:var(--signal-night);
}
.cta-banner h2{color:var(--on-night);margin-bottom:10px;font-size:clamp(24px, 2.4vw, 32px);}
.cta-banner p{color:var(--on-night-muted);margin-bottom:0;max-width:40em;}
.cta-banner .cta-actions{display:flex;gap:12px;flex-wrap:wrap;flex:none;}

/* ---------- Pricing ---------- */
.pricing-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--space-4);align-items:stretch;}
.price-card{
  position:relative;
  display:flex;flex-direction:column;
  padding:64px var(--space-6) var(--space-6);
  transition:box-shadow .2s ease, transform .2s ease;
}
.price-card:hover{box-shadow:var(--shadow-2);transform:translateY(-2px);}
.price-card.featured{border-color:var(--ink);box-shadow:0 0 0 1px var(--ink), var(--shadow-2);}
.price-card .badge,.price-card .chip{position:absolute;top:24px;left:var(--space-6);}
.price-card h3{margin-bottom:4px;}
.price-card .tagline{font-size:13.5px;margin-bottom:22px;}
.price-line{
  display:flex;justify-content:space-between;align-items:baseline;gap:12px;
  padding:10px 0;
  border-top:1px solid var(--line);
  font-size:13.5px;color:var(--ink-muted);
}
.price-line:first-of-type{border-top:none;}
.price-line b{font-family:var(--font-display);font-weight:600;font-size:17px;color:var(--ink);white-space:nowrap;}
.price-card ul{list-style:none;padding:0;margin:22px 0 0;flex:1;}
.price-card ul li{position:relative;padding-left:20px;margin-bottom:9px;font-size:14px;color:var(--ink-muted);}
.price-card ul li::before{
  content:"";
  position:absolute;left:1px;top:.6em;
  width:7px;height:7px;border-radius:2px;
  background:var(--signal);
}
.price-card .btn{margin-top:24px;}
.tbd-box{
  padding:14px 16px;
  border-radius:var(--radius-sm);
  background:var(--surface);
  border:1px dashed var(--line-strong);
}
.tbd-box .tbd{font-family:var(--font-display);font-weight:600;font-size:22px;color:var(--ink);}
.tbd-box .tbd-note{font-size:12.5px;color:var(--ink-muted);margin-top:3px;}

/* ---------- Why / how / what ---------- */
.whw{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-5);}
.whw-col{padding:var(--space-6);}
.whw-tag{
  display:inline-block;margin-bottom:12px;
  font:var(--type-label);letter-spacing:var(--tracking-label);text-transform:uppercase;
  color:var(--signal);
}
.whw-col h3{font-size:19px;margin-bottom:10px;}
.whw-col p{font-size:14.5px;}

/* ---------- Incident readout ---------- */
.scenario{overflow:hidden;}
.scenario-head{
  display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;
  padding:14px 26px;
  border-bottom:1px solid var(--line);
  background:var(--surface);
}
.scenario-head .tag{font:var(--type-label);letter-spacing:var(--tracking-label);text-transform:uppercase;color:var(--ink);}
.scenario-head .ts{font-size:13px;color:var(--ink-muted);font-variant-numeric:tabular-nums;}
.scenario-body{padding:20px 26px 6px;}
.scenario-line{
  display:grid;grid-template-columns:12px 1fr;gap:14px;align-items:start;
  padding:12px 0;
  border-top:1px solid var(--line);
}
.scenario-line:first-child{border-top:none;}
.scenario-line .dot{
  width:10px;height:10px;margin-top:.5em;border-radius:50%;
  background:var(--signal);
  box-shadow:0 0 0 3px var(--signal-soft);
}
.scenario-line .dot.warn{background:var(--crit);box-shadow:0 0 0 3px var(--crit-soft);}
.scenario-line .dot.ok{background:var(--good);box-shadow:0 0 0 3px var(--good-soft);}
.scenario-line p{margin:0;font-size:15px;color:var(--ink);}
.scenario-result{
  margin:8px 26px 24px;
  padding:16px 18px;
  border-radius:var(--radius-sm);
  background:var(--surface);
  border-left:3px solid var(--crit);
}
.scenario-result .label{
  display:block;margin-bottom:4px;
  font:var(--type-label);letter-spacing:var(--tracking-label);text-transform:uppercase;
  color:var(--crit);
}
.scenario-result p{margin:0;font-size:15px;color:var(--ink);font-weight:500;}
.scenario-more{margin-top:18px;text-align:center;}

/* ---------- Without / with ---------- */
.compare{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-5);margin-top:28px;}
.compare-col{padding:var(--space-6);}
.compare-col.with{border-color:var(--signal);box-shadow:0 0 0 1px var(--signal);}
.compare-col .clock{font-family:var(--font-display);font-weight:600;font-size:27px;margin-bottom:6px;letter-spacing:-.02em;}
.compare-col.without .clock{color:var(--ink-faint);}
.compare-col.with .clock{color:var(--signal);}
.compare-col h4{font:var(--type-label);letter-spacing:var(--tracking-label);text-transform:uppercase;color:var(--ink-muted);margin-bottom:10px;}
.compare-col p{font-size:14.5px;color:var(--ink);}

/* ---------- Persona cards ---------- */
.persona-split{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-5);}
.persona-card{padding:var(--space-6);}
.persona-card .role{margin-bottom:18px;padding-bottom:16px;border-bottom:1px solid var(--line);}
.persona-card .role h4{margin:0;font-size:17px;}
.persona-card .role span{display:block;font-size:12.5px;color:var(--ink-muted);margin-top:2px;}
.persona-card ul{list-style:none;padding:0;margin:0;}
.persona-card ul li{position:relative;padding-left:22px;margin-bottom:12px;font-size:15px;color:var(--ink-muted);}
.persona-card ul li:last-child{margin-bottom:0;}
.persona-card ul li::before{
  content:"";
  position:absolute;left:2px;top:.62em;
  width:8px;height:8px;border-radius:2px;
  background:var(--signal);
}
.persona-card em{color:var(--ink-muted);}

/* ---------- Data callout ---------- */
.data-callout{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-5);margin-top:8px;}
.data-stat{padding:var(--space-6);text-align:center;}
.data-stat .num{font-family:var(--font-display);font-weight:700;font-size:clamp(32px, 3.4vw, 44px);line-height:1;letter-spacing:-.03em;color:var(--ink);}
.data-stat .num span{font-size:.55em;color:var(--ink-muted);margin-left:2px;}
.data-stat .lbl{font-size:13px;color:var(--ink-muted);margin-top:8px;}
.source-note{font-size:12px;color:var(--ink-faint);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;font-size:14px;}
.kpi-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--space-4);}
.kpi-card{padding:var(--space-5);transition:box-shadow .2s ease, transform .2s ease;}
.kpi-card:hover{box-shadow:var(--shadow-2);transform:translateY(-2px);}
.kpi-card h4{font-size:15px;margin:0 0 8px;}
.kpi-card > p{font-size:14px;margin:0 0 14px;color:var(--ink);}
.kpi-card .best{font-size:13.5px;padding-top:13px;border-top:1px solid var(--line);}
.kpi-card .best .lbl{
  display:block;margin-bottom:4px;
  font:var(--type-label);letter-spacing:var(--tracking-label);text-transform:uppercase;
  color:var(--ink-muted);
}
.kpi-card .best.priced .lbl{color:var(--signal);}
.kpi-card .best p{margin:0;font-size:13.5px;}

/* ---------- FAQ ---------- */
.faq-list{max-width:820px;display:flex;flex-direction:column;gap:10px;}
.faq-item{padding:0 22px;transition:box-shadow .2s ease;}
.faq-item[open]{box-shadow:var(--shadow-2);}
.faq-item summary{
  display:flex;justify-content:space-between;align-items:center;gap:16px;
  padding:18px 0;
  font-weight:600;font-size:16px;color:var(--ink);
  cursor:pointer;list-style:none;
}
.faq-item summary::-webkit-details-marker{display:none;}
.faq-item summary::after{
  content:"";
  flex:none;
  width:9px;height:9px;
  border-right:1.75px solid var(--ink-muted);
  border-bottom:1.75px solid var(--ink-muted);
  rotate:45deg;translate:0 -3px;
  transition:rotate .2s var(--ease), translate .2s var(--ease);
}
.faq-item[open] summary::after{rotate:225deg;translate:0 2px;}
.faq-item p{margin:0 0 20px;font-size:14.5px;max-width:70ch;}

/* ---------- Footer ---------- */
footer.site{
  padding:48px 0 24px;
  background:var(--surface);
  border-top:1px solid var(--line);
  font-size:14px;
  color:var(--ink-muted);
}
.footer-grid{display:grid;grid-template-columns:2fr 1fr 1fr;gap:var(--space-8);}
.footer-grid h4{font:var(--type-label);letter-spacing:var(--tracking-label);text-transform:uppercase;color:var(--ink-muted);margin-bottom:14px;}
.footer-grid a{display:block;margin-bottom:8px;color:var(--ink);}
.footer-grid a:hover{color:var(--signal);}
.footer-brand .brand{margin-bottom:14px;display:inline-flex;}
.footer-brand .brand-logo{height:22px;}
.footer-brand p{font-size:14px;max-width:34ch;margin:0 0 6px;}
.footer-brand .footer-tagline{font-family:var(--font-display);font-weight:600;font-size:18px;letter-spacing:-.02em;color:var(--ink);}
.footer-bottom{
  display:flex;justify-content:flex-end;
  margin-top:36px;padding-top:18px;
  border-top:1px solid var(--line);
  font-size:13px;
}

/* ---------- Scroll reveal ----------
   landing.js adds .reveal only when it is sure to add .in-view again, so with scripting
   blocked nothing is hidden. Short and small: the brand's 150 to 200 ms. */
.has-js .reveal{opacity:0;transform:translateY(8px);transition:opacity .4s ease, transform .4s var(--ease);}
.has-js .reveal.in-view{opacity:1;transform:none;}

/* =========================================================
   Floor map (Components/Shared/FloorMapScene.razor)

   The brand's product shot: the floor map in its Night frame, a KPI card and a toast over
   it. A drawing until B7 replaces it with a screenshot of the portal's own map.
   ========================================================= */
.fm{position:relative;margin:0;max-width:680px;margin-inline:auto;}
.fm-frame{
  position:relative;
  padding:8px;
  border-radius:var(--radius-lg);
  background:var(--surface-raised);
  border:1px solid var(--line);
  box-shadow:var(--shadow-pop);
}
.fm-map{
  position:relative;
  overflow:hidden;
  border-radius:var(--radius-md);
  background:var(--night);
  color:var(--on-night);
  border:1px solid var(--night-line);
}
.fm-floor{display:block;width:100%;height:auto;}
.fm-grid{fill:var(--night-line);}
.fm-rack{fill:var(--night-raised);stroke:var(--night-line);stroke-width:1;}
.fm-bay{stroke:var(--night-line);stroke-width:1;}
.fm-aisle{fill:var(--on-night-muted);font:600 10px var(--font-sans);letter-spacing:.12em;}
.fm-dock{fill:none;stroke:var(--night-line);stroke-width:1.5;}
.fm-dock-label{fill:var(--on-night-muted);font:500 9px var(--font-sans);}
.fm-heat-good{stop-color:var(--signal-night);}
.fm-heat-warn{stop-color:var(--warn-night);}
.fm-heat-crit{stop-color:var(--crit-night);}
.fm-dev{stroke:var(--night);stroke-width:2;}
.fm-good{fill:var(--good-night);}
.fm-warn{fill:var(--warn-night);}
.fm-crit{fill:var(--crit-night);}
.fm-ring{
  fill:none;stroke:var(--good-night);stroke-width:1.5;
  transform-box:fill-box;transform-origin:center;
  animation:fm-ring 2.4s ease-out infinite;
}
@keyframes fm-ring{
  0%{transform:scale(1);opacity:.9;}
  100%{transform:scale(3.2);opacity:0;}
}
.fm-sel{fill:var(--amber);stroke:var(--night);stroke-width:2.5;}
.fm-sel-ring{fill:none;stroke:var(--amber);stroke-width:1.5;opacity:.55;}
.fm-path{fill:none;stroke:var(--amber);stroke-width:1.5;stroke-dasharray:3 4;opacity:.7;}

.fm-head{
  position:absolute;left:var(--space-4);top:var(--space-3);right:var(--space-4);
  display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);
  pointer-events:none;
}
.fm-head strong{font-size:13px;font-weight:600;color:var(--on-night);}
.fm-head span{font-size:12px;color:var(--on-night-muted);}

.fm-callout{
  position:absolute;
  left:calc(23.2% + 26px);top:calc(56% - 92px);
  width:min(200px, 40%);
  padding:10px 12px;
  border-radius:var(--radius-md);
  background:var(--night-raised);
  border:1px solid var(--night-line);
  box-shadow:var(--shadow-pop);
  font-size:11.5px;line-height:16px;
  color:var(--on-night-muted);
}
.fm-callout strong{display:block;color:var(--on-night);font-size:13px;line-height:18px;font-weight:600;}
.fm-callout dl{display:grid;grid-template-columns:auto 1fr;gap:2px 10px;margin:8px 0 0;}
.fm-callout dt{color:var(--on-night-muted);}
.fm-callout dd{margin:0;text-align:right;color:var(--on-night);font-variant-numeric:tabular-nums;}
.fm-callout dd.fm-warn{fill:none;color:var(--warn-night);}

.fm-float{position:absolute;z-index:3;}
.fm-float-kpi{left:-48px;bottom:-72px;width:228px;}
.fm-float-toast{right:-20px;top:-58px;width:272px;}

.fm-kpi{
  display:flex;flex-direction:column;gap:6px;
  padding:var(--space-4) var(--space-4) 0;
  overflow:hidden;
  border-radius:var(--radius-md);
  background:var(--surface-raised);
  border:1px solid var(--line);
  box-shadow:var(--shadow-pop);
}
.fm-kpi-top{display:flex;align-items:center;justify-content:space-between;gap:var(--space-2);}
.fm-label{font:var(--type-label);letter-spacing:var(--tracking-label);text-transform:uppercase;color:var(--ink-muted);}
.fm-delta{
  padding:2px 7px;border-radius:var(--radius-xs);
  background:var(--good-soft);color:var(--good);
  font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;
}
.fm-kpi-value{
  display:flex;align-items:baseline;gap:8px;
  font:var(--type-metric);letter-spacing:var(--tracking-metric);
  font-variant-numeric:tabular-nums;color:var(--ink);
}
.fm-kpi-value small{font-size:16px;letter-spacing:-.02em;color:var(--ink-muted);}
.fm-kpi-note{font-size:12.5px;line-height:18px;color:var(--ink-muted);}
.fm-spark{display:block;width:calc(100% + 2 * var(--space-4));height:46px;margin:6px calc(-1 * var(--space-4)) 0;}
.fm-spark-a{stop-color:var(--signal);stop-opacity:.18;}
.fm-spark-b{stop-color:var(--signal);stop-opacity:0;}
.fm-spark-line{fill:none;stroke:var(--signal);stroke-width:2;}
.fm-spark-end{fill:var(--signal);stroke:var(--surface-raised);stroke-width:2;}

.fm-toast{
  display:flex;align-items:center;gap:12px;
  padding:12px 14px;
  border-radius:var(--radius-md);
  background:var(--night);
  color:var(--on-night);
  border:1px solid var(--night-line);
  box-shadow:var(--shadow-pop);
}
.fm-toast-ico{
  flex:none;width:28px;height:28px;
  display:grid;place-items:center;
  border-radius:var(--radius-full);
  background:color-mix(in srgb, var(--good-night) 18%, transparent);
  color:var(--good-night);
}
.fm-toast-ico svg{width:15px;height:15px;}
.fm-toast-txt{min-width:0;}
.fm-toast-txt strong{display:block;font-size:13.5px;line-height:19px;font-weight:600;color:var(--on-night);}
.fm-toast-txt span{display:block;font-size:12.5px;line-height:18px;color:var(--on-night-muted);}


/* ---------- Page details the feature pages use ---------- */
.eyebrow .chip{margin-left:6px;}
.feature-row.feature-row-first{border-top:none;padding-top:0;}
.scenario + .scenario{margin-top:24px;}
.text-link{
  color:var(--signal);font-weight:600;
  text-decoration:underline;
  text-decoration-color:color-mix(in srgb, var(--signal) 35%, transparent);
  text-underline-offset:3px;
}
.text-link:hover{text-decoration-color:var(--signal);}
.section-head h1,.text-center h1{max-width:18em;margin-inline:auto;}
.text-center .lede{margin-inline:auto;}
.section-head.left h1{margin-inline:0;}
.pricing-note{margin-top:32px;text-align:center;font-size:14px;color:var(--ink-muted);}
.pricing-note a{color:var(--signal);font-weight:600;text-decoration:underline;text-underline-offset:3px;}
.section-dark .section-head .lede{margin-inline:auto;}
/* The closing banner on White, for a page whose last band is already Night. */
.cta-banner.cta-light{background:var(--surface-raised);color:var(--ink);border:1px solid var(--line);box-shadow:var(--shadow-1);--focus:var(--signal);}
.cta-banner.cta-light h2{color:var(--ink);}
.cta-banner.cta-light p{color:var(--ink-muted);}
/* A banner sitting inside a Night band: the band is the ground already. */
.cta-banner.cta-flat{padding:0;border-radius:0;}

/* =========================================================
   Feature page scenes and screenshots

   Ported from the old landing.css with every colour moved onto the tokens: the nearest
   token for an opaque colour, the nearest token mixed with transparent at the same alpha
   for a translucent one. Structure and motion are unchanged; the *Scene.razor components
   place things, these rules draw them.
   ========================================================= */
/* =========================================================
   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:color-mix(in srgb, var(--ink-muted) 70%, var(--surface-raised));
  --ns-steel-soft:color-mix(in srgb, var(--ink-faint) 70%, var(--surface-raised));
  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%, var(--ink) 9%, var(--ink) 91%, transparent 100%),
    radial-gradient(98% 100% at 50% 48%, var(--ink) 46%, transparent 92%),
    linear-gradient(180deg, transparent 0%, var(--ink) 9%, var(--ink) 90%, transparent 100%);
  -webkit-mask-repeat:no-repeat;
  -webkit-mask-composite:source-in;
  mask:
    linear-gradient(90deg, transparent 0%, var(--ink) 9%, var(--ink) 91%, transparent 100%),
    radial-gradient(98% 100% at 50% 48%, var(--ink) 46%, transparent 92%),
    linear-gradient(180deg, transparent 0%, var(--ink) 9%, var(--ink) 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 color-mix(in srgb, var(--ink-muted) 30%, transparent);
  border-radius:calc(var(--u) * 0.4);
  background:color-mix(in srgb, var(--surface-raised) 35%, transparent);
}
/* 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,
    color-mix(in srgb, var(--ink-muted) 50%, transparent) 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, color-mix(in srgb, var(--ink-muted) 13%, transparent) 0 1px, transparent 1px),
    linear-gradient(180deg, color-mix(in srgb, var(--ink-muted) 10%, transparent) 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:color-mix(in srgb, var(--ink-muted) 34%, transparent);
}
.ns-bay{
  flex:1;
  background:
    linear-gradient(180deg, var(--ns-steel-soft) 0 14%, color-mix(in srgb, var(--ink-faint) 34%, transparent) 14% 86%, var(--ns-steel) 86%);
  border-radius:calc(var(--u) * 0.2);
  box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--ink-muted) 16%, transparent);
}

/* ---------- 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(--radius-lg) * 2);
  aspect-ratio:1;
  margin-left:calc(var(--radius-lg) * -1);
  margin-top:calc(var(--radius-lg) * -1);
  border-radius:50%;
  background:radial-gradient(circle, color-mix(in srgb, var(--crit) 34%, transparent) 0%, color-mix(in srgb, var(--crit) 16%, transparent) 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 color-mix(in srgb, var(--crit) 50%, transparent);
  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(--signal), var(--signal));
  box-shadow:
    0 0 0 calc(var(--u) * 0.5) color-mix(in srgb, var(--surface-raised) 90%, transparent),
    0 calc(var(--u) * 0.7) calc(var(--u) * 2) color-mix(in srgb, var(--signal) 34%, transparent);
}
/* 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(--signal), var(--signal));
  box-shadow:0 0 0 calc(var(--u) * 0.3) color-mix(in srgb, var(--surface-raised) 85%, transparent);
}
.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 color-mix(in srgb, var(--signal) 55%, transparent);
  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) color-mix(in srgb, var(--surface-raised) 85%, transparent);
  opacity:0;
  animation:ns-read-in 18s linear infinite;
  animation-delay:var(--d);
}
.ns-read.is-good{background:var(--good-night);}
.ns-read.is-weak{background:var(--amber-hover);}
.ns-read.is-bad{background:color-mix(in srgb, var(--crit) 70%, var(--surface-raised));}
/* 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);
  box-shadow:0 0 0 calc(var(--u) * 0.4) color-mix(in srgb, var(--surface-raised) 90%, transparent);
  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 color-mix(in srgb, var(--night-line) 50%, transparent);
  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 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(--radius-lg);
  border:1px solid var(--line);
  box-shadow:var(--shadow-2);
  /* 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(--shadow-pop);}

/* 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(--shadow-pop);
}

/* ---------- 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:color-mix(in srgb, var(--surface) 90%, transparent);
  border-bottom:1px solid var(--line);
}
.shot-bar i{
  width:8px;height:8px;
  border-radius:50%;
  background:color-mix(in srgb, var(--night-raised) 14%, transparent);
}

.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(--radius-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:color-mix(in srgb, var(--surface-sunken) 70%, var(--surface-raised));}
}

/* =========================================================
   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 Signal Teal.
   ========================================================= */
.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:color-mix(in srgb, var(--ink) 70%, var(--surface-raised));
  --ids-shell-lit:color-mix(in srgb, var(--night-raised) 70%, var(--surface-raised));
  --ids-bezel:var(--ink);
  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%, var(--ink) 8%, var(--ink) 92%, transparent 100%),
    radial-gradient(100% 100% at 50% 48%, var(--ink) 48%, transparent 94%),
    linear-gradient(180deg, transparent 0%, var(--ink) 8%, var(--ink) 91%, transparent 100%);
  -webkit-mask-repeat:no-repeat;
  -webkit-mask-composite:source-in;
  mask:
    linear-gradient(90deg, transparent 0%, var(--ink) 8%, var(--ink) 92%, transparent 100%),
    radial-gradient(100% 100% at 50% 48%, var(--ink) 48%, transparent 94%),
    linear-gradient(180deg, transparent 0%, var(--ink) 8%, var(--ink) 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, color-mix(in srgb, var(--line-strong) 30%, var(--surface-raised)), color-mix(in srgb, var(--ink-faint) 70%, var(--surface-raised)));
  box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--ink-muted) 20%, transparent);
}
/* 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:color-mix(in srgb, var(--ink-muted) 34%, transparent);
}
.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:color-mix(in srgb, var(--ink-muted) 26%, transparent);
}
.ids-lines i:first-child{width:76%;}
.ids-lines i:last-child{width:48%;background:color-mix(in srgb, var(--ink-muted) 16%, transparent);}

/* 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(--signal), var(--signal));
}
.ids-holder.is-holder .ids-avatar::after{background:color-mix(in srgb, var(--surface-raised) 85%, transparent);}
.ids-holder.is-holder .ids-lines i{background:color-mix(in srgb, var(--signal) 45%, transparent);}
.ids-holder.is-holder .ids-lines i:last-child{background:color-mix(in srgb, var(--signal) 24%, transparent);}
@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%, var(--night-line) 78%, var(--night-raised) 100%);
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--surface-raised) 12%, transparent),
    0 calc(var(--u) * 2.4) calc(var(--u) * 5) color-mix(in srgb, var(--ink) 34%, transparent);
  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:color-mix(in srgb, var(--ink) 42%, transparent);
}

.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 color-mix(in srgb, var(--ink) 60%, transparent);
}

/* 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:var(--night);
  animation:ids-splash 12s var(--ease) infinite;
}
.ids-auth{
  background:linear-gradient(180deg, var(--night-raised) 0%, var(--ink) 100%);
  animation:ids-auth 12s var(--ease) infinite;
}
.ids-signed{
  background:linear-gradient(165deg, var(--good) 0%, var(--good) 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 WareVision mark on Night, as the app's own splash shows it. The design system's file,
   so the drawing is never retyped here. */
.ids-mark{
  width:46%;
  aspect-ratio:64 / 48;
  background:url('../_content/WareVision.Design/img/brand/warevision-mark-on-dark.svg') center / contain no-repeat;
}

/* 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 color-mix(in srgb, var(--surface-raised) 40%, transparent);
  background:transparent;
  animation:ids-dot 12s steps(1, end) infinite;
  animation-delay:var(--d);
}
@keyframes ids-dot{
  0%, 99%{background:color-mix(in srgb, var(--signal-night) 95%, transparent);}
  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:color-mix(in srgb, var(--surface-raised) 10%, transparent);
  box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--surface-raised) 6%, transparent);
}
/* 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:color-mix(in srgb, var(--signal-night) 75%, transparent);}
  4%, 100%{background:color-mix(in srgb, var(--surface-raised) 10%, transparent);}
}

/* 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 var(--surface-raised);
  border-bottom:calc(var(--u) * 0.75) solid var(--surface-raised);
  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, color-mix(in srgb, var(--surface-raised) 16%, transparent), color-mix(in srgb, var(--ink) 22%, transparent));
  box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--ink) 22%, transparent);
}

.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, color-mix(in srgb, var(--amber) 70%, var(--surface-raised)), var(--amber));
  box-shadow:inset 0 1px 0 color-mix(in srgb, var(--surface-raised) 50%, transparent);
}
.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:color-mix(in srgb, var(--ink) 30%, transparent);
}
.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%, var(--ink) 7%, var(--ink) 93%, transparent 100%),
    linear-gradient(180deg, transparent 0%, var(--ink) 7%, var(--ink) 93%, transparent 100%);
  -webkit-mask-repeat:no-repeat;
  -webkit-mask-composite:source-in;
  mask:
    linear-gradient(90deg, transparent 0%, var(--ink) 7%, var(--ink) 93%, transparent 100%),
    linear-gradient(180deg, transparent 0%, var(--ink) 7%, var(--ink) 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:color-mix(in srgb, var(--ink-muted) 28%, transparent);
}

.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(--signal) 0%, var(--signal) 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 color-mix(in srgb, var(--signal) 65%, transparent);
  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,
    color-mix(in srgb, var(--night-line) 50%, transparent) 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);
}

/* 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);
  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(--signal);
  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 color-mix(in srgb, var(--ink-muted) 16%, transparent);
}
/* The product's own three classes. Warm is the middle, so it is the neutral one. */
.ops-slot.is-fast{background:color-mix(in srgb, var(--good-night) 32%, transparent);}
.ops-slot.is-mid{background:color-mix(in srgb, var(--amber-hover) 28%, transparent);}
.ops-slot.is-slow{background:color-mix(in srgb, var(--ink-faint) 20%, transparent);}

/* 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, color-mix(in srgb, var(--amber-text) 30%, var(--surface-raised)), color-mix(in srgb, var(--ink-muted) 50%, var(--surface-raised)));
  box-shadow:0 calc(var(--u) * 0.6) calc(var(--u) * 1.4) color-mix(in srgb, var(--ink) 24%, transparent);
  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:var(--line-strong);
  --dt-steel-lit:var(--on-night-muted);
  --dt-steel-dark:color-mix(in srgb, var(--ink-muted) 33.3%, transparent);
  position:absolute;inset:0;
  isolation:isolate;
  -webkit-mask:
    linear-gradient(90deg, transparent 0%, var(--ink) 7%, var(--ink) 93%, transparent 100%),
    linear-gradient(180deg, transparent 0%, var(--ink) 5%, var(--ink) 95%, transparent 100%);
  -webkit-mask-repeat:no-repeat;
  -webkit-mask-composite:source-in;
  mask:
    linear-gradient(90deg, transparent 0%, var(--ink) 7%, var(--ink) 93%, transparent 100%),
    linear-gradient(180deg, transparent 0%, var(--ink) 5%, var(--ink) 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, color-mix(in srgb, var(--ink-muted) 10%, transparent) 0 1px, transparent 1px),
    linear-gradient(180deg, color-mix(in srgb, var(--ink-muted) 7%, transparent) 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%, var(--ink) 52%, transparent 100%);
  mask:radial-gradient(78% 82% at 46% 52%, var(--ink) 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, color-mix(in srgb, var(--surface-raised) 50%, transparent) 0, color-mix(in srgb, var(--surface-raised) 12%, transparent) 16%, transparent 30%),
    linear-gradient(90deg, transparent 62%, color-mix(in srgb, var(--night-line) 18%, transparent) 100%);
  box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--night-line) 22%, transparent);
}
/* 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:color-mix(in srgb, var(--good) 70%, var(--surface-raised));}
.dt-pallet.is-warm{background-color:color-mix(in srgb, var(--good-night) 70%, var(--surface-raised));}
.dt-pallet.is-cold{background-color:color-mix(in srgb, var(--line-strong) 30%, var(--surface-raised));}
/* 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(--crit);
  box-shadow:0 0 calc(var(--u) * 1.4) color-mix(in srgb, var(--crit-night) 85%, transparent);
  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 color-mix(in srgb, var(--crit-night) 80%, transparent);
  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(--signal);
  border-radius:calc(var(--u) * 0.3);
  background:color-mix(in srgb, var(--signal-night) 10%, transparent);
  box-shadow:0 0 calc(var(--u) * 2.2) color-mix(in srgb, var(--signal-night) 45%, transparent);
  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, color-mix(in srgb, var(--ink-faint) 16%, transparent), color-mix(in srgb, var(--ink-faint) 4%, transparent));
}
.dt-aisle::after{
  content:"";
  position:absolute;
  top:0;bottom:0;
  width:22%;
  background:linear-gradient(90deg, transparent, color-mix(in srgb, var(--signal-night) 50%, transparent), 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(--radius-md);
  background:var(--surface-raised);
  border:1px solid var(--line);
  box-shadow:0 calc(var(--u) * 2) calc(var(--u) * 5) color-mix(in srgb, var(--ink) 16%, transparent);
  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(--signal);
}
.dt-card-row{
  height:calc(var(--u) * 1.15);
  width:var(--w);
  border-radius:calc(var(--u) * 0.6);
  background:color-mix(in srgb, var(--ink-muted) 22%, transparent);
  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:color-mix(in srgb, var(--ink-muted) 12%, transparent);
}
.dt-card-face i.is-filled{background:color-mix(in srgb, var(--good-night) 70%, var(--surface-raised));}

/* 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, color-mix(in srgb, var(--signal) 75%, transparent), color-mix(in srgb, var(--signal) 18%, transparent));
}
.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(--signal);
}

@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 (max-width:1000px){
  .dt-scene{max-width:400px;}
}

/* =========================================================
   Communication scene

   The Communication page hero: the manager's console on the left, an operator's handheld
   on the right, and three exchanges between them. Structure is
   Components/Shared/CommunicationScene.razor.

   One 12s cycle, every animation-duration 12s, so nothing drifts. The windows:
     0.6s           a text message leaves the handheld          (5%)
     2.0s           it lands on the console                     (17%)
     4.0s - 6.4s    push to talk: side key lit, rings, talk bar (33% - 53%)
     6.4s           the voice message shows on the handheld     (53%)
     7.2s           and on the console, its waveform playing    (60%)
     8.2s           the console sends an urgent message         (68%)
     8.8s           it takes the handheld's screen              (73%)
     10.2s          acknowledged on the handheld                (85%)
     10.6s          the tick reaches the console                (88%)
     11.6s          everything clears for the next cycle        (97%)

   Same rules as the other scenes: no card around it, --u as the scene's own unit,
   physical things muted, the digital layer in Signal Teal, urgent in the critical red.
   ========================================================= */
.cm-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;
}
.cms-stage{
  --u:5.6px;
  --cms-shell:color-mix(in srgb, var(--ink) 70%, var(--surface-raised));
  --cms-shell-lit:color-mix(in srgb, var(--night-raised) 70%, var(--surface-raised));
  position:absolute;inset:0;
  isolation:isolate;
}
@supports (width: 1cqw){
  .cms-stage{--u:1cqw;}
}

/* ---------- The console ---------- */
.cms-console{
  position:absolute;
  left:3%;top:22%;
  width:58%;height:54%;
  border-radius:calc(var(--u) * 2.4);
  background:color-mix(in srgb, var(--surface-raised) 80%, transparent);
  border:1px solid var(--line);
  box-shadow:0 calc(var(--u) * 2) calc(var(--u) * 6) color-mix(in srgb, var(--ink) 16%, transparent);
  overflow:hidden;
}
.cms-bar{
  position:absolute;left:0;right:0;top:0;
  height:calc(var(--u) * 4);
  display:flex;align-items:center;gap:calc(var(--u) * 0.9);
  padding:0 calc(var(--u) * 1.8);
  background:color-mix(in srgb, var(--surface) 95%, transparent);
  border-bottom:1px solid var(--line);
}
.cms-bar i{width:calc(var(--u) * 1.1);height:calc(var(--u) * 1.1);border-radius:50%;background:color-mix(in srgb, var(--night-raised) 14%, transparent);}
.cms-list{
  position:absolute;left:0;top:calc(var(--u) * 4);bottom:0;
  width:30%;
  display:flex;flex-direction:column;gap:calc(var(--u) * 1.2);
  padding:calc(var(--u) * 2) calc(var(--u) * 1.4);
  border-right:1px solid var(--line);
}
.cms-row{display:flex;align-items:center;gap:calc(var(--u) * 0.9);padding:calc(var(--u) * 0.6);border-radius:calc(var(--u) * 0.8);}
.cms-row.is-open{background:color-mix(in srgb, var(--signal-night) 10%, transparent);}
.cms-dot{width:calc(var(--u) * 1.2);height:calc(var(--u) * 1.2);border-radius:50%;background:color-mix(in srgb, var(--ink-muted) 30%, transparent);flex:none;}
.cms-row.is-open .cms-dot{background:var(--signal);}
.cms-row i{height:calc(var(--u) * 0.9);flex:1;border-radius:calc(var(--u) * 0.5);background:color-mix(in srgb, var(--ink-muted) 22%, transparent);}
.cms-thread{position:absolute;left:30%;right:0;top:calc(var(--u) * 4);bottom:0;}

/* ---------- Bubbles, shared by both ends ---------- */
.cms-bubble{
  position:absolute;
  display:flex;flex-direction:column;justify-content:center;gap:calc(var(--u) * 0.7);
  padding:calc(var(--u) * 1.2) calc(var(--u) * 1.6);
  border-radius:calc(var(--u) * 1.6);
  opacity:0;
}
.cms-bubble > i{display:block;height:calc(var(--u) * 0.8);border-radius:calc(var(--u) * 0.4);}
.cms-in{left:7%;background:color-mix(in srgb, var(--surface-sunken) 70%, var(--surface-raised));border:1px solid var(--line);border-bottom-left-radius:calc(var(--u) * 0.4);}
.cms-in > i{background:color-mix(in srgb, var(--ink-muted) 35%, transparent);}
.cms-out{right:7%;background:linear-gradient(160deg, var(--signal), var(--signal));border-bottom-right-radius:calc(var(--u) * 0.4);}
.cms-out > i{background:color-mix(in srgb, var(--surface-raised) 85%, transparent);}
.cms-bubble > i:first-child{width:calc(var(--u) * 11);}
.cms-bubble > i:nth-child(2){width:calc(var(--u) * 7);}

/* A voice message: a row of bars, tallest in the middle. They move only while it plays. */
.cms-c-voice,.cms-d-voice{flex-direction:row;align-items:center;gap:calc(var(--u) * 0.5);height:calc(var(--u) * 4.4);}
.cms-bubble b{
  display:block;
  width:calc(var(--u) * 0.55);
  height:calc(var(--u) * (0.9 + var(--i) * 0.37 - var(--i) * var(--i) * 0.041));
  border-radius:calc(var(--u) * 0.3);
  background:currentColor;
}
.cms-c-voice{color:color-mix(in srgb, var(--ink-muted) 60%, transparent);}
.cms-d-voice{color:color-mix(in srgb, var(--surface-raised) 90%, transparent);}
.cms-c-voice b{animation:cms-wave 12s ease-in-out infinite;animation-delay:calc(var(--i) * 70ms);}
@keyframes cms-wave{
  0%, 61%{scale:1 1;}
  63%{scale:1 1.6;}
  65%{scale:1 0.7;}
  67%{scale:1 1.4;}
  69%, 100%{scale:1 1;}
}

/* Console thread positions and timings. */
.cms-c-text{top:12%;animation:cms-show-c-text 12s var(--ease) infinite;}
.cms-c-voice{top:36%;animation:cms-show-c-voice 12s var(--ease) infinite;}
.cms-c-urgent{
  top:62%;
  flex-direction:row;align-items:center;gap:calc(var(--u) * 1.1);
  background:linear-gradient(160deg, var(--crit), var(--crit));
  animation:cms-show-c-urgent 12s var(--ease) infinite;
}
.cms-bubble.cms-c-urgent > i{width:calc(var(--u) * 9);}
@keyframes cms-show-c-text{0%, 16%{opacity:0;transform:translateY(calc(var(--u) * 1.5));} 19%, 96%{opacity:1;transform:none;} 99%, 100%{opacity:0;}}
@keyframes cms-show-c-voice{0%, 59%{opacity:0;transform:translateY(calc(var(--u) * 1.5));} 62%, 96%{opacity:1;transform:none;} 99%, 100%{opacity:0;}}
@keyframes cms-show-c-urgent{0%, 67%{opacity:0;transform:translateY(calc(var(--u) * 1.5));} 70%, 96%{opacity:1;transform:none;} 99%, 100%{opacity:0;}}

/* The warning mark: a triangle drawn with a clip, an exclamation inside it. */
.cms-warn{
  position:relative;flex:none;
  width:calc(var(--u) * 2.2);height:calc(var(--u) * 2);
  background:var(--surface-raised);
  clip-path:polygon(50% 0, 100% 100%, 0 100%);
}
.cms-warn::after{
  content:"";position:absolute;left:50%;top:38%;
  width:calc(var(--u) * 0.35);height:calc(var(--u) * 0.8);
  translate:-50% 0;
  background:var(--crit);
  border-radius:calc(var(--u) * 0.2);
}
/* Acknowledged: a small tick that lands on the console's urgent bubble. */
.cms-ack{
  flex:none;
  width:calc(var(--u) * 1.8);height:calc(var(--u) * 0.9);
  border-left:calc(var(--u) * 0.4) solid var(--surface-raised);
  border-bottom:calc(var(--u) * 0.4) solid var(--surface-raised);
  rotate:-45deg;
  translate:0 calc(var(--u) * -0.3);
  opacity:0;
  animation:cms-ack 12s var(--ease) infinite;
}
@keyframes cms-ack{0%, 87%{opacity:0;scale:0.4;} 90%, 96%{opacity:1;scale:1;} 99%, 100%{opacity:0;}}

/* ---------- The handheld ----------
   The same generic rugged body as the Identity scene, slightly smaller so the console
   fits beside it. */
.cms-device{
  position:absolute;
  left:77%;top:52%;
  width:calc(var(--u) * 27);
  aspect-ratio:30 / 56;
  translate:-50% -50%;
  border-radius:calc(var(--u) * 3.1);
  background:linear-gradient(100deg, var(--cms-shell-lit) 0%, var(--cms-shell) 22%, var(--night-line) 78%, var(--night-raised) 100%);
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--surface-raised) 12%, transparent),
    0 calc(var(--u) * 2.4) calc(var(--u) * 5) color-mix(in srgb, var(--ink) 34%, transparent);
  animation:cms-hold 12s ease-in-out infinite;
}
@keyframes cms-hold{
  0%, 100%{rotate:1.4deg;}
  50%{rotate:-1.2deg;}
}
.cms-speaker{
  position:absolute;left:50%;top:calc(var(--u) * 1.9);
  width:26%;height:calc(var(--u) * 0.6);
  translate:-50% 0;
  border-radius:calc(var(--u) * 0.4);
  background:color-mix(in srgb, var(--ink) 42%, transparent);
}
.cms-screen{
  position:absolute;
  left:8%;right:8%;
  top:calc(var(--u) * 4);
  height:62%;
  border-radius:calc(var(--u) * 0.6);
  background:linear-gradient(180deg, color-mix(in srgb, var(--signal-soft) 30%, var(--surface-raised)) 0%, var(--surface-sunken) 100%);
  overflow:hidden;
  box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--ink) 60%, transparent);
}
.cms-screen .cms-bubble{padding:calc(var(--u) * 1) calc(var(--u) * 1.3);}
.cms-screen .cms-bubble > i:first-child{width:calc(var(--u) * 9);}
.cms-screen .cms-bubble > i:nth-child(2){width:calc(var(--u) * 5.5);}
.cms-d-text{top:10%;animation:cms-show-d-text 12s var(--ease) infinite;}
.cms-d-voice{top:34%;height:calc(var(--u) * 3.8);animation:cms-show-d-voice 12s var(--ease) infinite;}
@keyframes cms-show-d-text{0%, 4%{opacity:0;transform:translateY(calc(var(--u) * 1.5));} 7%, 96%{opacity:1;transform:none;} 99%, 100%{opacity:0;}}
@keyframes cms-show-d-voice{0%, 52%{opacity:0;transform:translateY(calc(var(--u) * 1.5));} 55%, 96%{opacity:1;transform:none;} 99%, 100%{opacity:0;}}

/* Talking: a red bar across the bottom of the screen while the side key is held. */
.cms-talk{
  position:absolute;left:7%;right:7%;bottom:6%;
  height:calc(var(--u) * 3.4);
  border-radius:calc(var(--u) * 1.2);
  background:linear-gradient(160deg, var(--crit), var(--crit));
  opacity:0;
  animation:cms-talk 12s var(--ease) infinite;
}
@keyframes cms-talk{0%, 32%{opacity:0;} 34%, 52%{opacity:1;} 54%, 100%{opacity:0;}}

/* The urgent message, full screen, with its acknowledge button. */
.cms-urgent{
  position:absolute;inset:0;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:calc(var(--u) * 1.1);
  background:linear-gradient(165deg, var(--crit) 0%, var(--crit) 100%);
  opacity:0;
  animation:cms-urgent 12s var(--ease) infinite;
}
.cms-urgent .cms-warn{width:calc(var(--u) * 3.6);height:calc(var(--u) * 3.2);margin-bottom:calc(var(--u) * 0.6);}
.cms-urgent > i{display:block;height:calc(var(--u) * 0.8);border-radius:calc(var(--u) * 0.4);background:color-mix(in srgb, var(--surface-raised) 85%, transparent);}
.cms-urgent > i:nth-of-type(1){width:60%;}
.cms-urgent > i:nth-of-type(2){width:40%;}
.cms-ack-btn{
  display:flex;align-items:center;justify-content:center;
  width:70%;height:calc(var(--u) * 3.6);
  margin-top:calc(var(--u) * 1.2);
  border-radius:calc(var(--u) * 1.2);
  background:var(--surface-raised);
  animation:cms-ack-btn 12s steps(1, end) infinite;
}
.cms-tick{
  width:calc(var(--u) * 2);height:calc(var(--u) * 1);
  border-left:calc(var(--u) * 0.45) solid var(--crit);
  border-bottom:calc(var(--u) * 0.45) solid var(--crit);
  rotate:-45deg;
  translate:0 calc(var(--u) * -0.3);
}
@keyframes cms-urgent{0%, 72%{opacity:0;transform:scale(0.96);} 75%, 88%{opacity:1;transform:none;} 92%, 100%{opacity:0;transform:none;}}
/* The press: the button goes green for a moment at 10.2s. */
@keyframes cms-ack-btn{0%{background:var(--surface-raised);} 85%{background:var(--good-soft);} 89%, 100%{background:var(--surface-raised);}}

.cms-pad{
  position:absolute;left:14%;right:14%;
  top:calc(var(--u) * 37.2);
  display:grid;grid-template-columns:repeat(4, 1fr);
  gap:calc(var(--u) * 0.7);
}
.cms-pad i{
  height:calc(var(--u) * 2);
  border-radius:calc(var(--u) * 0.4);
  background:linear-gradient(180deg, color-mix(in srgb, var(--surface-raised) 16%, transparent), color-mix(in srgb, var(--ink) 22%, transparent));
  box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--ink) 22%, transparent);
}
.cms-trigger{
  position:absolute;left:50%;bottom:calc(var(--u) * 3);
  width:34%;height:calc(var(--u) * 2.3);
  translate:-50% 0;
  border-radius:calc(var(--u) * 1.2);
  background:linear-gradient(180deg, color-mix(in srgb, var(--amber) 70%, var(--surface-raised)), var(--amber));
  box-shadow:inset 0 1px 0 color-mix(in srgb, var(--surface-raised) 50%, transparent);
}
/* The push to talk side key, on the left edge where an operator's thumb rests. */
.cms-ptt{
  position:absolute;left:calc(var(--u) * -0.7);top:30%;
  width:calc(var(--u) * 0.9);height:calc(var(--u) * 6);
  border-radius:calc(var(--u) * 0.5);
  background:var(--night-line);
  animation:cms-ptt 12s steps(1, end) infinite;
}
@keyframes cms-ptt{0%{background:var(--night-line);} 33%{background:var(--crit);} 53%, 100%{background:var(--night-line);}}

/* Rings leaving the device while it transmits. */
.cms-rings{position:absolute;left:50%;top:40%;width:0;height:0;z-index:-1;}
.cms-rings i{
  position:absolute;left:0;top:0;
  width:calc(var(--u) * 44);height:calc(var(--u) * 44);
  translate:-50% -50%;
  border-radius:50%;
  border:calc(var(--u) * 0.3) solid color-mix(in srgb, var(--crit-night) 45%, transparent);
  opacity:0;
  scale:0.3;
  animation:cms-ring 12s linear infinite;
}
.cms-rings i:nth-child(2){animation-delay:.8s;}
.cms-rings i:nth-child(3){animation-delay:1.6s;}
@keyframes cms-ring{
  0%, 33%{opacity:0;scale:0.3;}
  34%{opacity:0.9;scale:0.35;}
  45%{opacity:0;scale:1.1;}
  100%{opacity:0;scale:1.1;}
}

@media (max-width:1000px){
  .cm-scene{max-width:440px;}
}
@media (prefers-reduced-motion:reduce){
  /* Hold the finished exchange still: both messages on both ends, the urgent one
     acknowledged, nothing covering the handheld's screen. */
  .cms-c-text,.cms-c-voice,.cms-d-text,.cms-d-voice,.cms-c-urgent,.cms-ack{animation:none;opacity:1;}
  .cms-device,.cms-urgent,.cms-talk,.cms-rings i,.cms-ptt,.cms-c-voice b,.cms-ack-btn{animation:none;}
}

/* ---------- Responsive ---------- */
@media (max-width:1200px){
  nav.main{gap:0;}
  nav.main a{padding:8px 10px;}
  .nav-wrap{gap:var(--space-5);}
  .nav-cta{gap:8px;}
  .pricing-grid{grid-template-columns:repeat(2,minmax(0,1fr));}
  .modules{grid-template-columns:repeat(2,minmax(0,1fr));}
  .fm-float-kpi{left:-16px;}
  .fm-float-toast{right:-8px;}
}

@media (max-width:1000px){
  .hero-grid,.hero-grid.reverse,.hero-home .hero-grid{grid-template-columns:1fr;}
  .hero-visual img{max-height:42svh;}
  .hero-home .hero-visual{margin-top:24px;}
  .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));}
  .steps{grid-template-columns:1fr;}
  .band-grid{grid-template-columns:1fr;}
  .fm-float-kpi{left:-8px;bottom:-28px;}
  .fm-float-toast{right:-4px;top:-32px;}
}

@media (max-width:900px){
  /* The nav becomes a 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:10px;
    background:var(--surface-raised);
    border:1px solid var(--line);
    border-radius:var(--radius-md);
    box-shadow:var(--shadow-pop);
    max-height:calc(100vh - var(--header-h) - 24px);
    overflow-y:auto;
    opacity:0;
    visibility:hidden;
    transform:translateY(-6px);
    transition:opacity .2s ease, transform .2s var(--ease), visibility .2s;
  }
  nav.main.open{opacity:1;visibility:visible;transform:none;}
  nav.main a{padding:12px 14px;font-size:15px;}
  nav.main > a.active::after,
  nav.main .nav-menu-trigger.active::after{display:none;}
  nav.main > a.active{background:var(--surface);}

  /* In the sheet the Features menu is simply open: the trigger becomes a small heading. */
  .nav-menu-panel{
    position:static;width:auto;padding:0 0 6px;
    transform:none;
    opacity:1;visibility:visible;
  }
  .nav-menu-panel::before{display:none;}
  nav.main .nav-menu-trigger,
  nav.main .nav-menu-trigger.active{
    padding:6px 14px 4px;
    font:var(--type-label);letter-spacing:var(--tracking-label);text-transform:uppercase;
    color:var(--ink-muted);
    background:none;
  }
  .nav-caret{display:none;}
  nav.main .nav-menu-item{margin:0;padding:10px 14px;}
  nav.main .nav-menu-item:first-child{margin-top:0;}
  .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 logo, language switch, sign in, demo button and the menu button
     on one row at phone width. The demo button goes: it is repeated in the hero and in the
     closing banner, whereas sign in is the only route into the portal. */
  .nav-cta .btn-primary{display:none;}

  :root{--pad-x:18px;}
  .grid-2,.modules{grid-template-columns:1fr;}
  .pricing-grid{grid-template-columns:1fr;}
  /* One plan per row: no neighbour to line the names up with, so no room kept for a badge. */
  .price-card{padding-top:var(--space-6);}
  .price-card .badge,.price-card .chip{position:static;align-self:flex-start;margin-bottom:14px;}
  .cta-banner{flex-direction:column;align-items:flex-start;}
  .cta-banner .cta-actions,.cta-banner .btn{width:100%;}
  .scenario-body,.scenario-head{padding-left:18px;padding-right:18px;}
  .scenario-result{margin-left:18px;margin-right:18px;}
  .hero-actions .btn{width:100%;}
  .hero-actions{gap:10px;}
  .proof{gap:var(--space-5);}
  .proof dt{font-size:22px;line-height:26px;}

  /* The floats would cover most of a phone-sized map: the toast goes, the KPI card sits
     under the map instead of over its corner. */
  .fm-float-toast{display:none;}
  .fm-float-kpi{position:relative;left:auto;bottom:auto;width:auto;margin:-24px 16px 0;}
  .fm-callout{left:auto;right:10px;top:auto;bottom:10px;width:min(200px, 56%);font-size:11px;line-height:15px;}
  .fm-callout dl{gap:1px 10px;margin-top:6px;}
  .fm-head span{display:none;}
}

@media (max-width:520px){
  .nav-wrap{padding:10px 16px;gap:8px;}
  .brand-logo{height:18px;}
  .nav-cta{gap:6px;}
  .lang-switch a{padding:4px 7px;font-size:11px;}
  .nav-cta .btn-sm{padding:0 10px;font-size:12.5px;}
  .footer-grid{grid-template-columns:1fr;}
  .footer-bottom{justify-content:flex-start;}
}

@media (max-width:440px){
  header.site .brand-logo{display:none;}
  header.site .brand-mark-only{display:block;}
}

/* ---------- Accessibility ---------- */
@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;}
  .fm-ring{display:none;}
}

@media print{
  header.site,footer.site,.hero::before{display:none;}
  body{background:var(--surface-raised);}
}
