/*
  Vikman Technologies stylesheet

  Order of this file:
    Tokens, base, typography, layout, buttons, navbar, hero, cards, filters,
    marquee, forms, footer, floating buttons, animation, accessibility,
    responsive, the lens graphic, and print.

  Colours, shadows, radii and timings are all CSS variables at the top.
  Nothing below them hardcodes a hex value, so changing the brand navy in one
  place changes the whole site.
*/


/* ---------------------------------------------------------------- tokens */
:root {
  /* --- Brand palette (sampled from the Vikman visiting card) --- */
  --navy-900: #050f33;
  --navy-800: #071847;
  --navy-700: #0a1f5c;   /* PRIMARY brand navy */
  --navy-600: #102c7d;
  --navy-500: #163a9e;
  --navy-400: #2c55c4;
  --navy-100: #e6ecf9;
  --navy-50:  #f3f6fd;

  /* --- Accent: clinical cyan. Evokes optics/lenses, pairs with navy --- */
  --accent-600: #007d94;
  --accent-500: #00a3bf;   /* PRIMARY accent */
  --accent-400: #22bcd6;
  --accent-100: #e0f7fb;

  /* --- Neutrals --- */
  --ink-900: #0d1420;      /* headings */
  --ink-700: #33405a;      /* body text */
  --ink-500: #64748b;      /* muted text */
  --ink-300: #cbd5e1;      /* borders */
  --ink-100: #eef2f7;      /* subtle fills */
  --surface: #ffffff;
  --surface-alt: #f6f8fc;  /* page background */

  /* --- Semantic aliases: use THESE in components, not raw colours --- */
  --brand: var(--navy-700);
  --brand-dark: var(--navy-900);
  --brand-accent: var(--accent-500);
  --text-heading: var(--ink-900);
  --text-body: var(--ink-700);
  --text-muted: var(--ink-500);
  --border-soft: rgba(10, 31, 92, 0.10);

  /* --- Typography scale --- */
  --font-display: 'Plus Jakarta Sans', 'Segoe UI', system-ui, sans-serif;
  --font-body: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;

  /* --- Elevation (layered, tinted with navy instead of pure black) --- */
  --shadow-xs: 0 1px 2px rgba(10, 31, 92, 0.06);
  --shadow-sm: 0 2px 8px rgba(10, 31, 92, 0.06);
  --shadow-md: 0 8px 24px rgba(10, 31, 92, 0.08);
  --shadow-lg: 0 18px 48px rgba(10, 31, 92, 0.14);
  --shadow-glow: 0 12px 36px rgba(0, 163, 191, 0.22);

  /* --- Radii --- */
  --radius-sm: 8px;
  --radius-md: 14px;
  --radius-lg: 20px;
  --radius-xl: 28px;
  --radius-pill: 999px;

  /* --- Motion --- */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --dur-fast: 180ms;
  --dur-base: 320ms;
  --dur-slow: 700ms;
  --dur-reveal: 380ms;   /* scroll-in entrance, see .reveal */

  /* --- Layout --- */
  --nav-height: 88px;
  --section-pad: clamp(3.5rem, 8vw, 7rem);
}

/* Bootstrap variable overrides, makes Bootstrap components adopt our brand
   without writing !important everywhere. Bootstrap 5.3 exposes these. */
:root {
  --bs-primary: var(--navy-700);
  --bs-primary-rgb: 10, 31, 92;
  --bs-body-color: var(--ink-700);
  --bs-body-bg: var(--surface-alt);
  --bs-body-font-family: var(--font-body);
  --bs-link-color: var(--navy-600);
  --bs-link-hover-color: var(--accent-600);
  --bs-border-color: var(--ink-300);
  --bs-emphasis-color: var(--ink-900);
}


/* ------------------------------------------------------ base & reset */
* { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  /* Offset anchor jumps so the fixed navbar never covers the target heading */
  scroll-padding-top: calc(var(--nav-height) + 16px);
  /* Safety net against a stray wide element causing sideways scroll.
     NOTE: this belongs on <html>, not <body>. Per the CSS spec, setting
     overflow on one axis forces the other axis to compute to `auto`, so
     `body { overflow-x: hidden }` silently turns <body> into a second scroll
     container, which causes double scrollbars and breaks position: sticky. */
  overflow-x: hidden;
}

body {
  font-family: var(--font-body);
  color: var(--text-body);
  background-color: var(--surface-alt);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img { max-width: 100%; height: auto; }

::selection {
  background: var(--accent-500);
  color: #fff;
}

/* Custom scrollbar, small detail, reads as "considered" */
::-webkit-scrollbar { width: 10px; }
::-webkit-scrollbar-track { background: var(--ink-100); }
::-webkit-scrollbar-thumb {
  background: var(--navy-400);
  border-radius: var(--radius-pill);
  border: 2px solid var(--ink-100);
}
::-webkit-scrollbar-thumb:hover { background: var(--navy-500); }


/* -------------------------------------------------------- typography */
/*
   Headings use clamp() for fluid type: clamp(MIN, PREFERRED, MAX).
   The middle value scales with viewport width, so text resizes smoothly
   between breakpoints instead of jumping. No media queries needed.
*/
h1, h2, h3, h4, h5, h6, .display-title {
  font-family: var(--font-display);
  color: var(--text-heading);
  font-weight: 700;
  line-height: 1.18;
  letter-spacing: -0.02em;
}

h1, .h1 { font-size: clamp(2.1rem, 5vw, 3.5rem); }
h2, .h2 { font-size: clamp(1.7rem, 3.6vw, 2.6rem); }
h3, .h3 { font-size: clamp(1.3rem, 2.4vw, 1.75rem); }
h4, .h4 { font-size: clamp(1.1rem, 1.8vw, 1.3rem); }

.lead-text {
  font-size: clamp(1.02rem, 1.6vw, 1.18rem);
  color: var(--text-muted);
  line-height: 1.75;
}

.text-brand { color: var(--brand) !important; }
.text-accent { color: var(--accent-600) !important; }
.bg-brand { background-color: var(--brand) !important; }
.bg-brand-dark { background-color: var(--brand-dark) !important; }
.bg-surface { background-color: var(--surface) !important; }
.bg-surface-alt { background-color: var(--surface-alt) !important; }

/* Gradient text used for a single emphasised word in big headings */
.text-gradient {
  background: linear-gradient(100deg, var(--accent-400), var(--navy-400));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}

/* Small uppercase label that sits above a section heading */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--font-display);
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent-600);
  margin-bottom: 0.9rem;
}
.eyebrow::before {
  content: "";
  width: 26px;
  height: 2px;
  background: var(--accent-500);
  border-radius: 2px;
}
.eyebrow.eyebrow-light { color: var(--accent-400); }
.eyebrow.eyebrow-center { justify-content: center; }


/* ------------------------------------------ layout & section helpers */
.section { padding-block: var(--section-pad); }
.section-sm { padding-block: clamp(2.5rem, 5vw, 4rem); }
/* Two full --section-pad values meet at every section join, which at the top of
   the scale is 7rem + 7rem = 224px of nothing. These trim one side or the other
   where that reads as a missing block rather than as breathing room. */
.section-tight-top { padding-top: clamp(1.75rem, 3vw, 2.5rem); }
.section-tight-bottom { padding-bottom: clamp(1.75rem, 3vw, 2.75rem); }


/* --- Hero scroll cue ---
   Tells a first-time visitor there is more below the fold. A link, not an
   ornament: it is focusable and it jumps to the first content section. */
/* Fixed to the viewport, not placed in the flow. The hero is taller than most
   screens once the stats strip is in it, so a cue sitting at the end of that
   content lands below the fold: it would only come into view once the visitor
   had already scrolled, by which point it has nothing left to tell them.
   Pinned to the bottom of the window it is visible the moment the page opens,
   which is the only moment it is any use. */
.scroll-cue {
  position: fixed;
  z-index: 20;
  left: 50%;
  bottom: 1.4rem;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.55rem;
  width: max-content;
  text-decoration: none;
  color: rgba(255, 255, 255, 0.72);
  transition: color var(--dur-base) var(--ease-out),
              opacity var(--dur-base) var(--ease-out);
  /* Its own entrance rather than .rise-in: that helper animates to
     `transform: none`, which would throw away the translateX(-50%) doing the
     centring and leave the cue hanging off to the right. */
  opacity: 0;
  animation: cueIn 800ms var(--ease-out) 1000ms forwards;
}
@keyframes cueIn {
  from { opacity: 0; transform: translate(-50%, 20px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}
.scroll-cue:hover { color: #fff; }
.scroll-cue-text {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
/* A mouse body with a wheel that runs down inside it. Drawn rather than an
   icon so it inherits the hero's colour and needs no second asset. */
.scroll-cue-mouse {
  position: relative;
  width: 24px;
  height: 38px;
  border: 2px solid currentColor;
  border-radius: 999px;
  opacity: 0.85;
}
.scroll-cue-mouse::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 7px;
  width: 3px;
  height: 7px;
  margin-left: -1.5px;
  border-radius: 2px;
  background: currentColor;
  animation: scrollWheel 1.9s var(--ease-out) infinite;
}
@keyframes scrollWheel {
  0%   { transform: translateY(0);    opacity: 0; }
  22%  { opacity: 1; }
  72%  { transform: translateY(13px); opacity: 0; }
  100% { transform: translateY(13px); opacity: 0; }
}
/* Once the visitor has started scrolling the cue has done its job. main.js
   puts .has-scrolled on <body> past the first screenful. */
body.has-scrolled .scroll-cue {
  /* The entrance animation uses `forwards`, and a filled animation beats a
     plain declaration, so the fade-out has to cancel it rather than just set
     opacity. Cancelling also restores the base transform, which still carries
     the centring. */
  animation: none;
  opacity: 0;
  pointer-events: none;
}
@media (max-width: 991.98px) { .scroll-cue { display: none; } }

.section-head {
  max-width: 720px;
  margin-inline: auto;
  text-align: center;
  margin-bottom: clamp(2.2rem, 5vw, 3.5rem);
}
.section-head.text-start { margin-inline: 0; text-align: left !important; }

/* Thin gradient rule used to separate dark sections */
.rule-gradient {
  height: 3px;
  border: 0;
  opacity: 1;
  background: linear-gradient(90deg, transparent, var(--accent-500), transparent);
}

/* Page banner shown at the top of every inner page */
.page-banner {
  position: relative;
  padding-top: calc(var(--nav-height) + clamp(2.5rem, 6vw, 4.5rem));
  padding-bottom: clamp(2.5rem, 6vw, 4rem);
  background:
    radial-gradient(900px 400px at 15% 0%, rgba(0, 163, 191, 0.22), transparent 60%),
    linear-gradient(140deg, var(--navy-900), var(--navy-700) 55%, var(--navy-600));
  color: #fff;
  overflow: hidden;
}
.page-banner h1 { color: #fff; }
.page-banner p { color: rgba(255, 255, 255, 0.76); }

/* A photograph behind the banner on the two catalogue pages. The picture goes
   on ::before because .grid-texture already owns ::after, and both sit under
   the content: `.grid-texture > * { z-index: 1 }` has already lifted it.
   The image is named per page in a --banner-photo custom property rather than
   given its own class, so adding one to another page is one inline value.

   CAREFUL: the path in that property must be written relative to THIS FILE,
   not to the page. A url() inside a custom property is resolved where the
   var() is used, which is here in css/, so a page that writes
   "images/x.jpg" ends up requesting css/images/x.jpg and gets nothing. Hence
   the leading "../" on every one of them.

   `luminosity` is what keeps the banner navy: the photograph contributes its
   light and shade and the gradient underneath keeps the colour, so a warm
   picture cannot drag the brand towards brown. */
.page-banner--photo::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--banner-photo);
  background-size: cover;
  background-position: center;
  opacity: 0.34;
  mix-blend-mode: luminosity;
  pointer-events: none;
}

/* Partner detail banner */
.banner-lead { color: rgba(255, 255, 255, 0.8); max-width: 56ch; }
/* The brand's own line, where it has one. Italic and in the display face so it
   reads as something the manufacturer says, not as another line of our copy. */
.banner-motto {
  margin: 0.35rem 0 0;
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.06rem;
  color: rgba(255, 255, 255, 0.9);
}
.banner-meta {
  margin: 0.5rem 0 0.75rem;
  font-weight: 600;
  color: var(--accent-400);
}
.banner-logo-plate {
  place-items: center;
  min-height: 170px;
}
.banner-logo-plate img {
  max-height: 96px;
  max-width: 100%;
  object-fit: contain;
}

/* Detail page body */
.brand-story { font-size: 1.02rem; line-height: 1.8; }
.highlight-tick { color: var(--accent-500); }
.spec-list { font-size: 0.9rem; }
.spec-list dt { font-weight: 400; color: var(--text-muted); }
.spec-list dd { font-weight: 600; margin-bottom: 0.5rem; }

/* Navy call-to-action bands */
.band-heading { font-size: clamp(1.4rem, 2.6vw, 2rem); margin-bottom: 0.5rem; }
.band-sub { color: rgba(255, 255, 255, 0.75); }

/* Decorative grid texture overlay for dark bands */
.grid-texture::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(rgba(255,255,255,0.045) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,0.045) 1px, transparent 1px);
  background-size: 56px 56px;
  -webkit-mask-image: radial-gradient(70% 70% at 50% 40%, #000, transparent);
  mask-image: radial-gradient(70% 70% at 50% 40%, #000, transparent);
  pointer-events: none;
}
.grid-texture > * { position: relative; z-index: 1; }

/* Breadcrumb */
.crumbs {
  font-size: 0.86rem;
  color: rgba(255,255,255,0.62);
  margin-bottom: 1rem;
}
.crumbs a { color: rgba(255,255,255,0.86); text-decoration: none; }
.crumbs a:hover { color: #fff; text-decoration: underline; }
.crumbs .sep { margin-inline: 0.5rem; opacity: 0.5; }


/* ----------------------------------------------------------- buttons */
.btn {
  font-family: var(--font-display);
  font-weight: 600;
  border-radius: var(--radius-pill);
  padding: 0.72rem 1.6rem;
  transition: transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.btn:active { transform: translateY(1px); }

.btn-brand {
  background: linear-gradient(135deg, var(--navy-700), var(--navy-500));
  color: #fff;
  border: 1px solid transparent;
  box-shadow: var(--shadow-md);
  position: relative;
  overflow: hidden;
}
.btn-brand:hover,
.btn-brand:focus {
  color: #fff;
  transform: translateY(-2px);
  box-shadow: var(--shadow-lg);
}
/* Light sweep that travels across the button on hover */
.btn-brand::after {
  content: "";
  position: absolute;
  top: 0; left: -120%;
  width: 60%; height: 100%;
  background: linear-gradient(100deg, transparent, rgba(255,255,255,0.32), transparent);
  transform: skewX(-20deg);
  transition: left var(--dur-slow) var(--ease-out);
}
.btn-brand:hover::after { left: 140%; }

.btn-accent {
  background: linear-gradient(135deg, var(--accent-500), var(--accent-600));
  color: #fff;
  border: 1px solid transparent;
  box-shadow: var(--shadow-glow);
}
.btn-accent:hover, .btn-accent:focus {
  color: #fff;
  transform: translateY(-2px);
  box-shadow: 0 18px 44px rgba(0, 163, 191, 0.34);
}

.btn-outline-brand {
  border: 1.5px solid var(--brand);
  color: var(--brand);
  background: transparent;
}
.btn-outline-brand:hover, .btn-outline-brand:focus {
  background: var(--brand);
  color: #fff;
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

/* For use on dark backgrounds */
.btn-ghost-light {
  border: 1.5px solid rgba(255,255,255,0.45);
  color: #fff;
  background: rgba(255,255,255,0.06);
  backdrop-filter: blur(6px);
}
.btn-ghost-light:hover, .btn-ghost-light:focus {
  background: #fff;
  color: var(--brand);
  border-color: #fff;
  transform: translateY(-2px);
}

.btn-lg { padding: 0.9rem 2.1rem; font-size: 1.02rem; }

/* Arrow inside a button nudges right on hover */
.btn .bi-arrow-right {
  transition: transform var(--dur-fast) var(--ease-out);
}
.btn:hover .bi-arrow-right { transform: translateX(4px); }


/* ------------------------------------------------------------ navbar */
/*
   Starts transparent over the hero, then gains a solid background + shadow
   once the user scrolls (the .is-scrolled class is toggled by js/main.js).
*/
.site-nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 1030;
  min-height: var(--nav-height);
  background: transparent;
  transition: background-color var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out),
              backdrop-filter var(--dur-base) var(--ease-out);
}
.site-nav.is-scrolled,
.site-nav.is-solid {
  background: rgba(255, 255, 255, 0.86);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  box-shadow: var(--shadow-sm);
  border-bottom: 1px solid var(--border-soft);
}

.site-nav .navbar-brand {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding-block: 0.6rem;
}
/* The mark is an inline <svg> filled with currentColor, so its colour comes
   from the `color` of the brand link. White over the dark hero, navy once the
   navbar turns solid, one asset, no image swap mid-scroll, and it can be
   transitioned like any other colour. */
.site-nav .navbar-brand { color: #fff; }
.site-nav.is-scrolled .navbar-brand,
.site-nav.is-solid .navbar-brand { color: var(--brand); }

/* Deliberately the largest thing in the bar: the client wants the mark to be
   what the eye lands on first, ahead of the nav links. --nav-height goes up to
   match, since the hero's top padding is measured from it. */
.site-nav .brand-mark {
  height: 50px;
  width: auto;
  flex: 0 0 auto;
  transition: transform var(--dur-base) var(--ease-out),
              color var(--dur-base) var(--ease-out);
}
.site-nav .navbar-brand:hover .brand-mark { transform: scale(1.06); }

.brand-wordmark {
  display: flex;
  flex-direction: column;
  line-height: 1;
}
.brand-wordmark .name {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.34rem;
  letter-spacing: 0.01em;
  color: #fff;
  transition: color var(--dur-base) var(--ease-out);
}
/* The registered mark. Smaller than the wordmark and lifted, so it reads as a
   legal mark rather than as a letter of the name. line-height:0 keeps it from
   pushing the line box taller wherever it is used.

   It used to be 0.58em, which on the navbar worked out around 9px. At that size
   the ring and the R inside it merge and the glyph just reads as a dot. Nudged
   up, and given a slightly heavier weight so the counter inside the R survives
   at small sizes. `vertical-align: super` on its own lifted it too far for the
   larger glyph, so the offset is set explicitly instead. */
.rmark {
  font-size: 0.66em;
  font-weight: 600;
  letter-spacing: 0;
  vertical-align: baseline;
  position: relative;
  top: -0.42em;
  line-height: 0;
  margin-left: 0.1em;
}
/* Centred under the name, as it is on the visiting card. The column stretches
   its children, so the tag is already as wide as the name above it.
   text-indent cancels the trailing gap that letter-spacing leaves after the
   last letter; without it the line sits half a letter-space left of centre. */
.brand-wordmark .tag {
  font-size: 0.68rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  text-align: center;
  text-indent: 0.22em;
  color: rgba(255,255,255,0.65);
  margin-top: 4px;
  transition: color var(--dur-base) var(--ease-out);
}
.site-nav.is-scrolled .brand-wordmark .name,
.site-nav.is-solid .brand-wordmark .name { color: var(--brand); }
.site-nav.is-scrolled .brand-wordmark .tag,
.site-nav.is-solid .brand-wordmark .tag { color: var(--text-muted); }

.site-nav .nav-link {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.94rem;
  color: rgba(255,255,255,0.86);
  padding: 0.5rem 0.95rem !important;
  position: relative;
  transition: color var(--dur-fast) var(--ease-out);
}
.site-nav .nav-link:hover { color: #fff; }
.site-nav.is-scrolled .nav-link,
.site-nav.is-solid .nav-link { color: var(--text-body); }
.site-nav.is-scrolled .nav-link:hover,
.site-nav.is-solid .nav-link:hover { color: var(--brand); }

/* Animated underline that grows from the centre */
.site-nav .nav-link::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 2px;
  width: 0;
  height: 2px;
  border-radius: 2px;
  background: var(--accent-400);
  transform: translateX(-50%);
  transition: width var(--dur-base) var(--ease-out);
}
.site-nav .nav-link:hover::after,
.site-nav .nav-link.active::after { width: 22px; }
.site-nav .nav-link.active { color: #fff; }
.site-nav.is-scrolled .nav-link.active,
.site-nav.is-solid .nav-link.active { color: var(--brand); }

/* Hamburger: custom so it can be tinted for both nav states */
.site-nav .navbar-toggler {
  border: 1px solid rgba(255,255,255,0.4);
  padding: 0.4rem 0.6rem;
  border-radius: var(--radius-sm);
}
.site-nav .navbar-toggler:focus { box-shadow: none; }
.site-nav .navbar-toggler-icon {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(255,255,255,0.9)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2.2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
}
.site-nav.is-scrolled .navbar-toggler,
.site-nav.is-solid .navbar-toggler { border-color: var(--border-soft); }
.site-nav.is-scrolled .navbar-toggler-icon,
.site-nav.is-solid .navbar-toggler-icon {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(10,31,92,0.9)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2.2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
}

/* Mobile dropdown panel */
@media (max-width: 991.98px) {
  .site-nav .navbar-collapse {
    background: var(--surface);
    margin-top: 0.75rem;
    padding: 1rem;
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
  }
  .site-nav .nav-link,
  .site-nav.is-scrolled .nav-link { color: var(--text-body); }
  .site-nav .nav-link.active { color: var(--brand); }
  .site-nav .nav-link::after { display: none; }
  .site-nav .nav-cta { margin-top: 0.6rem; }
}

/* The phone number is one thing and must stay on one line. With the enlarged
   brand taking 385px of the bar, the button was being squeezed narrow enough
   to wrap the number across two lines. */
.site-nav .nav-cta { white-space: nowrap; }

/* Between lg and xl the bar holds the big brand, six links and the phone
   button. Trimming the link padding buys back the room rather than shrinking
   the brand, which is the thing the client wants noticed first. */
@media (min-width: 992px) and (max-width: 1399.98px) {
  .site-nav .nav-link { padding-inline: 0.55rem; }
  .site-nav .nav-cta { margin-left: 0.75rem !important; }
}


/* -------------------------------------------------------------- hero */
.hero {
  position: relative;
  min-height: min(100svh, 860px);
  display: flex;
  align-items: center;
  padding-top: calc(var(--nav-height) + 2rem);
  padding-bottom: 4rem;
  background:
    radial-gradient(1000px 520px at 78% 18%, rgba(0, 163, 191, 0.30), transparent 62%),
    radial-gradient(760px 420px at 10% 88%, rgba(44, 85, 196, 0.30), transparent 60%),
    linear-gradient(150deg, var(--navy-900) 0%, var(--navy-700) 52%, var(--navy-600) 100%);
  color: #fff;
  overflow: hidden;
}
.hero h1 { color: #fff; }
.hero .hero-sub {
  color: rgba(255,255,255,0.78);
  font-size: clamp(1rem, 1.8vw, 1.2rem);
  max-width: 40rem;
}

/* Slowly drifting blurred orbs behind the hero copy */
.hero-orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(70px);
  opacity: 0.5;
  pointer-events: none;
  animation: orbFloat 18s var(--ease-in-out) infinite alternate;
}
.hero-orb--1 { width: 380px; height: 380px; background: var(--accent-500); top: -90px; right: 6%; }
.hero-orb--2 { width: 300px; height: 300px; background: var(--navy-400); bottom: -80px; left: -60px; animation-delay: -6s; }
.hero-orb--3 { width: 220px; height: 220px; background: #4f8dff; top: 46%; right: 40%; animation-delay: -12s; opacity: 0.28; }

@keyframes orbFloat {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(28px, -34px, 0) scale(1.12); }
}

/* Trust chip row under the hero buttons */
.hero-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.42rem 0.95rem;
  border-radius: var(--radius-pill);
  background: rgba(255,255,255,0.09);
  border: 1px solid rgba(255,255,255,0.18);
  backdrop-filter: blur(8px);
  font-size: 0.83rem;
  color: rgba(255,255,255,0.9);
}
.hero-chip i { color: var(--accent-400); }

/* The glass panel on the right of the hero holding the stat tiles */
.glass-panel {
  background: rgba(255,255,255,0.07);
  border: 1px solid rgba(255,255,255,0.16);
  border-radius: var(--radius-xl);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  padding: clamp(1.25rem, 3vw, 2rem);
  box-shadow: 0 24px 60px rgba(0,0,0,0.25);
}

.stat-tile {
  text-align: center;
  padding: 1rem 0.5rem;
  border-radius: var(--radius-md);
  transition: background-color var(--dur-base) var(--ease-out);
}
.stat-tile:hover { background: rgba(255,255,255,0.07); }
.stat-tile .stat-num {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(1.8rem, 4vw, 2.6rem);
  line-height: 1;
  color: #fff;
  display: block;
}
.stat-tile .stat-num .suffix { color: var(--accent-400); }
.stat-tile .stat-label {
  display: block;
  margin-top: 0.45rem;
  font-size: 0.76rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.62);
}

/* NOTE: the hero previously carried a "Scroll" hint pinned to its bottom edge.
   Once the stats strip moved into that space the two overlapped, and the strip
   already does the job of showing there is more page below. Both the element
   and its styles were removed rather than nudged out of the way. */


/* ------------------------------------------------------------- cards */
.card-soft {
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  transition: transform var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out);
}
.card-soft:hover {
  transform: translateY(-6px);
  box-shadow: var(--shadow-lg);
  border-color: rgba(0, 163, 191, 0.34);
}

/* --- Partner card (partners grid + homepage) --- */
.partner-card {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100%;
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  text-decoration: none;
  transition: transform var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out);
}
/* Accent bar that wipes across the top edge on hover */
.partner-card::before {
  content: "";
  position: absolute;
  top: 0; left: 0;
  height: 3px;
  width: 100%;
  background: linear-gradient(90deg, var(--accent-500), var(--navy-400));
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-base) var(--ease-out);
}
.partner-card:hover {
  transform: translateY(-8px);
  box-shadow: var(--shadow-lg);
  border-color: rgba(0, 163, 191, 0.3);
}
.partner-card:hover::before { transform: scaleX(1); }

/* FLAT WHITE, not a gradient.
   Manufacturers ship logos in whatever form they have: some are transparent
   PNG/SVG, but several (Teleon, MiBo) are rasters with a baked-in WHITE
   background. On a tinted or gradient plate those show as a visible white
   rectangle. A flat white plate lets every one of them sit invisibly, which
   matters more than the plate itself looking interesting. */
.partner-card .logo-well {
  height: 132px;
  display: grid;
  place-items: center;
  padding: 1.4rem;
  background: #ffffff;
  border-bottom: 1px solid var(--border-soft);
}

/* Some manufacturers only supply a WHITE single-colour logo, which would be
   invisible on the light plate above. Those get a navy plate instead, set from
   the data via `logoOnDark`. The alternative, recolouring a partner's
   trademark to suit our layout, is not ours to do. */
.logo-well--dark {
  background: linear-gradient(160deg, var(--navy-700), var(--navy-900)) !important;
  border-bottom-color: transparent !important;
}
.partner-card .logo-well img {
  max-height: 74px;
  max-width: 86%;
  object-fit: contain;
  transition: transform var(--dur-base) var(--ease-out);
}
.partner-card:hover .logo-well img { transform: scale(1.06); }

.partner-card .card-body { padding: 1.35rem 1.4rem 1.5rem; flex: 1; display: flex; flex-direction: column; }
.partner-card .partner-name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.08rem;
  color: var(--text-heading);
  margin-bottom: 0.3rem;
}
.partner-card:hover .partner-name { color: var(--brand); }
.partner-card .partner-country {
  font-size: 0.8rem;
  color: var(--text-muted);
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin-bottom: 0.75rem;
}
.partner-card .partner-tagline {
  font-size: 0.9rem;
  color: var(--text-body);
  margin-bottom: 1rem;
  flex: 1;
}
/* Also used on its own, as a plain "go somewhere next" link under a grid */
.card-cta {
  font-family: var(--font-display);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--accent-600);
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  text-decoration: none;
}
.card-cta i { transition: transform var(--dur-fast) var(--ease-out); }
.partner-card:hover .card-cta i,
a.card-cta:hover i { transform: translateX(4px); }
a.card-cta:hover { color: var(--brand); }

/* --- Product card --- */
/* On the catalogue this renders as an <a> to the manufacturer, on a partner's
   own page as an <article>. The link resets are for the first case. */
.product-card {
  display: block;
  height: 100%;
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-xs);
  text-decoration: none;
  color: inherit;
  transition: transform var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out);
}
.product-card:hover {
  transform: translateY(-5px);
  box-shadow: var(--shadow-md);
  color: inherit;
}
a.product-card:hover { border-color: rgba(0, 163, 191, 0.34); }
.product-card:hover .card-cta i { transform: translateX(4px); }

/* The card the catalogue link pointed at, so the scroll does not look arbitrary */
.product-card--targeted {
  border-color: var(--accent-500);
  box-shadow: 0 0 0 3px rgba(0, 163, 191, 0.18), var(--shadow-md);
}
/* Also light in both themes, the product illustrations are navy line art. */
.product-card .product-media {
  height: 168px;
  display: grid;
  place-items: center;
  background: linear-gradient(160deg, #e2f6fb, #eef3fd);
  padding: 1rem;
  overflow: hidden;
}
.product-card .product-media img {
  max-height: 130px;
  object-fit: contain;
  transition: transform var(--dur-slow) var(--ease-out);
}
.product-card:hover .product-media img { transform: scale(1.08); }
/* Manufacturers' own product photography, nearly all of it cut out on white. */
.product-card .product-media--photo {
  background: #fff;
  padding: 0.5rem;
}
.product-card .product-media--photo img {
  max-height: 152px;
  max-width: 100%;
}

/* The named product list in the "at a glance" panel on a partner's page */
.panel-subhead {
  font-family: var(--font-display);
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 0.85rem;
}
.product-index {
  list-style: none;
  padding: 0;
  margin: 0 0 1.25rem;
}
.product-index li + li { margin-top: 0.4rem; }
.product-index a {
  display: flex;
  gap: 0.5rem;
  align-items: baseline;
  font-size: 0.94rem;
  font-weight: 600;
  color: var(--text-heading);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out);
}
.product-index a:hover { color: var(--accent-600); }
.product-index i {
  flex: 0 0 auto;
  font-size: 0.7rem;
  color: var(--accent-500);
  transition: transform var(--dur-fast) var(--ease-out);
}
.product-index a:hover i { transform: translateX(3px); }

/* Manufacturer name on catalogue cards */
.product-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin-bottom: 0.5rem;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--accent-600);
  text-decoration: none;
}
.product-brand:hover { color: var(--brand); }

.product-summary {
  margin-bottom: 0.75rem;
  font-size: 0.92rem;
  color: var(--text-body);
}

/* --- Value / feature card --- */
.value-card {
  height: 100%;
  padding: 2rem 1.75rem;
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
  transition: transform var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);
}
.value-card:hover { transform: translateY(-6px); box-shadow: var(--shadow-md); }
.icon-badge {
  width: 56px; height: 56px;
  border-radius: var(--radius-md);
  display: grid; place-items: center;
  font-size: 1.5rem;
  color: #fff;
  background: linear-gradient(135deg, var(--navy-700), var(--navy-500));
  box-shadow: var(--shadow-md);
  margin-bottom: 1.25rem;
  transition: transform var(--dur-base) var(--ease-out);
}
.value-card:hover .icon-badge { transform: rotate(-6deg) scale(1.07); }
.icon-badge.accent { background: linear-gradient(135deg, var(--accent-500), var(--accent-600)); }

/* --- Category tile ---
   Photograph on top, copy underneath. `overflow: hidden` is what keeps the
   picture inside the rounded corner, and it is also why the image can be
   scaled on hover without escaping the card. */
.category-tile {
  display: flex;
  flex-direction: column;
  text-decoration: none;
  border-radius: var(--radius-lg);
  background: var(--surface);
  border: 1px solid var(--border-soft);
  box-shadow: var(--shadow-xs);
  height: 100%;
  overflow: hidden;
  transition: transform var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out);
}
.category-tile:hover {
  transform: translateY(-6px);
  box-shadow: var(--shadow-lg);
  border-color: rgba(0, 163, 191, 0.32);
}

/* Fixed aspect rather than a fixed height: the tiles sit in a responsive grid,
   so a height in pixels would letterbox differently in every column width. */
.category-tile .cat-media {
  position: relative;
  display: block;
  aspect-ratio: 3 / 2;
  background: linear-gradient(150deg, var(--accent-100), #eef3fd);
  overflow: hidden;
  /* Half the category pictures are photographs and half are cut-out product
     shots on the tinted plate above. Without this hairline the pale ones have
     no edge against the white card body and the band disappears. */
  border-bottom: 1px solid var(--border-soft);
}
.category-tile .cat-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--dur-slow) var(--ease-out);
}
.category-tile:hover .cat-media img { transform: scale(1.06); }

/* The icon rides on the photograph as a small plate. Over a photo it needs its
   own background to stay legible whatever is behind it. */
.category-tile .cat-badge {
  position: absolute;
  left: 0.85rem;
  bottom: 0.85rem;
  width: 2.4rem;
  height: 2.4rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.94);
  color: var(--accent-600);
  font-size: 1.15rem;
  box-shadow: 0 2px 10px rgba(10, 31, 92, 0.18);
  transition: transform var(--dur-base) var(--ease-out);
}
.category-tile:hover .cat-badge { transform: scale(1.1); }
/* No photograph for this category yet: centre the icon in the empty plate so
   it reads as deliberate rather than as a picture that failed to load. */
.category-tile .cat-media--plain { display: grid; place-items: center; }
.category-tile .cat-media--plain .cat-badge {
  position: static;
  width: 3.4rem;
  height: 3.4rem;
  font-size: 1.6rem;
}

.category-tile .cat-body {
  display: block;
  padding: 1.3rem 1.4rem 1.5rem;
}
.category-tile .cat-name {
  font-family: var(--font-display);
  font-weight: 700;
  color: var(--text-heading);
  margin-bottom: 0.2rem;
}
.category-tile .cat-blurb {
  margin-bottom: 0.5rem;
  font-size: 0.9rem;
  color: var(--text-body);
}
.category-tile .cat-count { font-size: 0.82rem; color: var(--text-muted); }



/* Brochure library: icon, title, meta line, open affordance. Used on
   brochures.html and in the downloads block on each partner detail page.
   Renders as an <a> for files and a <button> for the video, so the button
   resets are needed here too. */
.brochure-card {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
  border-radius: var(--radius-lg);
  background: var(--surface);
  border: 1px solid var(--border-soft);
  box-shadow: var(--shadow-xs);
  overflow: hidden;
  font-family: inherit;
  font-size: inherit;
  text-align: left;
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  transition: transform var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out);
}
.brochure-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-md);
  border-color: rgba(0, 163, 191, 0.34);
  color: inherit;
}

/* The preview pane: the document itself, not just a name card. The icon plate
   sits underneath everything else, so it is what shows for a video (no frame
   to preview) and what shows for a fraction of a second before a PDF's own
   viewer has painted. */
.brochure-preview {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  background: var(--navy-50);
  border-bottom: 1px solid var(--border-soft);
  overflow: hidden;
}
.brochure-preview .brochure-icon {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  display: grid;
  place-items: center;
  border-radius: 0;
  color: var(--brand);
  font-size: 1.6rem;
}
/* A video has no page to preview, so the plate is the whole picture. The play
   button already sits in the middle of it; the file-type glyph behind would
   only peek out from under that circle, so it is dropped here. */
.brochure-preview.is-video { background: var(--accent-100); }
.brochure-preview.is-video .brochure-icon { display: none; }
/* Sits above the icon plate, below the play button */
.brochure-preview img {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%; height: 100%;
  object-fit: contain;
  background: var(--surface);
}
/* A rendered picture of page one. `contain` is wrong for these: a brochure page
   is far taller than this 4:3 well, so contain would shrink a whole A4 sheet
   into a thin strip with the text unreadable. Cover, pinned to the top, shows
   the masthead at a legible size, which is the part that identifies the
   document. */
.brochure-page {
  object-fit: cover !important;
  object-position: top center;
}
.brochure-play {
  position: absolute;
  z-index: 2;
  inset: 0;
  display: grid;
  place-items: center;
}
.brochure-play i {
  display: grid;
  place-items: center;
  width: 3rem; height: 3rem;
  border-radius: 50%;
  background: rgba(10, 31, 92, 0.82);
  color: #fff;
  font-size: 1.3rem;
  padding-left: 0.15rem; /* optical centring of the play glyph */
}

.brochure-body {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0.9rem 1.1rem 0.2rem;
}
.brochure-title {
  display: block;
  font-weight: 600;
  font-size: 0.95rem;
  line-height: 1.35;
  color: var(--text-heading);
}
.brochure-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  margin-top: 0.3rem;
  font-size: 0.76rem;
  color: var(--text-muted);
}
.brochure-brand { font-weight: 600; color: var(--accent-600); }
/* Separators between brand, type and size without extra markup */
.brochure-meta > * + *::before {
  content: "\00b7";
  margin-right: 0.35rem;
}
.brochure-go {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.35rem;
  padding: 0.6rem 1.1rem 0.9rem;
  color: var(--text-muted);
  font-size: 0.95rem;
  transition: transform var(--dur-base) var(--ease-out),
              color var(--dur-base) var(--ease-out);
}
.brochure-go::before {
  content: "Open";
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.02em;
}
.brochure-card[data-video] .brochure-go::before { content: "Play"; }
.brochure-card:hover .brochure-go {
  color: var(--accent-600);
  transform: translateX(2px);
}

/* "Documents for X" bar on brochures.html, shown when you arrive from a product */
.product-context {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  margin-bottom: 1.5rem;
  padding: 0.85rem 1.1rem;
  border-radius: var(--radius-lg);
  background: var(--accent-100);
  border: 1px solid rgba(0, 163, 191, 0.24);
}
.product-context-label {
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--accent-600);
}
.product-context-name {
  font-family: var(--font-display);
  font-weight: 700;
  color: var(--text-heading);
}
.product-context-brand {
  font-size: 0.84rem;
  font-weight: 600;
  color: var(--accent-600);
  text-decoration: none;
}
.product-context-brand::before { content: "\00b7"; margin-right: 0.7rem; }
.product-context-brand:hover { color: var(--brand); text-decoration: underline; }
/* Pushed to the far end on a wide row, sits underneath once it wraps */
.product-context-clear {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-left: auto;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text-muted);
  text-decoration: none;
}
.product-context-clear i { font-size: 0.7rem; }
.product-context-clear:hover { color: var(--brand); }

/* Shown instead of a document count on the three products that have none */
.product-nodocs {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.82rem;
  color: var(--text-muted);
}

/* Sits above the catalogue search. The cards are links but nothing else says
   so, and a doctor should not have to discover that by guessing. */
/* A banner, not a footnote. The cards being links is the most useful thing on
   this page and nothing else announces it, so it is sized to be read rather
   than skimmed past.

   Centred text rather than a centred flex row: as a flex item the icon gets
   middle-aligned against the whole block, so once the line wraps on a phone it
   drifts down between the two lines. Inline, it stays with the first word. */
.products-hint {
  max-width: 42rem;
  margin: 0 auto 1.75rem;
  padding: 0.85rem 1.25rem;
  border-radius: var(--radius-lg);
  background: var(--accent-100);
  border: 1px solid rgba(0, 163, 191, 0.28);
  text-align: center;
  font-family: var(--font-display);
  font-size: 1.02rem;
  font-weight: 700;
  color: var(--brand);
}
.products-hint i {
  margin-right: 0.5rem;
  font-size: 1.1rem;
  color: var(--accent-600);
}

.downloads-note {
  max-width: 70ch;
  font-size: 0.82rem;
  color: var(--text-muted);
}

.video-modal { border: none; border-radius: var(--radius-lg); overflow: hidden; }
.video-modal .modal-body { padding: 0; background: #000; }
.video-modal video { display: block; width: 100%; max-height: 70vh; background: #000; }
/* A YouTube embed has no intrinsic size, so unlike <video> it will not size
   itself from the file. aspect-ratio gives it the 16:9 every YouTube video
   uses and keeps it responsive without the old padding-top hack. */
.video-modal .video-embed {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  max-height: 70vh;
  border: 0;
  background: #000;
}

/* Dark contact card used in the footer CTA / contact page */
.contact-card {
  display: flex;
  gap: 1rem;
  align-items: flex-start;
  padding: 1.5rem;
  border-radius: var(--radius-lg);
  background: var(--surface);
  border: 1px solid var(--border-soft);
  box-shadow: var(--shadow-xs);
  text-decoration: none;
  color: inherit;
  height: 100%;
  transition: transform var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);
}
.contact-card:hover { transform: translateY(-5px); box-shadow: var(--shadow-md); color: inherit; }
.contact-card .ci {
  flex: 0 0 46px;
  width: 46px; height: 46px;
  border-radius: var(--radius-sm);
  display: grid; place-items: center;
  background: var(--navy-50);
  color: var(--brand);
  font-size: 1.2rem;
}
/* A flex item will not shrink below its content, and the email address is one
   unbreakable word, so without this the Email card pushes wider than its
   column and the row spills. */
.contact-body { min-width: 0; }

.contact-label {
  display: block;
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.contact-value {
  display: block;
  font-size: 1.02rem;
  font-weight: 700;
  color: var(--text-heading);
}
/* The email address is the one value that will not fit a quarter-width card at
   the size above. Set smaller and held on one line, because an address broken
   across two lines is harder to read back than a slightly smaller one. */
.contact-value--fit {
  font-size: 0.74rem;
  letter-spacing: -0.01em;
  white-space: nowrap;
}
.contact-note {
  display: block;
  font-size: 0.84rem;
  color: var(--text-muted);
}


/* ------------------------------------------ filters, search & badges */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  justify-content: center;
}
.filter-btn {
  font-family: var(--font-display);
  font-size: 0.87rem;
  font-weight: 600;
  padding: 0.52rem 1.25rem;
  border-radius: var(--radius-pill);
  border: 1.5px solid var(--border-soft);
  background: var(--surface);
  color: var(--text-body);
  cursor: pointer;
  transition: all var(--dur-fast) var(--ease-out);
}
.filter-btn:hover {
  border-color: var(--accent-500);
  color: var(--accent-600);
  transform: translateY(-2px);
}
.filter-btn.active {
  background: linear-gradient(135deg, var(--navy-700), var(--navy-500));
  border-color: transparent;
  color: #fff;
  box-shadow: var(--shadow-md);
}
.filter-btn .count {
  display: inline-block;
  margin-left: 0.4rem;
  font-size: 0.72rem;
  opacity: 0.65;
}

.search-field { position: relative; max-width: 460px; margin-inline: auto; }
.search-field .bi-search {
  position: absolute;
  left: 1.1rem; top: 50%;
  transform: translateY(-50%);
  color: var(--text-muted);
  pointer-events: none;
}
.search-field input {
  width: 100%;
  padding: 0.85rem 2.6rem 0.85rem 2.9rem;
  border-radius: var(--radius-pill);
  border: 1.5px solid var(--border-soft);
  background: var(--surface);
  font-size: 0.95rem;
  transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.search-field input:focus {
  outline: none;
  border-color: var(--accent-500);
  box-shadow: 0 0 0 4px rgba(0, 163, 191, 0.14);
}
.search-clear {
  position: absolute;
  right: 0.9rem; top: 50%;
  transform: translateY(-50%);
  border: 0; background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  display: none;
  padding: 0.25rem;
  line-height: 1;
}
.search-field.has-value .search-clear { display: block; }
.search-clear:hover { color: var(--brand); }

.tag-pill {
  display: inline-block;
  padding: 0.28rem 0.75rem;
  border-radius: var(--radius-pill);
  font-size: 0.72rem;
  font-weight: 600;
  font-family: var(--font-display);
  letter-spacing: 0.03em;
  background: var(--navy-50);
  color: var(--navy-600);
  border: 1px solid var(--border-soft);
}
.tag-pill.accent { background: var(--accent-100); color: var(--accent-600); border-color: rgba(0,163,191,0.2); }
.tag-pill.light { background: rgba(255,255,255,0.12); color: #fff; border-color: rgba(255,255,255,0.22); }

/* Empty state shown when a filter/search returns nothing */
.empty-state {
  text-align: center;
  padding: 4rem 1rem;
  color: var(--text-muted);
}
.empty-state i { font-size: 3rem; color: var(--ink-300); display: block; margin-bottom: 1rem; }

.result-count {
  font-size: 0.88rem;
  color: var(--text-muted);
  text-align: center;
  margin-bottom: 1.75rem;
}
.result-count strong { color: var(--brand); }


/* -------------------------- marquee, infinite horizontal logo strip */
/*
   The track holds the logo list TWICE. Translating by exactly -50% therefore
   lands on a pixel-identical frame, so the restart is invisible.
*/
.marquee {
  position: relative;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.marquee-track {
  display: flex;
  gap: 2.5rem;
  width: max-content;
  animation: marqueeScroll 38s linear infinite;
}
.marquee:hover .marquee-track { animation-play-state: paused; }
@keyframes marqueeScroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
.marquee-item {
  flex: 0 0 auto;
  height: 78px;
  min-width: 168px;
  display: grid;
  place-items: center;
  padding: 0 1.25rem;
  background: #ffffff;   /* see .logo-well, white-background rasters */
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-xs);
  transition: transform var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);
}
.marquee-item:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); }
.marquee-item img { max-height: 46px; max-width: 130px; object-fit: contain; }

/* The same tiles, standing still. Used on the about page, where the logos are
   evidence rather than motion, so it needs nothing but a wrapping container. */
.brand-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1rem;
}
/* Two per row on a phone. The tile's shared 168px minimum only lets one fit a
   343px container, which turns eleven brands into eleven rows. No flex-grow,
   so the odd one out stays half width instead of stretching across. */
@media (max-width: 575.98px) {
  .brand-grid .marquee-item {
    min-width: 0;
    flex: 0 1 calc(50% - 0.5rem);
    height: 66px;
  }
}


/* ------------------------------------------------------------- forms */
.form-label {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.86rem;
  color: var(--text-heading);
  margin-bottom: 0.4rem;
}
.form-control, .form-select {
  border-radius: var(--radius-sm);
  border: 1.5px solid var(--border-soft);
  padding: 0.72rem 0.95rem;
  font-size: 0.95rem;
  background: var(--surface);
  transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
/* The shorthand above sets padding on all four sides, which wipes out the
   right gutter Bootstrap reserves for the caret arrow. On a wide field nobody
   notices; on the narrow Title select the chosen value ("Prof.") runs straight
   under the arrow and is unreadable. Put the gutter back. */
.form-select {
  padding-right: 2.1rem;
  background-position: right 0.75rem center;
}
.form-control:focus, .form-select:focus {
  border-color: var(--accent-500);
  box-shadow: 0 0 0 4px rgba(0, 163, 191, 0.14);
}
.form-control.is-invalid, .form-select.is-invalid { border-color: #dc3545; }
.form-control.is-valid, .form-select.is-valid { border-color: #1a9e6a; }

/* Validation state on a <select> is the one that actually bit us. Bootstrap
   reserves 4.125rem on the right for a tick or cross icon, but the rule above
   keeps our plain caret background, so that icon is never drawn — the reserved
   space just squeezes the chosen value out of a narrow field the instant it
   validates. Which is exactly what happened to the Title dropdown: pick
   "Prof." and it half-disappeared. We show validity with the border, so take
   the space back. The :not() pair is not decoration — it is there to match the
   specificity of Bootstrap's own selector, which carries the same two :not()s.
   Without them this rule loses and the padding stays at 66px. */
.form-select.is-valid:not([multiple]):not([size]),
.form-select.is-invalid:not([multiple]):not([size]) {
  padding-right: 2.1rem;
  background-position: right 0.75rem center;
}
.invalid-feedback { font-size: 0.8rem; }

.form-note { font-size: 0.8rem; color: var(--text-muted); }

/* The consent tick is the only required control that is not a text field, and
   Bootstrap's default is a 1em box with a hairline border. Sitting next to
   0.95rem inputs it read as decoration rather than as something to fill in. */
.form-check-input {
  width: 1.15em;
  height: 1.15em;
  margin-top: 0.15em;
  border: 1.5px solid rgba(10, 31, 92, 0.34);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.form-check-input:hover { border-color: var(--accent-500); }
.form-check-input:checked {
  background-color: var(--accent-600);
  border-color: var(--accent-600);
}
.form-check-input:focus {
  border-color: var(--accent-500);
  box-shadow: 0 0 0 4px rgba(0, 163, 191, 0.14);
}
.form-check-input.is-invalid { border-color: #dc3545; }
.form-check-label { cursor: pointer; }

/* Boxed and tinted so a required agreement does not read as a footnote.
   Bootstrap gives .form-check a 1.5em left pad and pulls the input back by the
   same amount, so the larger pad here insets the box without moving the tick
   off its label. */
.consent-check {
  padding: 0.85rem 1rem 0.85rem 2.4rem;
  border: 1.5px solid var(--border-soft);
  border-radius: var(--radius-sm);
  background: var(--navy-50);
  font-size: 0.9rem;
}
.consent-check:focus-within { border-color: var(--accent-500); }

/* Inline status message under the form */
.form-status {
  display: none;
  padding: 0.9rem 1.1rem;
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
  margin-top: 1rem;
}
.form-status.show { display: block; }
.form-status.success { background: #e7f7ef; color: #14684a; border: 1px solid #b6e6ce; }
.form-status.error { background: #fdecec; color: #8f2222; border: 1px solid #f5c2c2; }


/* ------------------------------------------------------------ footer */
.site-footer {
  background: linear-gradient(160deg, var(--navy-900), var(--navy-700));
  color: rgba(255,255,255,0.72);
  padding-top: clamp(3rem, 6vw, 4.5rem);
  position: relative;
  overflow: hidden;
}
/* Footer column headings are <h3> for a correct document outline; their small
   uppercase look comes from here, not from using a smaller heading tag. */
.site-footer h3 {
  color: #fff;
  font-size: 0.82rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  margin-bottom: 1.15rem;
}
.site-footer .footer-brand-name {
  color: #fff;
  font-weight: 700;
  font-size: 1.15rem;
  letter-spacing: 0.01em;
  margin-bottom: 0.25rem;
}
.site-footer .footer-tagline {
  margin-bottom: 1rem;
  font-style: italic;
  font-size: 0.92rem;
  color: var(--accent-400);
}
.site-footer .footer-blurb {
  max-width: 34ch;
  font-size: 0.9rem;
}
.site-footer .footer-icon { color: var(--accent-400); }
.site-footer .footer-small { font-size: 0.8rem; opacity: 0.75; }
.site-footer a {
  color: rgba(255,255,255,0.72);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out), padding-left var(--dur-fast) var(--ease-out);
}
.site-footer a:hover { color: #fff; }
.site-footer .footer-links a:hover { padding-left: 5px; }
.site-footer .footer-links li { margin-bottom: 0.55rem; font-size: 0.92rem; }
.site-footer .brand-mark { height: 34px; width: auto; color: #fff; }
.footer-bottom {
  border-top: 1px solid rgba(255,255,255,0.12);
  margin-top: clamp(2rem, 4vw, 3rem);
  padding-block: 1.4rem;
  font-size: 0.84rem;
  /* Three items now. Below md they are already stacked by the utility classes,
     but between md and lg the regulatory line is still one long row and a third
     item would crowd it. Wrapping handles that without another breakpoint. */
  flex-wrap: wrap;
}
/* Legible rather than hidden, but still below the company's own copyright in
   the visual order: the surrounding text carries no opacity and the label here
   does, so the eye reaches the client's line first and this one second. */
.site-footer .footer-credit {
  font-size: 0.86rem;
  color: rgba(255,255,255,0.68);
}
/* .site-footer a strips underlines, which is right for the link lists above but
   would leave these two reading as plain text. Both links are set the same
   weight and colour as the words around them: the underline is enough to show
   they are clickable without the line shouting. */
.site-footer .footer-credit a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: rgba(255,255,255,0.35);
  text-underline-offset: 3px;
}
.site-footer .footer-credit a:hover {
  color: #fff;
  text-decoration-color: currentColor;
}
/* The separator is plain text in the template, not a ::before on the link:
   inside the anchor it would be underlined and clickable. */
.site-footer .footer-credit-sep { margin: 0 0.3rem; opacity: 0.6; }
/* Same treatment for the pipe in the trademark line, so the two separators in
   the bottom bar sit at the same weight. */
.site-footer .footer-sep { margin: 0 0.35rem; opacity: 0.45; }
.social-dot {
  width: 38px; height: 38px;
  border-radius: 50%;
  display: grid; place-items: center;
  background: rgba(255,255,255,0.09);
  border: 1px solid rgba(255,255,255,0.16);
  color: #fff;
  transition: all var(--dur-fast) var(--ease-out);
}
.social-dot:hover {
  background: var(--accent-500);
  border-color: var(--accent-500);
  transform: translateY(-3px);
  color: #fff;
}


/* -------------------------------------------------- floating actions */
.whatsapp-fab {
  position: fixed;
  right: 20px; bottom: 20px;
  z-index: 1040;
  width: 56px; height: 56px;
  border-radius: 50%;
  display: grid; place-items: center;
  background: #25d366;
  color: #fff;
  font-size: 1.7rem;
  box-shadow: 0 10px 26px rgba(37, 211, 102, 0.42);
  text-decoration: none;
  transition: transform var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);
}
.whatsapp-fab:hover { transform: scale(1.09); color: #fff; box-shadow: 0 14px 34px rgba(37, 211, 102, 0.55); }
/* Expanding ring to draw the eye without being obnoxious */
.whatsapp-fab::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 2px solid #25d366;
  animation: pulseRing 2.6s var(--ease-out) infinite;
}
@keyframes pulseRing {
  0%   { transform: scale(1); opacity: 0.7; }
  100% { transform: scale(1.65); opacity: 0; }
}

/* Call button, phones and tablets only.
   The navbar's phone CTA lives inside the collapsed menu below 992px, which is
   exactly the screen where someone is most likely to want to just ring. Sits
   above the WhatsApp button, in the site accent rather than WhatsApp green so
   the two read as different actions at a glance. No pulse ring: one thing on
   screen asking for attention is enough. */
.call-fab {
  position: fixed;
  right: 20px; bottom: 88px;
  z-index: 1040;
  width: 56px; height: 56px;
  border-radius: 50%;
  display: grid; place-items: center;
  background: linear-gradient(135deg, var(--accent-500), var(--accent-600));
  color: #fff;
  font-size: 1.45rem;
  box-shadow: 0 10px 26px rgba(0, 163, 191, 0.42);
  text-decoration: none;
  transition: transform var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out);
}
.call-fab:hover {
  transform: scale(1.09);
  color: #fff;
  box-shadow: 0 14px 34px rgba(0, 163, 191, 0.55);
}
/* From lg up the navbar shows the number again, so a second one is clutter. */
@media (min-width: 992px) { .call-fab { display: none; } }

.to-top {
  position: fixed;
  right: 20px; bottom: 88px;
  z-index: 1040;
  width: 44px; height: 44px;
  border-radius: 50%;
  border: 1px solid var(--border-soft);
  background: var(--surface);
  color: var(--brand);
  display: grid; place-items: center;
  box-shadow: var(--shadow-md);
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  transform: translateY(12px);
  transition: all var(--dur-base) var(--ease-out);
}
.to-top.show { opacity: 1; visibility: visible; transform: translateY(0); }
.to-top:hover { background: var(--brand); color: #fff; }
/* Below lg the call button takes the slot back-to-top was using, so back-to-top
   moves up one place: WhatsApp, call, top, reading up from the thumb.
   This has to come AFTER the base rule above, not before it. Both are one class
   deep, so with equal specificity the later one wins and a media query placed
   above would simply never take effect. */
@media (max-width: 991.98px) { .to-top { bottom: 156px; } }

/* Thin progress bar showing how far down the page the user has scrolled */
.scroll-progress {
  position: fixed;
  top: 0; left: 0;
  height: 3px;
  width: 0%;
  z-index: 1050;
  background: linear-gradient(90deg, var(--accent-500), var(--navy-400));
  transition: width 90ms linear;
}


/* ----------------------------------------------- animation utilities */
/*
   Elements marked .reveal start hidden and slightly offset. js/main.js uses an
   IntersectionObserver to add .is-visible when they scroll into view, which
   triggers the transition. Stagger children with inline --delay.
*/
/* Content resolves INTO FOCUS as it arrives: slightly blurred and offset, then
   sharp. For an eye-care company that is the right metaphor, and it reads as
   more considered than a plain fade.

   TIMING: deliberately quick. An entrance animation is there to make content
   feel like it settled into place, not to be watched. At 700ms with a 60ms
   stagger the last card in a row was still arriving well after the reader's
   eye had got there, which reads as the page being slow rather than as polish.
   380ms with a 40ms stagger keeps the effect and removes the wait. */
.reveal {
  opacity: 0;
  transform: translateY(16px);
  filter: blur(3px);
  transition: opacity var(--dur-reveal) var(--ease-out),
              transform var(--dur-reveal) var(--ease-out),
              filter var(--dur-reveal) var(--ease-out);
  transition-delay: var(--delay, 0ms);
  will-change: opacity, transform, filter;
}
.reveal.is-visible {
  opacity: 1;
  transform: none;
  filter: blur(0);
}
/* Once revealed, drop the compositing hint so long pages don't hold dozens of
   unnecessary GPU layers alive. */
.reveal.is-visible { will-change: auto; }

.reveal-left  { transform: translateX(-32px); }
.reveal-right { transform: translateX(32px); }
.reveal-zoom  { transform: scale(0.94); }

/* On narrow screens the horizontal variants slide in from off-canvas, which
   widens the page until they reveal. Columns are stacked at this size anyway,
   so a left/right entrance reads as an accident rather than a design, fall
   back to the plain vertical rise. */
@media (max-width: 991.98px) {
  .reveal-left,
  .reveal-right { transform: translateY(16px); }
}

/* Fires on page load rather than on scroll (used for hero content) */
.rise-in {
  opacity: 0;
  transform: translateY(24px);
  animation: riseIn 850ms var(--ease-out) forwards;
  animation-delay: var(--delay, 0ms);
}
@keyframes riseIn {
  to { opacity: 1; transform: none; }
}

.hover-lift { transition: transform var(--dur-base) var(--ease-out); }
.hover-lift:hover { transform: translateY(-4px); }


/* ----------------------------------------------------- accessibility */
/* Keyboard users get a clear focus ring; mouse users don't see it */
:focus-visible {
  outline: 3px solid var(--accent-500);
  outline-offset: 3px;
  border-radius: 4px;
}

/* Jump link for screen-reader / keyboard users, visible only on focus */
.skip-link {
  position: absolute;
  left: 50%;
  top: -60px;
  transform: translateX(-50%);
  z-index: 2000;
  background: var(--brand);
  color: #fff;
  padding: 0.7rem 1.4rem;
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  text-decoration: none;
  font-weight: 600;
  transition: top var(--dur-fast) var(--ease-out);
}
.skip-link:focus { top: 0; color: #fff; }

.visually-hidden-focusable:not(:focus):not(:focus-within) {
  position: absolute !important;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
}

/* Respect the OS "reduce motion" setting, required for accessible sites.
   Users with vestibular disorders can be made ill by large motion. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .reveal, .rise-in { opacity: 1 !important; transform: none !important; }
  .hero-orb { animation: none; }

  /* The logo strip is nearly three screens wide inside a clipped box, so simply
     stopping it would leave most of the brands unreachable. Stop it and hand
     the scrolling to the reader instead. The second copy of the list exists
     only to make the loop seamless, so it goes: without the animation it is
     just every logo printed twice. */
  .marquee-track { animation: none; }
  .marquee {
    overflow-x: auto;
    /* A full-weight scrollbar under a row of logos reads as a bug. Thin still
       shows there is more to see, which is the whole point of having it. */
    scrollbar-width: thin;
    scrollbar-color: var(--border-soft) transparent;
  }
  .marquee-item[aria-hidden="true"] { display: none; }
}


/* ---------------------------------------------- responsive overrides */
@media (max-width: 767.98px) {
  :root { --nav-height: 66px; }
  /* Back to the smaller mark on phones. At 36px the brand grows wide enough to
     push the hamburger onto a second row, which doubles the navbar height and
     breaks the --nav-height above that the hero's top padding is measured from.
     Enlarging the wordmark for the desktop navbar brought that fault straight
     back - 346px of brand on a 375px screen - so the type comes down here too.
     The mark is still the largest thing in the bar, which is the point; it just
     is not competing with the hamburger for the same row. */
  .site-nav .brand-mark { height: 30px; }
  .brand-wordmark .name { font-size: 1.02rem; }
  .brand-wordmark .tag { font-size: 0.58rem; letter-spacing: 0.16em; text-indent: 0.16em; }
  .hero { min-height: auto; padding-top: calc(var(--nav-height) + 3rem); }
  .hero-orb--3 { display: none; }
  /* Smaller and tighter to the corner on a phone, still stacked in the same
     order: WhatsApp at thumb height, call above it, back-to-top above that. */
  .whatsapp-fab { width: 50px; height: 50px; font-size: 1.5rem; right: 14px; bottom: 14px; }
  .call-fab { width: 50px; height: 50px; font-size: 1.3rem; right: 14px; bottom: 74px; }
  .to-top { right: 14px; bottom: 134px; }
  .filter-bar { justify-content: flex-start; }
}

/* ---------------------------- the lens, the site's signature visual */
/*
   An iris built entirely from nested elements and CSS gradients: no image, no
   SVG file, no JavaScript. It scales to any size without loss, weighs nothing,
   recolours itself from the design tokens, and every layer can be animated
   independently.
   Layer order, outside in:
   ring--outer   dashed calibration ring, rotating slowly
   ring--mid     solid inner ring
   striations    the radial fibres of an iris (repeating-conic-gradient)
   iris          the coloured body, lit from the upper left
   pupil         the dark centre
   glint         specular highlight, as on a real wet cornea
   sweep         a slow refraction pass across the surface
*/
/* -------------------------------------------------- hero eye
   A macro photograph of an iris, masked into the hero rather than framed in a
   box. Two drawn versions were tried before this and both read as clip art
   beside real product photography.

   Nothing here is a circle. An earlier version clipped the photograph into a
   disc with a dashed ring around it, which framed it like a portrait medallion;
   the image now runs past its column and dissolves on every side, so it reads
   as part of the hero's background rather than as an object sitting on it.
   The hero itself has overflow:hidden, which is what makes the bleed safe.
*/
/* Above the hero's own background layers, below the copy */
.hero-copy { position: relative; z-index: 2; }

.eye-visual {
  position: relative;
  z-index: 1;
  /* The 46vw term is the one that matters between 992px and about 1500px. The
     percentage is of the column, which is only half the page, so on a narrower
     desktop 132% would push the far side of the eye off the screen. Tying the
     cap to the viewport keeps the whole eye in frame at every width. */
  width: min(132%, 700px, 46vw);
  aspect-ratio: 1;
  /* Sits over on the right, where the hero has the room, but stops short of the
     viewport edge so the whole eye is in frame. Only a small pull left, enough
     that it grazes the headline rather than being walled off from it; the copy
     is lifted above it by .hero-copy. */
  margin: -8% -2% -8% -6%;
  animation: eyeFloat 8s var(--ease-in-out) infinite alternate;
}
@media (max-width: 991.98px) {
  /* Stacked layout: nothing to sit behind, so it centres and stays in bounds */
  .eye-visual {
    width: min(100%, 470px);
    margin: 0 auto;
  }
}
@keyframes eyeFloat {
  from { transform: translateY(-16px); }
  to   { transform: translateY(16px); }
}

/* Light spilling out from behind the photograph. Spread much wider than the
   image so the glow is the outermost thing, and there is no point at which the
   picture visibly stops. */
.eye-halo {
  position: absolute;
  inset: -34%;
  background: radial-gradient(ellipse at 48% 46%,
    rgba(0, 163, 191, 0.30) 0%,
    rgba(0, 163, 191, 0.14) 38%,
    rgba(44, 85, 196, 0.10) 60%,
    transparent 80%);
  filter: blur(18px);
}

.eye-photo {
  position: absolute;
  inset: 0;
  display: block;
  /* No border-radius and no clip. The mask alone decides where the picture
     ends.

     The radii must stay under 50%. A radial-gradient's size is measured from
     the centre, so anything above 50% puts the transparent stop outside the
     box: the fade never completes, the image is still part-opaque when the
     element ends, and you get the straight edge this is meant to avoid. At 44%
     it is fully gone with room to spare on every side. */
  -webkit-mask-image: radial-gradient(ellipse 47% 47% at 50% 48%,
    #000 0%, #000 34%, rgba(0,0,0,0.76) 58%, rgba(0,0,0,0.26) 78%, transparent 96%);
  mask-image: radial-gradient(ellipse 47% 47% at 50% 48%,
    #000 0%, #000 34%, rgba(0,0,0,0.76) 58%, rgba(0,0,0,0.26) 78%, transparent 96%);
  /* Required now that overflow:hidden is gone. mask-repeat defaults to repeat,
     so without this the gradient would tile outside the box and the zoomed
     image would reappear as ghost ellipses around the edges. */
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}
.eye-photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* A push in that also pans a little, so the movement has a direction instead
     of only breathing in and out. Large enough to notice, slow enough that it
     never competes with the headline beside it. */
  animation: eyeDrift 12s var(--ease-in-out) infinite alternate;
}
@keyframes eyeDrift {
  /* Starts at 1, not below it: the photograph is framed so the whole eye is in
     shot, and zooming further would push the lids out of frame. */
  from { transform: scale(1) translate(0, 0); }
  to   { transform: scale(1.05) translate(-1%, 0.8%); }
}
/* Cools the photograph towards the navy palette and darkens the rim, so it
   sits in the hero's colour rather than beside it. */
.eye-photo::after {
  content: "";
  position: absolute;
  z-index: 1;
  inset: 0;
  /* Both layers end transparent. An earlier version let the rim gradient finish
     on solid navy, which then filled the corners of the box with a rectangle of
     tint - the second cause of the visible edge. */
  background:
    radial-gradient(ellipse 48% 48% at 50% 48%,
      transparent 38%, rgba(10, 31, 92, 0.38) 76%, transparent 96%),
    linear-gradient(160deg, rgba(0, 163, 191, 0.14), rgba(10, 31, 92, 0.22));
}

/* A band of light crossing the eye, as if it were catching a moving source.
   This is the movement you actually notice; the drift underneath is what stops
   the picture feeling frozen between sweeps.

   The gradient moves INSIDE a stationary box rather than the element being
   translated: the layer is masked to the same soft ellipse as the photograph,
   so moving it would drag that fade across the hero instead of sweeping light
   across the iris. */
.eye-photo::before {
  content: "";
  position: absolute;
  z-index: 2;
  inset: 0;
  background: linear-gradient(102deg,
    transparent 40%,
    rgba(255, 255, 255, 0.30) 50%,
    transparent 60%);
  background-size: 260% 100%;
  background-position: 150% 0;
  animation: eyeSweep 9s var(--ease-in-out) infinite;
  opacity: 0;
}
@keyframes eyeSweep {
  0%   { background-position: 150% 0; opacity: 0; }
  15%  { opacity: 0.9; }
  55%  { opacity: 0.9; }
  72%  { background-position: -60% 0; opacity: 0; }
  100% { background-position: -60% 0; opacity: 0; }
}


/* -------------------------------------------------- brand photo
   One photograph inside a brand's story, to break up what is otherwise four
   or five paragraphs of unbroken text. Optional: a brand without one simply
   has no figure.
*/
.brand-photo {
  margin: 0;
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid var(--border-soft);
  background: var(--surface);
  box-shadow: var(--shadow-xs);
}
.brand-photo img {
  display: block;
  width: 100%;
  height: auto;
}
.brand-photo figcaption {
  padding: 0.7rem 1rem;
  font-size: 0.86rem;
  color: var(--text-muted);
  border-top: 1px solid var(--border-soft);
}


/* -------------------------------------------------- hero stats strip */
/*
   Four figures across the base of the hero, divided by hairlines.
*/
.stats-strip {
  margin-top: clamp(2.5rem, 6vw, 4.5rem);
  padding-top: clamp(1.5rem, 3vw, 2.25rem);
  border-top: 1px solid rgba(255, 255, 255, 0.14);
}
.stats-strip .stat-cell {
  text-align: center;
  padding: 0.5rem 0.75rem;
  position: relative;
}
/* Hairline between cells, but never before the first one in a row */
@media (min-width: 576px) {
  .stats-strip .stat-cell::before {
    content: "";
    position: absolute;
    left: 0; top: 18%;
    height: 64%;
    width: 1px;
    background: linear-gradient(rgba(255,255,255,0), rgba(255,255,255,0.20), rgba(255,255,255,0));
  }
  .stats-strip [class*="col"]:nth-child(odd) .stat-cell::before { content: none; }
}
@media (min-width: 992px) {
  .stats-strip [class*="col"]:nth-child(odd) .stat-cell::before { content: ""; }
  .stats-strip [class*="col"]:first-child .stat-cell::before { content: none; }
}
.stats-strip .stat-figure {
  display: block;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(2rem, 4.4vw, 3rem);
  line-height: 1;
  color: #fff;
  letter-spacing: -0.03em;
}
.stats-strip .stat-figure .suffix { color: var(--accent-400); }
.stats-strip .stat-caption {
  display: block;
  margin-top: 0.6rem;
  font-size: 0.72rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.55);
}


/* -------------------------------------------------- cursor spotlight */
/*
   A soft radial light follows the pointer across a card. The JS only writes
   two custom properties (--mx / --my); all the rendering is CSS. That keeps
   the mousemove handler to two style writes and no layout reads.
*/
.spotlight { position: relative; }
.spotlight::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-out);
  background: radial-gradient(
    300px circle at var(--mx, 50%) var(--my, 50%),
    rgba(0, 163, 191, 0.16),
    transparent 62%
  );
}
.spotlight:hover::after { opacity: 1; }


/* ---------------------------------------------------- section rhythm */
/* A soft coloured wash behind a light section, so the page is not a flat
   slab of grey between the dark bands. */
.section-glow {
  position: relative;
  isolation: isolate;
}
.section-glow::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(760px 420px at 82% 8%, rgba(0, 163, 191, 0.09), transparent 62%),
    radial-gradient(680px 380px at 6% 92%, rgba(44, 85, 196, 0.08), transparent 60%);
  pointer-events: none;
}

/* Curved base on the dark hero, so the next section rises into it rather than
   meeting it at a hard line. */
.hero, .page-banner {
  border-bottom-left-radius: clamp(20px, 4vw, 44px);
  border-bottom-right-radius: clamp(20px, 4vw, 44px);
}

/* Logos show in FULL COLOUR.
   The usual trick here is greyscale-until-hover, to stop a logo strip reading
   as ten competing palettes. That works when every logo is bold and
   high-contrast, it was fine against the placeholder wordmarks. Against the
   real artwork it failed: AccuLens is pale blue and the MiBo mark sits over a
   faint watermark, and greyscaling both at 62% opacity made them almost
   invisible. On a page whose entire job is to establish who Vikman represents,
   legibility of the partner marks beats visual tidiness. */
.marquee-item img {
  opacity: 0.92;
  transition: opacity var(--dur-base) var(--ease-out);
}
.marquee-item:hover img { opacity: 1; }

/* Word-by-word headline entrance. main.js wraps each word in a span. */
.split-words .word {
  display: inline-block;
  opacity: 0;
  transform: translateY(0.55em);
  animation: wordRise 720ms var(--ease-out) forwards;
  animation-delay: calc(var(--w, 0) * 55ms + 120ms);
}
@keyframes wordRise {
  to { opacity: 1; transform: none; }
}


/* Reduced-motion additions for everything introduced above */
@media (prefers-reduced-motion: reduce) {
  /* The wheel's keyframes end on opacity 0, so the blanket animation-disabling
     rule above would leave it invisible. Park it visible instead. */
  .scroll-cue-mouse::before { animation: none !important; opacity: 1; }

  .eye-visual,
  .eye-photo img,
  .eye-halo { animation: none !important; }
  /* The sweep is the one piece that would still read as motion, so it goes
     entirely rather than freezing mid-pass across the iris. */
  .eye-photo::before { display: none; }
  .split-words .word { opacity: 1; transform: none; animation: none; }
  .spotlight::after { display: none; }
}


/* Print: strip chrome so a doctor can print a partner sheet cleanly */
@media print {
  .site-nav, .site-footer, .whatsapp-fab, .call-fab, .to-top, .scroll-progress { display: none !important; }
  body { background: #fff; }
  .reveal { opacity: 1 !important; transform: none !important; }
  a[href]::after { content: " (" attr(href) ")"; font-size: 0.75em; color: #666; }
}
