/* =========================================================
   LATA SCIENTIFIC — premium layer
   Every page already links this file; it was missing, which left the
   cinematic loader and the hero visual completely unstyled (23 classes
   with no rules at all) and produced 200+ 404s across the site.

   This restores the intended presentation using the existing tokens in
   styles.css. Nothing here overrides an existing rule — it only styles
   markup that previously had none.

   SAFETY: the loader dismisses itself with CSS alone. premium.js only
   makes it faster. If the script fails to load again, the page still
   becomes usable — the overlay can never trap the site.
   ========================================================= */

/* =========================================================
   CINEMATIC LOADER (index.html only)
   ========================================================= */
.loader {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: grid;
  place-items: center;
  background:
    radial-gradient(70% 100% at 82% 0%, rgba(37, 99, 235, 0.28), transparent 55%),
    radial-gradient(50% 90% at 8% 100%, rgba(14, 165, 183, 0.20), transparent 60%),
    var(--brand);
  color: var(--on-brand);
  /* Self-dismissing: runs without any JavaScript at all. */
  animation: loaderAuto 0.55s var(--ease) 1.5s forwards;
  will-change: opacity;
}
/* JS-driven early exit — whichever fires first wins. */
.loader.is-done {
  animation: none;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.45s var(--ease), visibility 0.45s;
}
@keyframes loaderAuto {
  to { opacity: 0; visibility: hidden; pointer-events: none; }
}
/* Lock scroll only while the overlay is genuinely up. */
html.is-loading, html.is-loading body { overflow: hidden; }

.loader__grid {
  position: absolute; inset: 0; pointer-events: none;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, transparent 1px);
  background-size: 46px 46px;
  mask-image: radial-gradient(120% 90% at 50% 45%, #000, transparent 72%);
  -webkit-mask-image: radial-gradient(120% 90% at 50% 45%, #000, transparent 72%);
}
.loader__particles {
  position: absolute; inset: 0; pointer-events: none; opacity: 0.6;
  background-image:
    radial-gradient(1.5px 1.5px at 20% 30%, rgba(97, 233, 255, 0.9), transparent 60%),
    radial-gradient(1.5px 1.5px at 72% 22%, rgba(255, 255, 255, 0.7), transparent 60%),
    radial-gradient(2px 2px at 45% 72%, rgba(30, 107, 255, 0.8), transparent 60%),
    radial-gradient(1.5px 1.5px at 85% 66%, rgba(97, 233, 255, 0.7), transparent 60%),
    radial-gradient(1.5px 1.5px at 12% 80%, rgba(255, 255, 255, 0.5), transparent 60%);
  animation: loaderDrift 6s ease-in-out infinite alternate;
}
@keyframes loaderDrift {
  to { transform: translate3d(0, -14px, 0); opacity: 0.35; }
}

.loader__stage {
  position: relative;
  display: grid;
  justify-items: center;
  gap: 26px;
  padding-inline: 24px;
  width: min(460px, calc(100vw - 48px));
}
.loader__pipe { width: 100%; height: auto; overflow: visible; }

/* Glass tube: each segment draws itself in, in sequence. */
.loader__pipe .tube {
  fill: none;
  stroke: rgba(255, 255, 255, 0.30);
  stroke-width: 9;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: var(--len) var(--len);
  stroke-dashoffset: var(--len);
  animation: tubeDraw 0.55s var(--ease-out) forwards;
}
.loader__pipe .tube--1 { animation-delay: 0.05s; }
.loader__pipe .tube--2 { animation-delay: 0.35s; }
.loader__pipe .tube--3 { animation-delay: 0.62s; }
@keyframes tubeDraw { to { stroke-dashoffset: 0; } }

/* Product flows through the drawn tube. */
.loader__pipe .flow {
  fill: none;
  stroke: url(#loaderFlow);
  stroke-width: 5.5;
  stroke-linecap: round;
  stroke-dasharray: 60 620;
  stroke-dashoffset: 680;
  animation: flowRun 1.15s linear 0.75s infinite;
}
@keyframes flowRun { to { stroke-dashoffset: 0; } }

.loader__pipe .joint {
  fill: var(--brand);
  stroke: var(--cyan);
  stroke-width: 3;
  opacity: 0;
  animation: jointPop 0.4s var(--ease-out) forwards;
}
.loader__pipe .joint--1 { animation-delay: 0.45s; }
.loader__pipe .joint--2 { animation-delay: 0.68s; }
.loader__pipe .joint--3 { animation-delay: 0.9s; }
@keyframes jointPop { from { opacity: 0; transform: scale(0.4); } to { opacity: 1; transform: scale(1); } }
.loader__pipe .joint { transform-box: fill-box; transform-origin: center; }

.loader__readout {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  font-family: var(--f-mono);
  font-size: 13px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--on-brand-dim);
}
.loader__gauge {
  flex: 1;
  height: 3px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.14);
  overflow: hidden;
}
.loader__gauge > span {
  display: block;
  height: 100%;
  width: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--blue), var(--cyan));
  transform: scaleX(0);
  transform-origin: left;
  animation: gaugeFillAuto 1.45s var(--ease-out) forwards;
}
@keyframes gaugeFillAuto { to { transform: scaleX(1); } }
.loader__pct { color: var(--teal); font-variant-numeric: tabular-nums; }

.loader__logo {
  position: absolute;
  left: 50%;
  bottom: clamp(28px, 7vh, 64px);
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  gap: 10px;
  opacity: 0;
  animation: logoIn 0.6s var(--ease-out) 0.3s forwards;
}
@keyframes logoIn { to { opacity: 1; } }
.loader__logo .mk {
  display: grid; place-content: center;
  width: 36px; height: 36px; border-radius: 10px;
  color: #fff; background: var(--grad);
}
.loader__logo .wm {
  font-family: var(--f-display);
  font-weight: 700;
  font-size: 19px;
  color: #fff;
  letter-spacing: -0.01em;
}
.loader__logo .wm span { color: var(--teal); }

/* Reduced motion, or no-JS: never show a blocking overlay. */
@media (prefers-reduced-motion: reduce) {
  .loader { display: none; }
  html.is-loading, html.is-loading body { overflow: auto; }
}
html.no-js .loader { display: none; }

/* =========================================================
   HERO — ambient depth layer behind the copy
   `.hero__webgl` had no rules at all, so it rendered as a dead box.
   ========================================================= */
.hero__webgl {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  background:
    radial-gradient(38% 55% at 78% 30%, rgba(97, 233, 255, 0.16), transparent 70%),
    radial-gradient(42% 60% at 12% 78%, rgba(30, 107, 255, 0.13), transparent 72%);
  will-change: transform;
  animation: heroAmbient 18s ease-in-out infinite alternate;
}
@keyframes heroAmbient {
  to { transform: translate3d(0, -18px, 0) scale(1.04); }
}
@media (prefers-reduced-motion: reduce) {
  .hero__webgl { animation: none; }
}

/* The hero copy column sits above the ambient layer and the canvas field. */
.hero__copy { position: relative; z-index: 2; }
.hero__media { position: relative; z-index: 2; }

/* Supplied product renders are centred subjects on a light ground —
   `cover` (the styles.css default for this box) crops the product out of
   frame. Contain them and give the tile a soft studio backdrop instead. */
.hero__figure { background: linear-gradient(160deg, #F4F8FD, #E4EDF7); }
.hero__figure img {
  object-fit: contain;
  padding: clamp(14px, 3.2vw, 34px);
}

/* =========================================================
   Category card artwork (products.html + category pages)
   ========================================================= */
.pcard__media img.pcard__shot,
.featcat__media img.featcat__shot {
  object-fit: contain;
  padding: 10%;
  background: linear-gradient(160deg, #F6F9FD, #E9F0F8);
}

/* =========================================================
   PERFORMANCE
   Long catalogue pages carry 40+ sections and several wide tables.
   Skipping layout/paint for off-screen blocks removes most of the
   scroll cost without changing a single pixel of the design.
   `contain-intrinsic-size` reserves height so the scrollbar stays stable.
   ========================================================= */
@supports (content-visibility: auto) {
  main > section:not(:first-of-type) {
    content-visibility: auto;
    contain-intrinsic-size: auto 620px;
  }
  .site-footer { content-visibility: auto; contain-intrinsic-size: auto 480px; }
}

/* Promote only what actually moves, and only while it moves. */
.hero__slide.is-active { will-change: opacity, transform; }
.hero__slide:not(.is-active) { will-change: auto; }
.showcase__track, .ttrack { will-change: transform; }

/* Decorative full-screen canvases must never intercept input or force
   the compositor to re-rasterise the page beneath them. */
.site-fx, .hero__fx, .pagehero__fx { contain: strict; }
.site-aura { contain: layout paint size; }

/* =========================================================
   HEADER FIT — measured fixes, no visual redesign
   ========================================================= */

/* The desktop header (logo 259 + nav 579 + CTA 169 + gaps) needs ~1105px,
   but the drawer only takes over at 900px. Between 901px and 1104px the
   header overflowed and pushed "Request a quote" clean off the right edge
   — invisible and unclickable on iPad Pro portrait (1024), Nest Hub and
   small laptops. Hide the CTA (as ≤900px already does) and drop the logo
   strapline so the nav has real breathing room. Every other route to the
   CTA — mega-menu promo, page CTAs, footer — is untouched. */
@media (min-width: 901px) and (max-width: 1119px) {
  .header__cta { display: none; }
  .logo__tag { display: none; }
}

/* At 320px the logo (259px) plus the 44px hamburger plus gutters came to
   341px — the menu button was ~21px outside the viewport. */
@media (max-width: 379px) {
  .logo__tag { display: none; }
  .logo__name { font-size: 19px; }
  .logo__mark { width: 38px; height: 38px; }
}

/* =========================================================
   MOBILE DRAWER
   ========================================================= */
@media (max-width: 900px) {
  /* THE mobile-menu bug.
     .site-header carries backdrop-filter for its frosted look, and any
     element with a backdrop-filter becomes the containing block for its
     position:fixed descendants. The drawer lives inside the header, so its
     `inset: 0 0 0 auto` resolved against the HEADER box rather than the
     viewport — computed height 122px instead of 800px. Only "Home" and part
     of "About" were ever on screen; every other item was clipped away, which
     is why the panel looked empty behind the scrim.

     Moving the frosted layer to a pseudo-element keeps the appearance
     pixel-identical while taking the containing block off the header, so the
     drawer measures against the viewport again and fills the screen. */
  .site-header {
    background: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .site-header::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background: rgba(255, 255, 255, 0.86);
    backdrop-filter: blur(14px) saturate(150%);
    -webkit-backdrop-filter: blur(14px) saturate(150%);
  }

  /* Nothing in the drawer may be compressed. .btn--primary carries
     overflow:hidden for its sheen sweep, and on a flex item that changes
     the automatic minimum size from min-content to 0 — so once the drawer
     content grew past the viewport, flex-shrink crushed the "Request a
     quote" button from ~59px to 28px. Pinning flex on every child fixes
     it and lets the drawer scroll instead of squashing. */
  .mainnav > *, .mainnav > .has-mega { flex: 0 0 auto; }

  .mainnav {
    /* No z-index here on purpose. .site-header is position:sticky with
       z-index:100, which makes it a stacking context — so the drawer inside
       it is painted at the header's level whatever value it is given, and
       styles.css's z-index:130 already orders it correctly against its own
       siblings. Raising it here only looked like it worked. */
    overscroll-behavior: contain;          /* no scroll-chaining to the page */
    -webkit-overflow-scrolling: touch;
    padding-bottom: max(30px, env(safe-area-inset-bottom, 0px));
    /* A closed drawer sits off-canvas but was still in the tab order, so
       keyboard users tabbed into an invisible menu. visibility removes it
       from the tree; transitioning it keeps the slide-out animation intact. */
    visibility: hidden;
    transition: transform .4s var(--ease), visibility .4s var(--ease);
  }
  .mainnav.is-open { visibility: visible; }

  /* styles.css animates the submenu open to a max-height of 1400px. With ten
     categories on a 320px screen the list wraps to ~1400px and was sitting
     right on that ceiling, so the last entries risked being cut off. Raise
     the ceiling well clear of the content — max-height does not stretch the
     box, so the panel is still only as tall as it needs to be — and keep an
     overflow fallback in case the range grows again. */
  .has-mega.is-open .mega { max-height: 3000px; overflow-y: auto; overscroll-behavior: contain; }

  /* Scrim behind the drawer — dims and blurs the page, and gives a
     tap-anywhere-to-close target. Injected by main.js.

     z-index must sit BELOW the header's 100. The drawer lives inside the
     header's stacking context, so anything above 100 at body level covers
     the menu — which is exactly what happened: the blur painted over the
     whole screen and the menu items disappeared behind it. 96 clears the
     page content and both floating buttons (.to-top 90, .wa-float 95)
     while staying under the header, so the drawer and the close button
     remain on top and tappable. */
  .nav-scrim {
    position: fixed; inset: 0; z-index: 96;
    background: rgba(10, 14, 23, 0.45);
    backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
    opacity: 0; visibility: hidden;
    transition: opacity .35s var(--ease), visibility .35s;
  }
  .nav-scrim.is-shown { opacity: 1; visibility: visible; }
}
/* The scrim is a mobile-only device; never let it linger on desktop. */
@media (min-width: 901px) { .nav-scrim { display: none; } }

/* =========================================================
   TOUCH TARGETS — coarse pointers only, so desktop is unchanged
   ========================================================= */
@media (pointer: coarse) {
  .topbar__contacts a,
  .breadcrumb a,
  .footer__legal a { display: inline-flex; align-items: center; min-height: 44px; }
  .footer__col a { display: flex; align-items: center; min-height: 44px; }
  .footer__social a { width: 44px; height: 44px; }
  .pcard__arrow { width: 44px; height: 44px; }
  .acc__q { min-height: 56px; }
  .chip { min-height: 44px; display: inline-flex; align-items: center; }
  /* The enquiry form's country datalist and every field stay comfortable
     to hit without changing the desktop rhythm. */
  .enq .field input, .enq .field textarea { min-height: 46px; }
  .enq__btn { min-height: 48px; }
}

/* Hero carousel dots read as 34x5 bars by design. That is far too small to
   hit, so an invisible pseudo-element carries the touch target out to 44px
   tall while the bar itself is untouched. */
.hero__dot { position: relative; }
.hero__dot::before {
  content: "";
  position: absolute;
  left: 0; right: 0;
  top: 50%;
  height: 44px;
  transform: translateY(-50%);
}

/* Product cards link only through their small arrow, so on a phone the
   tappable area was 34×34 in a full-width card. Stretch the existing link
   over the whole card — no markup change, no visual change. */
.pcard { position: relative; }
.pcard__arrow::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
/* Keep genuinely interactive children above the stretched hit area. */
.pcard a:not(.pcard__arrow), .pcard button { position: relative; z-index: 1; }

/* Cleaner tap feedback than the default grey flash. */
a, button, .btn, summary, [role="button"] { -webkit-tap-highlight-color: rgba(30, 107, 255, 0.14); }

/* =========================================================
   GRID DENSITY AT TABLET
   Three 230px cards on a 768px tablet was cramped; two reads far better.
   ========================================================= */
@media (min-width: 621px) and (max-width: 900px) {
  .grid--3, .grid--4 { grid-template-columns: repeat(2, 1fr); }
  .ctagrid { grid-template-columns: repeat(2, 1fr); }
}

/* =========================================================
   ULTRA-WIDE — let the measure grow a little rather than
   stranding the content in a 1200px ribbon on a 4K panel.
   ========================================================= */
@media (min-width: 1800px) { :root { --container: 1320px; } }
@media (min-width: 2400px) { :root { --container: 1440px; } }

/* =========================================================
   NOTCHED / SAFE-AREA DEVICES (iPhone landscape, foldables)
   ========================================================= */
@supports (padding: max(0px, env(safe-area-inset-left))) {
  .container {
    padding-left: max(var(--gutter), env(safe-area-inset-left));
    padding-right: max(var(--gutter), env(safe-area-inset-right));
  }
  .wa-float {
    right: max(20px, env(safe-area-inset-right));
    bottom: max(20px, env(safe-area-inset-bottom));
  }
  .to-top {
    right: max(24px, env(safe-area-inset-right));
    bottom: max(92px, calc(env(safe-area-inset-bottom) + 72px));
  }
}

/* Keep the sticky in-page nav clear of a stuck header on short screens. */
@media (max-height: 520px) {
  .pdnav { position: static; }
}

/* =========================================================
   SHRINK-TO-FIT
   Grid and flex children default to min-width:auto, so they refuse to go
   below their content's minimum size. At 320px that pushed the footer
   columns, the contact form and the info cards ~25-40px past the viewport
   (clipped by overflow-x, but the content was genuinely cut off).
   ========================================================= */
.footer__inner > *,
.contact > *,
.formgrid > *,
.catlayout > *,
.applayout > *,
.pd > *,
.split > * { min-width: 0; }

/* .infostack is a single-column grid, and an implicit `auto` track floors at
   its cards' min-content — so the cards sat ~25px wider than the column at
   320px and had their right edge clipped. minmax(0,1fr) lets the track
   shrink; the contact strings then wrap instead of forcing the width back. */
.infostack { grid-template-columns: minmax(0, 1fr); }
.infostack > *,
.inforow > div { min-width: 0; }
.inforow a, .inforow span { overflow-wrap: anywhere; }

.field input, .field textarea, .field select { min-width: 0; width: 100%; }
/* The newsletter row is a flex pair; the input's default intrinsic width
   pushed the subscribe button off the edge at 320px. */
.news { min-width: 0; }
.news input { min-width: 0; }
.footer__blurb { max-width: min(34ch, 100%); }

/* Long words and unbroken strings (product codes, emails) must wrap rather
   than widen their container. Data tables are excluded — their cells are
   deliberately nowrap and scroll horizontally instead. */
h1, h2, h3, h4, p, li, figcaption, .bcard, .chip { overflow-wrap: break-word; }

/* A long product name pushed the final breadcrumb item off the right edge
   on phones — the row could not wrap. */
.breadcrumb { flex-wrap: wrap; row-gap: 4px; }

/* capabilities.html pinned its process grid to three columns with an inline
   style, which outranks every media query — so it stayed three columns at
   320px and ran ~90px past the screen. As a modifier it keeps the intended
   three-up desktop layout and inherits .steps' responsive collapse below. */
@media (min-width: 1041px) {
  .steps--3 { grid-template-columns: repeat(3, 1fr); }
}

/* Sideways reveals start 52px off-axis and only settle once they fire.
   styles.css already converts them to a rise below 680px; the split layout
   actually goes single-column at 900px, so extend it to match. */
@media (max-width: 900px) {
  .js .reveal.reveal--left,
  .js .reveal.reveal--right { transform: translateY(30px); }
}
/* Above that, cap the slide distance to the page gutter so the pre-reveal
   offset can never sit outside the viewport (it was 52px against a 41px
   gutter at 912–1024px). Full 52px is retained once there is room for it. */
@media (min-width: 901px) {
  .js .reveal.reveal--left { transform: translateX(calc(-1 * min(52px, 3vw))); }
  .js .reveal.reveal--right { transform: translateX(min(52px, 3vw)); }
}
