/* Lato 900 (Black) was dropped from this import along with the last
   font-weight:900 rule — headings now top out at 700. One fewer font file to
   download, and nothing references the Black cut any more. */
@import url('https://fonts.googleapis.com/css2?family=Lato:wght@400;700&family=Source+Sans+3:wght@400;500;600;700&display=swap');

:root {
  --page-bg: #FBF8F3;
  --card-bright: #FFFDFA;
  --card-soft: #F3EDE3;
  /* One perceptible step below --card-soft, for the hovered/pressed state of
     a control resting on --card-bright or --card-soft. This was a duplicate
     of --card-soft, which silently made all seven ":hover { background:
     var(--card-deep) }" rules into no-ops. 1.30:1 against --card-bright —
     enough to register as a state change, not enough to read as a different
     component. --text-1 on it is 14.2:1. */
  --card-deep: #E9DFCD;
  --photo-bg: #FFFFFF;
  --dark: #16181B;
  --border: #DED2BC;
  /* The hairline for dark surfaces, where --border would glow. Was used
     seven times as a bare #2A2E31 literal before it had a name. */
  --border-dark: #2A2E31;
  /* Used only where the boundary is the sole thing identifying an interactive
     control — inputs, chips, outlined buttons. Measured 3.06:1 on
     --card-soft, 3.37:1 on --page-bg, 3.51:1 on --card-bright, which is what
     WCAG 1.4.11 requires and what --border (1.47:1) never met. --border stays
     pale for structural hairlines: a card edge is decorative, not a control
     affordance, and darkening those would undo the softening. */
  --border-control: #94866D;
  --text-1: #101215;
  --text-2: #43484E;
  --text-3: #54595F;
  --orange-fill: #F58138;
  --orange-border: #DA6A20;
  --orange-link: #8F420C;
  /* A burnt brand orange, picked by luminance rather than by eye: at L≈0.168
     it clears 3:1 against every surface a ring can land on here — all three
     cream tints, both dark bands, the near-black active-chip fill, and the
     tinted panel on the partner hero. The old --orange-border ring measured
     2.97:1 on --card-soft, which is exactly where the filter chips sit, so it
     was failing in the one place it was used most. */
  --focus-ring: #B4581A;
  --header-h: 70px;
  --mono: ui-monospace, Menlo, Consolas, monospace;
  --display: 'Lato', Helvetica, Arial, sans-serif;

  /* Radius scale, ~1.6x steps. Sized against the components that exist here:
     4px for 18-44px micro-chrome (type badges, thumbnails), 6px for the
     42-48px control row, 10px for anything that is a surface with content in
     it, 16px for the two full-screen overlays. Everything was a flat 3px,
     which at 19px body text reads as a square corner someone forgot to
     finish. Nested radii step down one level so the inner curve is always
     tighter than the outer — see .product-photo inside .product-card. */
  --r-xs: 4px;
  --r-sm: 6px;
  --r-md: 10px;
  --r-lg: 16px;
  --r-pill: 999px;

  /* Elevation. The shadow colour is a warm brown-black, not neutral grey — a
     grey shadow on a cream page (#FBF8F3) reads as dirt rather than depth.
     Two layers each: a tight contact shadow that defines the edge, and a wide
     diffuse one that does the lifting. Alphas stay low because the page is
     light. Nothing on a dark surface gets one; a shadow on #16181B is
     invisible and just costs a paint. */
  --shadow-1: 0 1px 2px rgba(64,48,24,.05), 0 2px 6px rgba(64,48,24,.05);
  --shadow-2: 0 2px 4px rgba(64,48,24,.06), 0 8px 20px rgba(64,48,24,.09);
  --shadow-3: 0 12px 28px rgba(64,48,24,.12), 0 2px 8px rgba(64,48,24,.08);
  --shadow-4: 0 24px 60px rgba(16,18,21,.34), 0 4px 12px rgba(16,18,21,.18);

  /* Motion. Two durations: --dur-fast for colour changes, quick enough that a
     pointer sweeping a chip row doesn't leave a trail of half-lit chips;
     --dur for shape, shadow and position. The ease is a decelerate curve — it
     leaves immediately and settles slowly, which is what makes a state change
     feel soft instead of mechanical. */
  --dur-fast: 120ms;
  --dur: 200ms;
  --ease: cubic-bezier(.2, 0, 0, 1);
}

* { box-sizing: border-box; }

/* Kept !important deliberately: some elements this hides also match
   higher-specificity rules (e.g. [data-view-mode="cards"] .view-cards),
   which source order alone can't beat — [hidden] needs to always win. */
[hidden] { display: none !important; }

/* Keeps a heading in the accessibility tree / document outline without
   showing it — used where the visual design has no room for a page's H1
   (e.g. partners.html, whose "hero" is the tab row itself) but the page
   still needs exactly one for screen readers and document structure. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
  overflow-x: clip;
}

body {
  margin: 0;
  background: var(--page-bg);
  color: var(--text-1);
  font-family: 'Source Sans 3', 'Segoe UI', Arial, sans-serif;
  font-size: 19px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-wrap: pretty;
}

h1, h2, h3 { font-family: var(--display); margin: 0; letter-spacing: -0.015em; }
/* The negative tracking above is optical compensation for large display type.
   Below ~24px it stops compensating and starts cramping, so the small
   headings opt back out of it. */
.h3, .modal-section-title, .empty-state-title, .external-title { letter-spacing: 0; }
p { margin: 0; }

a { color: var(--orange-link); text-decoration: none; }
a:hover { color: var(--orange-border); text-decoration: underline; }

/* Offset is >= the ring's own width so the ring is bounded by page background
   on both sides rather than sitting flush against the component's fill —
   that's what keeps it legible on a filled .btn-primary, whose orange is too
   close to the ring colour to contrast against directly. Never transition
   this: a focus ring that fades in reads as lag to a keyboard user. */
:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

/* .table sets overflow-x:auto, which per spec computes overflow-y to auto as
   well, so the first and last row's outward ring is clipped. The rows have
   16px of padding, so an inset ring has room to sit inside instead. */
.t-row:focus-visible { outline-offset: -3px; }

/* .filter-panel-inner has overflow:hidden (it clips the collapse animation)
   and zero padding, with the search input, Clear button and every chip flush
   against its edges — so an outward ring on any of them was being cut off
   entirely. Inset here for the same reason as .t-row. */
.filter-panel-inner :focus-visible { outline-offset: -3px; }

button { font-family: inherit; }
input, textarea, select { font-family: inherit; font-size: 18px; }

img { max-width: 100%; }

/* ---------- motion ----------
   Every hover/active colour change on the site, declared once here rather
   than as ~16 scattered per-component transitions. Properties are listed
   explicitly rather than using `all`, which would otherwise catch
   .filter-panel's grid-template-rows and fight its own transition. */
a, .btn, .btn-ghost, .chip, .page-btn, .clear-btn, .toggle-btn,
.icon-btn, .link-name, .nav-link, .footer-link,
.modal-close,
.doc-actions a, .doc-row, .t-row, .rail-logo {
  transition: background-color var(--dur-fast) var(--ease),
              border-color     var(--dur-fast) var(--ease),
              color            var(--dur-fast) var(--ease),
              opacity          var(--dur-fast) var(--ease);
}

/* Cards move between two elevation levels, which is a bigger visual step than
   a colour swap — so they get the slower duration. */
.product-card, .row-card, .partner-card, .site-header {
  transition: border-color var(--dur) var(--ease),
              box-shadow   var(--dur) var(--ease);
}

@keyframes sheet-up {
  from { transform: translateY(100%); }
  to { transform: translateY(0); }
}

/* ---------- layout helpers ---------- */

.wrap {
  max-width: 1240px;
  margin: 0 auto;
  padding-left: clamp(20px, 4vw, 32px);
  padding-right: clamp(20px, 4vw, 32px);
}

.section-pad { padding-top: clamp(36px, 4.5vw, 64px); padding-bottom: clamp(36px, 4.5vw, 64px); }
.section-pad-lg { padding-top: clamp(44px, 5vw, 72px); padding-bottom: clamp(44px, 5vw, 72px); }
.section-pad-sm { padding-top: clamp(28px, 3.5vw, 48px); padding-bottom: clamp(28px, 3.5vw, 48px); }

.bg-page { background: var(--page-bg); }
.bg-soft { background: var(--card-soft); }

.mono { font-family: var(--mono); }
.text-2 { color: var(--text-2); }

/* text scale — on light backgrounds, then on dark */
.t-xs { font-size: 15px; color: var(--text-3); }
.t-sm { font-size: 16px; color: var(--text-3); }
.t-body { font-size: 18px; color: var(--text-2); }
.lead { font-size: clamp(19px, 1.3vw, 21px); color: var(--text-2); }
.t-dim { font-size: 16px; color: #A9AFB4; }

/* headings */
.h3 { font-weight: 700; font-size: 20px; margin-bottom: 8px; }
.h2-sm { font-size: clamp(24px, 2.2vw, 30px); }

/* spacing + rules */
.mt-sm { margin-top: 20px; }
.mt-md { margin-top: 24px; }
.mt-lg { margin-top: 28px; }
.rule-top { border-top: 1px solid var(--border); padding-top: 16px; }
/* A boxed card, not just a coloured .rule-top — Emtrons being the sole
   agency Omega Electric holds, not just one of several equal facts, is the
   one thing on both the home page teaser and about.html's fuller "What we
   offer" grid the client specifically wants to stand apart from
   Warranty/Delivery/Customer service, not just tinted a different colour
   while still sitting as an equal-weight item in the same row. Same
   background-plus-left-border treatment .partner-card.is-lead already uses
   for the identical job elsewhere on the site (the featured item among
   otherwise-equal cards), reused rather than a new "featured" language
   invented just for this. */
.offer-featured {
  background: var(--card-soft);
  border: 1px solid var(--orange-border);
  border-left: 4px solid var(--orange-fill);
  border-radius: var(--r-md);
  padding: clamp(20px, 2.4vw, 28px);
}
.offer-featured .h3 { color: var(--orange-link); }
.col { display: flex; flex-direction: column; }
.row { display: flex; align-items: center; }
.gap-sm { gap: 8px; }
.max-prose { max-width: 62ch; }

.note-text {
  font-family: var(--mono);
  font-size: 16px;
  color: var(--text-3);
}

.stripes {
  background-image: repeating-linear-gradient(135deg, var(--card-bright) 0 11px, var(--card-soft) 11px 22px);
}

/* ---------- buttons ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border-radius: var(--r-sm);
  border: 1px solid transparent;
  font-size: 18px;
  font-weight: 600;
  padding: 13px 22px;
  cursor: pointer;
  text-decoration: none;
  line-height: 1.2;
}
.btn-sm { padding: 9px 16px; font-size: 17px; }

.btn-primary { background: var(--orange-fill); color: #14161A; border-color: var(--orange-border); }
.btn-primary:hover { background: var(--orange-border); color: #14161A; text-decoration: none; }

.btn-secondary { background: var(--card-bright); color: var(--text-1); border-color: var(--border-control); }
.btn-secondary:hover { background: var(--card-deep); color: var(--text-1); text-decoration: none; }

.btn-dark { background: #101214; color: #FFFFFF; border-color: #101214; }
.btn-dark:hover { background: #2A2E31; color: #FFFFFF; text-decoration: none; }

.btn-ghost { background: none; border: 0; padding: 4px 0; font-weight: 600; color: var(--orange-link); cursor: pointer; }
.btn-ghost:hover { color: var(--orange-fill); text-decoration: none; }

/* ---------- logo (animated, asset-based — see js/logo-anim.js) ---------- */

/* Built from a Claude Design motion study (the "pendulum" variant) and
   dropped in close to as-authored: omega-logo.png and omega-o-mark.png
   painted as background-images below, cropped with percentage insets
   measured directly off that artwork rather than redrawn shapes — the
   mark's own geometry, typography and colour are untouched.

   Every part below is positioned as a percentage of .logo's own box, so
   --logo-h is the only thing that ever needs to change for size; the whole
   animation scales with it. aspect-ratio keeps that box's footprint fixed
   through every frame — nothing about its size or position, or the header
   around it, ever changes, which is what keeps the animation scoped to the
   logo instead of reflowing anything else. */
.logo {
  --logo-bar-bg: var(--card-bright);
  --logo-h: clamp(34px, 3.6vw, 44px);
  position: relative;
  display: block;
  height: var(--logo-h);
  aspect-ratio: 1795 / 485;
}
.logo-word { position: absolute; left: 21.5%; overflow: hidden; }
.logo-word-mega { top: 0; width: 78.5%; height: 76.5%; }
.logo-word-electric { top: 76.5%; width: 78.5%; height: 23.5%; }
/* One full copy of the lockup, cropped down to just its own row via a
   negative left offset plus an oversized width/height (127.39%/130.73% for
   "mega", the more extreme 425.4% tall / -325.4% top for "ELECTRIC" since
   that row is a much thinner slice of the same full-height image) — the
   same technique the old two-<img> logo used, just against one shared rule
   instead of two separate clip-paths. */
.logo-art {
  position: absolute; left: -27.39%; width: 127.39%;
  background-image: url('../assets/omega-logo.png');
  background-size: 100% 100%; background-repeat: no-repeat;
}
.logo-word-mega .logo-art { top: 0; height: 130.73%; }
.logo-word-electric .logo-art { top: -325.4%; height: 425.4%; }
/* The reveal: .logo-cover is opaque and starts flush over the art (hiding
   it completely), .logo-front is a soft light-coloured edge riding just
   ahead of it. Both slide right together (see js/logo-anim.js) so the word
   reads as uncovered by a passing edge of light, not as an independent
   fade-in — --logo-bar-bg has to match whatever this sits on (the header's
   own background) or the cover reads as a mismatched patch instead of
   invisibly blending with the bar behind it. */
.logo-reveal { position: absolute; inset: 0; transform: translateX(0); }
.logo-front {
  position: absolute; left: -11%; top: -20%; width: 11%; height: 140%; opacity: 0;
  background: linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,.5) 70%, rgba(255,255,255,.95) 100%);
}
.logo-cover { position: absolute; left: 0; top: -20%; width: 110%; height: 140%; background: var(--logo-bar-bg); }
/* transform-origin: 50% 96% is the pendulum's pivot — the bulb's own screw
   base near the bottom of this box, not its centre, so the swing (see
   js/logo-anim.js's BULB keyframes) reads as a lamp hanging from a cord
   rather than the whole mark spinning in place. */
.logo-bulb { position: absolute; left: 0; top: 0; width: 20.17%; height: 100%; transform-origin: 50% 96%; }
.logo-mark {
  position: absolute; inset: 0;
  background-image: url('../assets/omega-o-mark.png');
  background-size: 100% 100%; background-repeat: no-repeat;
}

/* Both words already fully revealed with no JS at all — belt and braces
   alongside initLogoAnimation's own reduced-motion check in logo-anim.js,
   in case anything ever renders this markup without that script running. */
@media (prefers-reduced-motion: reduce) {
  .logo-reveal { transform: translateX(118%); }
}

/* The mobile drawer sits on a dark overlay (see .navmenu), not this site's
   usual light surfaces, so the logo needs its own dark-background palette
   here rather than the light-bar one it uses everywhere else — deliberately
   matching .footer-logo img's own brightness(0) invert(1) below, the same
   flat white silhouette treatment this mark already gets on the footer's
   dark surface, rather than a separate dark-mode look invented just for
   here.

   Two independent, narrower pieces, not one blanket filter across the
   whole .logo (an earlier version of this rule did that, and it broke
   .logo-cover along with everything else — the cover meant to HIDE
   "mega"/"ELECTRIC" got inverted right along with the text underneath it,
   so covered and revealed looked identical and the reveal sweep became
   invisible):
   1. --logo-bar-bg switches to the drawer's own dark tone, so .logo-cover
      (a plain background colour, not a filter) blends with the dark bar
      directly — same mechanism this variable already exists for, just a
      different value.
   2. .logo-art (the "mega"/"ELECTRIC" wordmark image, dark ink on
      transparent) gets the footer's exact filter — a static rule on a
      plain, never-animated element, so it can't conflict with or corrupt
      anything .logo-mark's JS-driven filter/opacity is doing on a
      different element entirely.
   .logo-mark itself needs no CSS override here: js/logo-anim.js's
   playLogoAnimation() already checks body.nav-open at play time and
   switches it to this exact same brightness(0) invert(1) silhouette,
   animating opacity instead of colour for the "dim, then lights up" beat
   — brightness(0) permanently crushes away any colour or brightness
   variation, which is what makes it read as a flat silhouette in the
   first place, but also means there's no room left in the filter itself
   for that beat to live. */
body.nav-open .logo { --logo-bar-bg: var(--dark); }
body.nav-open .logo-art { filter: brightness(0) invert(1); }

/* Rescaled for the cropped logo file too — see the .logo comment above. */
.footer-logo { display: block; height: clamp(28px, 3.4vw, 38px); }
.footer-logo img { display: block; height: 100%; width: auto; filter: brightness(0) invert(1); }

/* ---------- utility bar ---------- */

.topbar { background: var(--dark); color: #C7CBCE; }
.topbar-inner {
  max-width: 1240px;
  margin: 0 auto;
  padding: 7px clamp(20px, 4vw, 32px);
  display: flex;
  gap: clamp(12px, 2.5vw, 22px);
  flex-wrap: wrap;
  align-items: center;
  font-size: 16px;
}
.topbar-inner a { color: var(--orange-fill); }
.topbar-inner a:hover { color: #FFA164; text-decoration: underline; }

@media (max-width: 700px) {
  .topbar-addr { display: none; }
  .topbar-inner { gap: 8px; }
}

/* ---------- header ---------- */

/* Always rendered in its final appearance — no lit/unlit state any more.
   The header used to start dark and "sunrise" into this on page load,
   which read as the logo's own light source turning the whole bar on; that
   entrance now lives entirely inside the logo's SVG (js/logo-anim.js)
   instead, so the navbar itself — this background, the nav links, the
   icons — never animates or changes appearance at all. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--card-bright);
  border-bottom: 1px solid var(--border);
  transition: box-shadow var(--dur) var(--ease);
}
.header-inner { position: relative; }
.header-row {
  max-width: 1240px;
  margin: 0 auto;
  padding: 14px clamp(20px, 4vw, 32px);
  display: flex;
  align-items: center;
  gap: clamp(16px, 3vw, 40px);
}
body.is-scrolled .header-row { padding-top: 8px; padding-bottom: 8px; }
/* Lifts the header off the page once it stops being flush with the top, so
   content scrolling underneath reads as passing behind it rather than
   colliding with it. Safe to hang off .is-scrolled: box-shadow doesn't affect
   layout, so measureHeader() in chrome.js still reads the right height.
   Deliberately NOT a padding/height transition — that function measures
   synchronously right after the class toggles and would catch a mid-flight
   value. */
body.is-scrolled .site-header { box-shadow: var(--shadow-1); }

.nav {
  display: flex;
  gap: clamp(14px, 2vw, 28px);
  align-items: center;
  margin-left: auto;
  flex-wrap: wrap;
}

.nav-link {
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  padding: 6px 2px;
  font-size: 18px;
  color: var(--text-2);
  cursor: pointer;
  text-decoration: none;
}
/* text-decoration:none is needed explicitly, not inherited from .nav-link's
   own base rule above — the generic a:hover (near the top of this file)
   sets text-decoration:underline, and for that one property its 1 class +
   1 type beats .nav-link's bare 1 class, so without repeating it here the
   underline would win on hover despite .nav-link's own resting state
   already being none. var(--orange-fill), not --orange-link, to match
   .navmenu-link:hover below — the same brand orange the buttons and the
   logo's O use, not the darker tone tuned for body-copy links. */
.nav-link:hover { color: var(--orange-fill); text-decoration: none; }
.nav-link.is-active { color: var(--text-1); font-weight: 700; border-bottom-color: var(--orange-fill); }

.icon-btn {
  background: none;
  border: 0;
  padding: 0;
  width: 44px; height: 44px;
  display: grid;
  place-items: center;
  cursor: pointer;
}
.icon-btn:hover { opacity: .7; }
.icon-btn img { display: block; width: 26px; height: 26px; object-fit: contain; }

.nav-search-mobile {
  display: none;
  margin-left: auto;
  width: 48px; height: 48px;
  place-items: center;
  border: 1px solid var(--border-control);
  border-radius: var(--r-sm);
  background: var(--card-bright);
  cursor: pointer;
}
.nav-search-mobile img { display: block; width: 24px; height: 24px; object-fit: contain; }

/* The burger doubles as the close button, so it sits above the menu overlay
   (see body.nav-open .site-header) and its three bars animate into a cross
   rather than being swapped for an ×. Rotating the whole button 135deg while
   the bars converge is what makes the two states feel like one object moving,
   instead of two icons cross-fading. */
.nav-toggle {
  display: none;
  position: relative;
  width: 48px; height: 48px;
  border: 1px solid var(--border-control);
  border-radius: var(--r-sm);
  background: var(--card-bright);
  cursor: pointer;
  align-items: center;
  justify-content: center;
  transition: background-color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
/* The rotation lives on the bar group, not the button. Rotating the button
   itself would swing its rounded-square border round into a diamond — the
   original prototype got away with it only because its button was a circle,
   where rotation is invisible. This keeps the border square and matching the
   search button next to it, while the bars still swing 135deg. */
.nav-toggle-bars {
  display: block;
  position: relative;
  width: 24px;
  height: 3px;
  transition: rotate var(--dur-fast) linear;
}
.nav-toggle .bar {
  position: absolute;
  left: 0;
  top: 0;
  width: 24px;
  height: 3px;
  border-radius: var(--r-pill);
  background: var(--text-1);
  /* Each property gets its own timing: the bars slide together slowly enough
     to read, but rotate fast and linearly so the cross snaps into alignment. */
  transition: translate var(--dur) var(--ease),
              rotate var(--dur-fast) linear,
              opacity var(--dur) var(--ease),
              background-color var(--dur-fast) var(--ease);
}
.nav-toggle .bar.top { translate: 0 -7px; }
.nav-toggle .bar.bottom { translate: 0 7px; }

body.nav-open .nav-toggle { background: transparent; border-color: rgba(255,255,255,.28); }
body.nav-open .nav-toggle-bars { rotate: 135deg; }
body.nav-open .nav-toggle .bar { background: #FFFFFF; }
body.nav-open .nav-toggle .bar.top { translate: 0 0; }
body.nav-open .nav-toggle .bar.bottom { translate: 0 0; rotate: -90deg; }
body.nav-open .nav-toggle .bar.middle { rotate: -90deg; opacity: 0; }

/* While the menu is open the header rides above the overlay so the burger
   stays clickable and the logo stays visible — but it drops its own fill and
   rule so it reads as part of the dark menu rather than a cream bar stuck on
   top of it. The logo is knocked out to white by the same filter the footer
   already uses.

   position:fixed, not the header's usual sticky: lockScroll (see util.js)
   pins <body> itself to position:fixed at a negative top offset to freeze
   scrolling, which took the header's sticky positioning with it — sticky
   only holds an element at the top of its scrolling ancestor, and once
   body itself stops being that ancestor's scrolled content, the header
   just sat wherever body's own offset put it, scrolled up out of the
   viewport by however far the page had been scrolled before the drawer
   opened. Fixed and pinned to the true viewport edges here sidesteps that
   entirely, independent of whatever body is doing.

   top: var(--header-open-top), not a bare 0 — the topbar above the header
   (see .topbar) is still part of normal flow and doesn't get its own fixed
   treatment, so a hardcoded top:0 snapped the header up to the very
   viewport edge even when the topbar was still visible above it (i.e. the
   page hadn't been scrolled far enough yet for the header's own sticky
   behavior to have reached y:0 on its own) — a visible jump of the header,
   and the burger button under the visitor's thumb, the instant the drawer
   opened. --header-open-top is set by initDrawer's setOpen (chrome.js),
   measured from the header's actual position the moment before it switches
   to fixed, so it lands exactly where it already was instead. */
body.nav-open .site-header {
  position: fixed;
  top: var(--header-open-top, 0px);
  left: 0;
  right: 0;
  z-index: 60;
  background: transparent;
  border-bottom-color: transparent;
  box-shadow: none;
}
body.nav-open .nav-search-mobile {
  background: transparent;
  border-color: rgba(255,255,255,.28);
}
body.nav-open .nav-search-mobile img { filter: brightness(0) invert(1); }

@media (max-width: 900px) {
  .nav { display: none; }
  .nav-toggle { display: flex; }
  .nav-search-mobile { display: grid; }
}

/* Small popup search, opened from the header search icon on any page. It
   slides down from the header and points back at the icon with a css-arrow,
   but only takes the query — actual results always render on products.html. */
.nav-search {
  position: absolute;
  top: 100%;
  right: clamp(20px, 4vw, 32px);
  width: min(320px, calc(100vw - 40px));
  padding: 12px;
  background: var(--card-bright);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-3);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-8px);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease), visibility var(--dur);
  z-index: 45;
}
.nav-search.is-open { opacity: 1; visibility: visible; transform: translateY(0); }
.nav-search::before {
  content: '';
  position: absolute;
  top: -7px;
  left: var(--arrow-x, calc(100% - 38px));
  width: 12px; height: 12px;
  background: var(--card-bright);
  border-left: 1px solid var(--border);
  border-top: 1px solid var(--border);
  transform: rotate(45deg);
}
.nav-search-form { display: flex; gap: 8px; }
.nav-search-form .search-input { flex: 1; min-width: 0; padding: 10px 12px; }

/* ---------- mobile menu (circle reveal) ----------

   The menu doesn't slide in from an edge — it's a filled circle that grows out
   of the burger button until it covers the screen, with the links following in
   a short stagger. --origin-x/y and --open-scale are measured and written by
   initDrawer() in chrome.js, because where the circle starts and how far it
   has to grow both depend on the button's real position at that moment.

   The circle is a separate element rather than a background on .navmenu so the
   links can sit above it and fade in on their own timing while it's still
   expanding — that overlap is what makes it feel like one gesture. */

.navmenu {
  position: fixed;
  inset: 0;
  z-index: 55;
  overflow: hidden;
  display: grid;
  align-content: center;
}

.navmenu-circle {
  position: absolute;
  left: var(--origin-x, 100%);
  top: var(--origin-y, 0px);
  width: 100px;
  height: 100px;
  margin: -50px 0 0 -50px;
  border-radius: 50%;
  background: var(--dark);
  transform: scale(0);
  transition: transform .42s var(--ease);
}
.navmenu.is-open .navmenu-circle { transform: scale(var(--open-scale, 24)); }

.navmenu-items {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 0 clamp(24px, 8vw, 56px);
  padding-bottom: env(safe-area-inset-bottom);
}

/* Every item shares one transition and differs only by --i, so the list reads
   as a single wave rather than six independent animations. The entrance delay
   holds them back until the circle has actually covered the area they appear
   in — start sooner and they fade in over the page behind.

   Delays are declared per property rather than with a single transition-delay:
   the entrance needs the stagger, but hover colour must NOT inherit it, or
   pointing at a link would sit dead for up to a third of a second first. */
.navmenu-items > * {
  opacity: 0;
  translate: 0 16px;
  transition:
    opacity .3s var(--ease) calc(.10s + var(--i, 0) * .05s),
    translate .3s var(--ease) calc(.10s + var(--i, 0) * .05s),
    color var(--dur-fast) var(--ease) 0s,
    background-color var(--dur-fast) var(--ease) 0s,
    border-color var(--dur-fast) var(--ease) 0s;
}
.navmenu.is-open .navmenu-items > * { opacity: 1; translate: 0 0; }
/* Closing drops the stagger entirely and runs short — a staggered exit makes
   dismissal feel slow, which is the opposite of what closing should feel. */
.navmenu:not(.is-open) .navmenu-items > * {
  transition: opacity .12s var(--ease), translate .12s var(--ease);
}

.navmenu-link {
  padding: 12px 0;
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(30px, 9vw, 44px);
  line-height: 1.1;
  letter-spacing: -0.015em;
  color: #FFFFFF;
  text-decoration: none;
}
.navmenu-link:hover { color: var(--orange-fill); text-decoration: none; }

.navmenu-cta,
.navmenu-call {
  align-self: flex-start;
  min-height: 48px;
  display: inline-flex;
  align-items: center;
  border-radius: var(--r-sm);
  padding: 12px 22px;
  font-size: 18px;
  font-weight: 600;
  text-decoration: none;
}
.navmenu-cta {
  margin-top: 26px;
  background: var(--orange-fill);
  color: #14161A;
  border: 1px solid var(--orange-border);
}
.navmenu-cta:hover { background: var(--orange-border); color: #14161A; text-decoration: none; }
.navmenu-call {
  margin-top: 10px;
  background: transparent;
  color: #E7EAEC;
  border: 1px solid var(--border-dark);
}
.navmenu-call:hover { background: rgba(255,255,255,.06); color: #FFFFFF; text-decoration: none; }

/* ---------- partner rail ---------- */

/* position+z-index so this sits above page content that positions itself
   with a stacking level of its own — the about page's bulb mark
   (.about-illustration, z-index:0) reaches up past its own section far
   enough to pass behind here, and without an explicit stacking context
   .rail is a plain unpositioned block that would otherwise lose that
   comparison and let the mark show through on top of it.

   Transparent on every page, not just the home page — on the home page it
   sits directly over the hero photo (see .hero's negative margin-top
   below), and elsewhere it just shows whatever page background sits behind
   it. .about-illustration's mark used to rely on this being an opaque fill
   to stay fully hidden behind the rail; see .about-illustration img's own
   comment for how that's kept from bleeding through now that it isn't. */
.rail { position: relative; z-index: 2; background: transparent; border-bottom: none; }
/* No max-width/centring, unlike .header-row or .topbar-inner above — this
   strip is a continuous logo marquee, not a column of readable content, so
   it runs the full viewport width at every size rather than sitting boxed
   into the site's usual 1240px column with dead space either side of it on
   wide screens. No horizontal padding for the same reason: padding here
   would just be a gap the marquee has to scroll the logos past before
   reaching the true edge, working against "full width" rather than framing
   it. */
.rail-inner {
  padding: 16px 0;
  display: flex;
  align-items: center;
  overflow: hidden;
}
.rail-logo {
  display: inline-flex;
  align-items: center;
  height: 52px;
  flex: none;
}
.rail-logo img { display: block; height: 100%; width: auto; }
/* vol-logo-home.svg (see chrome.js's isHomeVolLogo) has its wordmark inked
   about 18% shorter than its own viewBox — a plain height:100% next to
   sibling logos whose art fills their file edge-to-edge left this one
   reading visibly smaller in the same 52px-tall row. Scaled up around its
   own centre to match their apparent size instead of re-cropping the
   source file's viewBox by hand. */
.rail-logo-vol-home { transform: scale(1.22); transform-origin: center; }
.rail-logo:hover { opacity: .75; }

/* An infinite horizontal marquee, same track on desktop and mobile. The logo
   set is rendered twice back to back (see initChrome) so translating exactly
   -50% lands the second copy where the first began — the jump back to 0% is
   invisible, so the loop reads as continuous. Paused on hover so a visitor
   aiming for a logo gets a stable target instead of a moving one. */
[data-rail-logos] {
  display: flex;
  align-items: center;
  gap: clamp(24px, 3.5vw, 48px);
  width: max-content;
  flex: none;
  will-change: transform;
  animation: rail-marquee 60s linear infinite;
}
.rail:hover [data-rail-logos] { animation-play-state: paused; }

@keyframes rail-marquee {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* Everything motion-related, in one place. Previously this block covered only
   the marquee, leaving scroll-behavior, the modal's sheet-up and every
   transition running for visitors who asked for reduced motion.

   The wildcard uses .01ms rather than `none` so any transitionend/animationend
   listener still fires — nothing depends on one today, but a future one
   shouldn't silently break — and !important so this block doesn't have to be
   kept in sync with every rule above it. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }

  /* The marquee needs its own override, and it must come after the wildcard.
     Collapsing an infinite animation to .01ms doesn't stop it — it runs one
     iteration instantly and parks the track at its `to` state,
     translateX(-50%), which leaves the first copy of the logo set scrolled
     off to the left and a visible gap where it was. Cancel the animation
     outright and reset the transform instead. will-change goes too: with no
     animation there's no reason to keep a compositor layer alive. */
  [data-rail-logos] {
    animation: none !important;
    transform: none !important;
    will-change: auto !important;
  }
}

@media (max-width: 700px) {
  .rail-inner { padding-top: 12px; padding-bottom: 12px; }
  .rail-logo { height: 48px; }
}

/* ---------- hero ---------- */

/* Full-bleed photograph behind the whole opening section, with the copy laid
   over it edge to edge rather than boxed into one column.

   The scrim is a wash in --dark, not the page's cream — this hero runs as a
   deliberate dark panel (night skyline photo, white copy) rather than trying
   to keep the near-black body text legible over it, which is what a cream
   wash was previously for. It's declared as background layers rather than a
   ::before so there's no pseudo-element stacking to reason about: layers
   paint front-to-back, so the gradient sits over the photo, which sits over
   the flat fallback colour (also swapped to --dark, so a slow-loading photo
   doesn't flash cream before the wash paints over it).

   Left-heavy on purpose: the copy is left-aligned and capped at 52ch, so the
   dense end of the wash sits under the text while the right side stays open
   enough to read as a photograph.

   The stops themselves aren't just eyeballed: hero-background.jpg has real
   bright spots in it (sampled directly — lit windows up to L≈0.98 sit
   within the left half of the frame, where the copy renders), and an
   earlier version of this gradient (34% @ .82, dropping to 62% @ .55) let
   .hero-lead's translucent white text fall to as low as ~3.4:1 against one
   of those spots — under WCAG AA's 4.5:1 for normal text. The opacity floor
   through 66% is now high enough (.88 down to .62, worked out against that
   same worst-case bright pixel rather than a plain dark assumption) to keep
   every hero text colour at 5.5:1+ even directly over the brightest window
   in the photo; only past 66% — beyond where .hero-lead/.hero-sub's 52ch
   cap ever reaches — does it fall away toward the open photograph. */
.hero {
  background:
    linear-gradient(96deg,
      rgba(22, 24, 27, .92) 0%,
      rgba(22, 24, 27, .88) 40%,
      rgba(22, 24, 27, .62) 66%,
      rgba(22, 24, 27, .22) 100%),
    url('../assets/hero-background.jpg') center / cover no-repeat,
    var(--dark);
  /* Pulled up by the rail's own measured height (--rail-h, set by
     measureRail() in chrome.js) so this photo extends up behind the now-
     transparent rail instead of stopping at a hard seam where the rail's old
     solid fill used to be. .rail keeps its own z-index:2 and stays
     non-positioned here, so per normal stacking order the rail (a positioned,
     positive-z-index box) still paints over this in-flow section regardless
     of which one is later in the document — the photo shows through the
     transparent rail rather than covering it. */
  margin-top: calc(-1 * var(--rail-h, 0px));
}
.hero-inner {
  /* Extra top padding equal to the same pulled-up amount, so the title still
     lands where it always did — only the photo behind it extends further up,
     not the readable content. */
  padding-top: calc(clamp(48px, 7vw, 92px) + var(--rail-h, 0px));
  padding-bottom: clamp(48px, 7vw, 92px);
}

/* 1.2 rather than 1.15: this is the one heading that reliably wraps to two or
   three lines, so it's where line density actually shows. Uppercase +
   white, matching the photo treatment above — the rest of the site's
   headings stay sentence case, this is a hero-only look. */
.hero-title { max-width: 18ch; font-weight: 700; font-size: clamp(34px, 4vw, 54px); line-height: 1.2; color: #FFFFFF; text-transform: uppercase; }
.hero-lead { max-width: 52ch; margin-top: 22px; font-size: clamp(19px, 1.3vw, 21px); color: rgba(255,255,255,.86); }
.hero-sub { max-width: 52ch; margin-top: 12px; font-size: 17px; color: rgba(255,255,255,.68); }
.hero-sub-link { font-weight: 600; color: var(--orange-fill); text-decoration: underline; }

.hero-actions { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 32px; }
/* .btn-secondary elsewhere is a light card-coloured button meant for a cream
   surface — on this dark photo it would look like a mistake, so it's
   overridden here rather than changed globally. */
.hero .btn-secondary { background: rgba(255,255,255,.08); color: #FFFFFF; border-color: rgba(255,255,255,.4); }
.hero .btn-secondary:hover { background: rgba(255,255,255,.16); color: #FFFFFF; }

@media (max-width: 700px) {
  /* At phone widths the photo is mostly cropped away behind the copy anyway,
     so the wash goes near-opaque top-to-bottom instead of left-to-right —
     a horizontal gradient has no room to breathe in 375px. */
  .hero {
    background:
      linear-gradient(180deg,
        rgba(22, 24, 27, .92) 0%,
        rgba(22, 24, 27, .85) 60%,
        rgba(22, 24, 27, .74) 100%),
      url('../assets/hero-background.jpg') center / cover no-repeat,
      var(--dark);
  }
  .hero-inner { padding-bottom: 30px; }
  .hero-actions > * { flex: 1 1 100%; }
}

/* ---------- section heading rows ---------- */

.section-head {
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
}
.h2 { font-weight: 700; font-size: clamp(26px, 2.4vw, 34px); }
.h2-lg { font-weight: 700; font-size: clamp(30px, 3.2vw, 42px); }

/* ---------- shared product field styles (see `field` in app.js) ---------- */

/* B2B listings are read for numbers, so field text is sized and contrasted
   for scanning rather than for decoration. */
.photo-slot { width: 100%; height: 100%; display: grid; place-items: center; }
.photo-slot .note-text { font-size: 12px; }
.f-brand { font-size: 17px; font-weight: 600; color: var(--text-2); }
/* No box — the category reads as a label through weight, spacing and the
   brand accent colour instead. */
.f-category {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--orange-link);
  justify-self: start;
}
.f-empty { color: var(--text-3); }
/* In a table the brand is a column of its own, so it carries more weight. */
.t-row .f-brand { font-size: 18px; font-weight: 700; color: var(--text-1); }

/* ---------- product table ---------- */

.table { background: var(--card-bright); border: 1px solid var(--border); border-radius: var(--r-md); box-shadow: var(--shadow-1); overflow-x: auto; }
.table-min { min-width: 960px; }
.table-min-sm { min-width: 880px; }
.t-head {
  display: grid;
  gap: 16px;
  padding: 13px 18px;
  background: var(--card-soft);
  border-bottom: 1px solid var(--border);
  font-size: 16px;
  font-weight: 700;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--text-2);
}
.t-row {
  display: grid;
  gap: 16px;
  padding: 16px 18px;
  border-bottom: 1px solid var(--border);
  align-items: center;
  font-size: 18px;
}
.t-row:last-child { border-bottom: none; }
.t-row:hover { background: var(--card-soft); }
.cols-preview { grid-template-columns: 60px 1.1fr 2.2fr 1fr 300px; }

/* Datasheet + Certificates sit side by side. */
.row-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.row-actions > .btn { flex: 2; }

.thumb { width: 44px; height: 44px; border-radius: var(--r-xs); overflow: hidden; background: var(--photo-bg); flex: none; }
.thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.thumb img[data-partner="vol-electronic"] { object-fit: scale-down; }

.link-name {
  text-align: left;
  background: none;
  border: 0;
  padding: 0;
  font-family: var(--display);
  font-size: 19px;
  font-weight: 700;
  color: var(--text-1);
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: var(--border);
  text-underline-offset: 3px;
  overflow-wrap: anywhere;
}
.link-name:hover { color: var(--orange-link); }

/* ---------- product cards ---------- */

.product-card {
  background: var(--card-bright);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-1);
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  cursor: pointer;
  text-align: left;
  width: 100%;
  font: inherit;
  color: inherit;
}
.product-card:hover { border-color: var(--orange-border); box-shadow: var(--shadow-2); }
/* One step down the radius scale from the card that contains it — the photo
   is inset by 20px of card padding, so it isn't concentric; the only rule
   that matters is that the inner curve reads tighter than the outer. */
.product-photo { width: 100%; aspect-ratio: 1/1; border-radius: var(--r-sm); overflow: hidden; margin-bottom: 4px; background: var(--photo-bg); }
.product-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.product-photo img[data-partner="vol-electronic"] { object-fit: scale-down; }
.product-name {
  text-align: left;
  background: none;
  border: 0;
  padding: 0;
  font-family: var(--display);
  font-weight: 700;
  font-size: 22px;
  line-height: 1.25;
  color: var(--text-1);
  cursor: default;
  pointer-events: none;
  overflow-wrap: anywhere;
}

/* mobile row card (Products/Home/Partner preview at <700px) */
.row-card {
  background: var(--card-bright);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-1);
  padding: 14px;
  display: grid;
  grid-template-columns: 84px minmax(0,1fr);
  gap: 14px;
  align-items: start;
  cursor: pointer;
  text-align: left;
  width: 100%;
  font: inherit;
  color: inherit;
}
/* Matches .product-card:hover — this card is equally clickable but had no
   hover feedback at all before. */
.row-card:hover { border-color: var(--orange-border); box-shadow: var(--shadow-2); }
.row-card-photo { width: 84px; height: 84px; border-radius: var(--r-sm); overflow: hidden; background: var(--photo-bg); flex: none; }
.row-card-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.row-card-photo img[data-partner="vol-electronic"] { object-fit: scale-down; }

.empty-state {
  background: var(--card-bright);
  border: 1px dashed var(--border);
  border-radius: var(--r-md);
  padding: clamp(28px, 4vw, 48px);
  display: flex;
  flex-direction: column;
  gap: 10px;
  align-items: flex-start;
}
.empty-state-title { font-family: var(--display); font-weight: 700; font-size: 21px; }

/* ---------- pagination ---------- */

.pager { display: flex; gap: 8px; align-items: center; justify-content: center; margin-top: 22px; flex-wrap: wrap; }
.page-btn {
  min-width: 44px; height: 44px;
  padding: 0 10px;
  border-radius: var(--r-sm);
  border: 1px solid var(--border-control);
  background: var(--card-bright);
  color: var(--text-1);
  font-size: 17px;
  font-weight: 600;
  cursor: pointer;
}
.page-btn:hover { background: var(--card-deep); }
.page-btn.is-active { background: var(--orange-fill); color: #14161A; border-color: var(--orange-border); }
.page-ellipsis { padding: 0 4px; color: var(--text-3); font-size: 17px; }

/* ---------- filter bar ---------- */

.filter-bar { position: sticky; top: var(--header-h); z-index: 30; background: var(--card-soft); border-bottom: 1px solid var(--border); }
.filter-bar-inner { padding-top: 14px; padding-bottom: 14px; display: flex; flex-direction: column; gap: 12px; }

/* Carries the spacing .filter-bar-inner used to give the search row and
   chip rows directly, now that they sit one level deeper inside the
   collapsible .filter-panel below — plus overflow:hidden, so the panel
   actually clips its content while animating shut. */
.filter-panel-inner { display: flex; flex-direction: column; gap: 12px; overflow: hidden; min-height: 0; }

/* Collapsed behind an arrow by default (see data-filter-collapsed in
   products.html) to save vertical space at every width — search and the
   agency/category chips are one tap away. Holds the arrow button and the
   view-toggle side by side; the view-toggle's buttons can't nest inside
   .filter-toggle itself, since it's a <button> and interactive elements
   can't contain other interactive elements, so it sits next to it instead.
   Animated with grid-template-rows rather than max-height so the slide
   matches the panel's real content height exactly, instead of guessing a
   cap and racing the easing against it. */
.filter-toggle-row { display: flex; align-items: center; gap: 12px; }
.filter-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  background: none;
  border: 0;
  padding: 10px 2px;
  font-size: 17px;
  font-weight: 700;
  color: var(--text-1);
  cursor: pointer;
}
.filter-toggle-arrow {
  width: 20px;
  height: 20px;
  flex: none;
  color: var(--text-3);
  transition: transform var(--dur) var(--ease);
}
/* Collapsed (default): arrow points down — tap to slide the panel down.
   Expanded: arrow flips to point up — tap to slide it back up. */
.filter-bar-inner:not([data-filter-collapsed]) .filter-toggle-arrow { transform: rotate(180deg); }

.filter-panel {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows .25s var(--ease);
}
.filter-bar-inner[data-filter-collapsed] { gap: 0; }
.filter-bar-inner[data-filter-collapsed] .filter-panel { grid-template-rows: 0fr; }
.search-row { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
.search-input { flex: 1 1 200px; min-width: 0; border: 1px solid var(--border-control); border-radius: var(--r-sm); padding: 12px 13px; background: var(--card-bright); color: var(--text-1); }
.clear-btn { background: var(--card-bright); border: 1px solid var(--border-control); border-radius: var(--r-sm); padding: 12px 16px; min-height: 44px; font-size: 17px; font-weight: 600; color: var(--text-1); cursor: pointer; }
.clear-btn:hover { background: var(--card-deep); }

.chip-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }

@media (max-width: 700px) {
  .scroll-row { flex-wrap: wrap; }
}

.chip-row-label { font-size: 16px; color: var(--text-3); margin-right: 4px; }
.chip {
  border: 1px solid var(--border-control);
  background: var(--card-bright);
  color: var(--text-1);
  border-radius: var(--r-sm);
  padding: 9px 14px;
  min-height: 42px;
  font-size: 17px;
  cursor: pointer;
}
.chip:hover { background: var(--card-deep); }
.chip.is-active { background: var(--text-1); color: #FFFFFF; border-color: var(--text-1); }

.view-toggle { display: flex; align-items: center; gap: 10px; }

/* The margin belongs on the wrapper JS actually injects .view-toggle into,
   not .view-toggle itself — auto-margin only pushes an element against its
   own flex container, and on products.html the wrapper (not .view-toggle)
   is the direct flex item of .chip-row / .filter-toggle-row. On the two
   other pages that use this control, the parent row already right-aligns
   it with justify-content: space-between, so this is merely redundant
   there rather than load-bearing — safe either way. */
[data-view-toggle] { margin-left: auto; }
/* No overflow:hidden on the group. It used to be what rounded the two
   buttons' outer corners, but it also clipped their focus ring away entirely
   — an outline is painted on the element and then cut off by any ancestor's
   clipping box. The buttons round their own outer corners instead. These two
   genuinely are concentric with the group (only its 1px border separates
   them), so -1px is the correct inset, not a guess. */
.toggle-group { display: flex; border: 1px solid var(--border-control); border-radius: var(--r-sm); }
.toggle-btn { background: var(--card-bright); color: var(--text-1); border: 0; border-radius: 0; padding: 8px 14px; font-size: 16px; font-weight: 600; cursor: pointer; }
.toggle-btn:first-child { border-radius: calc(var(--r-sm) - 1px) 0 0 calc(var(--r-sm) - 1px); }
.toggle-btn:last-child { border-radius: 0 calc(var(--r-sm) - 1px) calc(var(--r-sm) - 1px) 0; }
.toggle-btn + .toggle-btn { border-left: 1px solid var(--border-control); }
.toggle-btn.is-active { background: var(--text-1); color: #FFFFFF; }


/* ---------- partner cards (home + about + partner hero) ---------- */

/* Wide, short cards: logo sits beside the copy rather than above it. */
.partner-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(440px, 1fr)); gap: 16px; }

.partner-card {
  min-width: 0;
  background: var(--card-bright);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-1);
  padding: 20px clamp(20px, 2vw, 26px);
  display: grid;
  grid-template-columns: 132px minmax(0, 1fr);
  gap: clamp(18px, 2vw, 26px);
  align-items: center;
}
.partner-card:hover { border-color: var(--orange-border); box-shadow: var(--shadow-2); }

/* The sole agent leads: full width, dark, orange-edged. */
.partner-card.is-lead {
  grid-column: 1 / -1;
  background: var(--dark);
  border-color: var(--orange-border);
  border-left: 4px solid var(--orange-fill);
  color: #E7EAEC;
  grid-template-columns: 168px minmax(0, 1fr);
  padding: clamp(22px, 2.4vw, 30px) clamp(22px, 2.4vw, 30px);
}
.partner-card.is-lead .partner-card-summary { color: #D3D7DA; }
.partner-card.is-lead .count-num { color: #FFFFFF; }
.partner-card.is-lead .count-label { color: #A9AFB4; }
.partner-card.is-lead .count-row { border-top-color: #2A2E31; }
.partner-card.is-lead .badge-sole { color: var(--orange-fill); }
.partner-card.is-lead .btn-ghost { color: var(--orange-fill); }
.partner-card.is-lead .partner-card-logo { height: 60px; }

.partner-card-media { display: flex; align-items: center; min-width: 0; }
.partner-card-logo { display: block; height: 46px; }
.partner-card-logo img { display: block; height: 100%; width: auto; max-width: 100%; object-fit: contain; }
/* Philips's mark is a square badge and VoL's is a stacked two-line logo —
   neither is a wide single-line wordmark like Emtrons, so at the same box
   height both read visibly smaller. Taller box for both to match its
   visual weight. */
.partner-card-logo[data-partner="philips"],
.partner-card-logo[data-partner="vol-electronic"] { height: 64px; }

.partner-card-body { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.partner-card-summary { font-size: 17px; color: var(--text-2); }
.partner-card-foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

@media (max-width: 640px) {
  .partner-grid { grid-template-columns: minmax(0, 1fr); }
  .partner-card, .partner-card.is-lead { grid-template-columns: minmax(0, 1fr); gap: 14px; }
}

.badge-sole {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  align-self: flex-start;
  border: 1px solid var(--orange-border);
  /* Fully rounded — a status badge is the one place a pill reads as
     idiomatic rather than as a stray rounding decision. */
  border-radius: var(--r-pill);
  padding: 6px 12px;
  font-size: 16px;
  font-weight: 700;
  /* --orange-link is a dark burnt orange, contrast-checked against the
     site's light surfaces (7:1 on --card-bright, 6.1:1 on --card-soft) —
     see .partner-card.is-lead and .partner-spotlight elsewhere for the
     dark surfaces this badge also appears on, which override this back up
     to --orange-fill instead. */
  color: var(--orange-link);
}

.count-row { display: inline-flex; align-items: baseline; gap: 8px; }
.count-num { font-family: var(--display); font-weight: 700; font-size: 24px; }
.count-label { font-size: 16px; color: var(--text-3); }

/* ---------- footer ---------- */

.site-footer { background: var(--dark); color: #B9BEC2; }
.footer-grid {
  max-width: 1240px;
  margin: 0 auto;
  padding: clamp(36px, 4.5vw, 56px) clamp(20px, 4vw, 32px) 22px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: clamp(24px, 3vw, 44px);
  align-items: start;
}
.footer-col { display: flex; flex-direction: column; gap: 10px; }
.footer-col-head { margin: 0 0 2px; font-size: 16px; color: #A9AFB4; }
.footer-link { align-self: flex-start; background: none; border: 0; padding: 0; font-size: 18px; color: #FFFFFF; cursor: pointer; text-align: left; text-decoration: none; }
.footer-link:hover { color: var(--orange-fill); text-decoration: none; }
.footer-desc { margin: 0; max-width: 38ch; font-size: 18px; color: #A7ACB0; }
.footer-bottom {
  max-width: 1240px;
  margin: 0 auto;
  padding: 16px clamp(20px, 4vw, 32px) 28px;
  border-top: 1px solid var(--border-dark);
  display: flex;
  gap: 18px;
  flex-wrap: wrap;
  justify-content: space-between;
  font-size: 16px;
  color: #A9AFB4;
}
@media (max-width: 700px) {
  .footer-desc { display: none; }
}

/* ---------- quick bar ---------- */

.quickbar-spacer { display: none; height: 78px; }
.quickbar {
  display: none;
  position: fixed; left: 0; right: 0; bottom: 0;
  z-index: 50;
  background: var(--dark);
  border-top: 1px solid var(--border-dark);
  padding: 10px 14px max(10px, env(safe-area-inset-bottom));
  gap: 10px;
  align-items: center;
}
@media (max-width: 700px) {
  .quickbar-spacer { display: block; }
  .quickbar { display: flex; }
}
.quickbar a {
  flex: 1 1 0; min-width: 0;
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 46px;
  border-radius: var(--r-sm);
  font-size: 18px; font-weight: 600;
  text-decoration: none;
}
.quickbar-wa { background: #FFFFFF; color: #101214; border: 1px solid #FFFFFF; }
.quickbar-wa:hover { background: #E7E7E7; color: #101214; text-decoration: none; }
.quickbar-close {
  flex: 0 0 auto;
  width: 40px; height: 46px;
  background: none;
  border: 1px solid var(--border-dark);
  border-radius: var(--r-sm);
  font-size: 21px; line-height: 1;
  color: #C7CBCE;
  cursor: pointer;
}

/* ---------- product modal ---------- */

.modal-backdrop {
  position: fixed; inset: 0;
  z-index: 100;
  background: rgba(14,17,19,.6);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(10px, 2vw, 20px);
}
.modal-panel {
  background: var(--card-bright);
  border-radius: var(--r-lg);
  max-width: 920px;
  width: 100%;
  max-height: 92vh;
  overflow-y: auto;
  box-shadow: var(--shadow-4);
}
.sheet-handle { display: none; justify-content: center; padding: 8px 0 2px; }
.sheet-handle span { display: block; width: 42px; height: 4px; border-radius: var(--r-pill); background: var(--border); }

.modal-head {
  position: sticky; top: 0;
  background: var(--card-bright);
  border-bottom: 1px solid var(--border);
  padding: 16px clamp(20px, 3vw, 28px);
  display: flex;
  align-items: center;
  gap: 14px;
  justify-content: space-between;
}
.modal-eyebrow { display: flex; gap: 10px; align-items: center; font-size: 16px; color: var(--text-3); flex-wrap: wrap; }

.modal-close {
  flex: 0 0 auto;
  background: var(--card-soft);
  border: 1px solid var(--border-control);
  border-radius: var(--r-sm);
  width: 44px; height: 44px;
  font-size: 22px; line-height: 1;
  color: var(--text-1);
  cursor: pointer;
}
.modal-close:hover { background: var(--card-deep); }

.modal-grid {
  padding: clamp(18px, 3vw, 28px);
  display: grid;
  grid-template-columns: minmax(0,1fr) minmax(0,1fr);
  gap: clamp(18px, 3vw, 32px);
  align-items: start;
}

/* Placed after .modal-backdrop/.modal-panel/.sheet-handle/.modal-grid's own
   rules above so these mobile overrides win by normal cascade order. */
@media (max-width: 700px) {
  .modal-backdrop { align-items: flex-end; padding: 0; }
  .modal-panel { max-height: 90vh; max-height: 90dvh; border-radius: var(--r-lg) var(--r-lg) 0 0; animation: sheet-up .22s var(--ease); }
  .sheet-handle { display: flex; }
  .modal-grid { grid-template-columns: minmax(0,1fr); }
}

.modal-title { font-weight: 700; font-size: clamp(24px, 2.6vw, 32px); }
.modal-section-title { font-weight: 700; font-size: 19px; margin-bottom: 10px; }

.spec-table { border: 1px solid var(--border); border-radius: var(--r-md); }
.spec-row { display: grid; grid-template-columns: 130px 1fr; gap: 14px; padding: 10px 14px; border-bottom: 1px solid var(--border); font-size: 17px; }
.spec-row:last-child { border-bottom: none; }
.spec-label { color: var(--text-3); font-size: 16px; }

.ask-box { background: var(--card-soft); border: 1px solid var(--border); border-radius: var(--r-md); padding: 16px; display: flex; flex-direction: column; gap: 10px; }

/* ---------- document downloads ---------- */

.doc-list { border: 1px solid var(--border); border-radius: var(--r-md); overflow: hidden; }
.doc-row {
  display: grid;
  grid-template-columns: 82px minmax(0, 1fr) auto;
  grid-template-areas: "type label actions";
  gap: 12px;
  align-items: center;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  color: var(--text-1);
  text-decoration: none;
  font-size: 17px;
}
.doc-row:last-child { border-bottom: none; }
.doc-row:hover { background: var(--card-soft); }
.doc-type {
  grid-area: type;
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 700;
  text-align: center;
  padding: 3px 6px;
  border: 1px solid var(--border);
  border-radius: var(--r-xs);
  color: var(--text-3);
  background: var(--card-soft);
}
.doc-label { grid-area: label; min-width: 0; overflow-wrap: anywhere; }
.doc-actions { grid-area: actions; display: flex; gap: 14px; align-items: center; white-space: nowrap; }
.doc-actions a { font-size: 16px; font-weight: 600; color: var(--orange-link); }
.doc-actions a:hover { color: var(--orange-border); }
/* Below 400px-ish the label column of the single-row layout (82px badge +
   the "View Download" actions both sharing the row with it) can pinch down
   to less than a normal label's own rendered width, forcing
   overflow-wrap:anywhere to split a plain word like "Datasheet" mid-letter
   instead of wrapping at a space it doesn't have. Giving the actions their
   own row below frees the label to use the row's full width alongside just
   the 82px badge, which is enough room for any label actually in use. */
@media (max-width: 420px) {
  .doc-row { grid-template-columns: 82px minmax(0, 1fr); grid-template-areas: "type label" "actions actions"; row-gap: 6px; }
  .doc-actions { justify-content: flex-end; }
}

/* number of certificates, shown on the listing's Certificates button */
.doc-count {
  font-size: 14px;
  font-weight: 700;
  padding: 1px 6px;
  border-radius: var(--r-pill);
  background: var(--card-deep);
  color: var(--text-2);
}
/* The badge sits inside a .btn-secondary, whose hover fill is also
   --card-deep — without this the badge would disappear into the button on
   hover. Only surfaced once --card-deep stopped being a duplicate of
   --card-soft and started actually rendering. */
.btn-secondary:hover .doc-count { background: var(--card-bright); }

/* ---------- partner whose catalogue is hosted by the manufacturer ---------- */

/* Lives inside the dark partner hero by default; .bg-page below re-themes it
   for the light products-page results area, where the same "no local range"
   message appears when the selected partner's filter is external-only. */
.external-panel {
  background: rgba(255, 255, 255, .05);
  border: 1px solid var(--border-dark);
  /* The accent edge is an inset shadow rather than a 3px border: at the new
     10px radius a thick border tapers into the 1px one around the corner arc,
     which reads as a rendering fault. Safe to use a shadow here (unlike
     .partner-card.is-lead) because this panel never gets an elevation shadow
     to conflict with — it lives on the dark hero, where one is invisible. */
  box-shadow: inset 3px 0 0 var(--orange-fill);
  border-radius: var(--r-md);
  padding: 18px 20px;
  margin-top: 4px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  align-items: flex-start;
}
.external-title { font-family: var(--display); font-weight: 700; font-size: 19px; color: #FFFFFF; }
.external-text { font-size: 17px; color: #C3C8CC; max-width: 52ch; }
.external-note { font-size: 15px; color: #8E949A; }
.external-mark { font-size: 17px; }

.bg-page .external-panel { background: var(--card-bright); border-color: var(--border); }
.bg-page .external-title { color: var(--text-1); }
.bg-page .external-text { color: var(--text-2); }
.bg-page .external-note { color: var(--text-3); }

/* ---------- two-column stacks ---------- */

.stack { display: grid; grid-template-columns: minmax(240px,320px) 1fr; gap: clamp(24px,4vw,56px); align-items: start; }
@media (max-width: 700px) { .stack { grid-template-columns: minmax(0,1fr); } }

.grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 24px; }

/* The full bulb mark (O ring + the hanging base beneath it — see
   assets/omega-o-mark.png), pinned to the top-right corner of its column
   and sized to spill well past the .wrap's own right edge — a bounded card
   was the wrong treatment for a mark meant to read as artwork tucked into
   the corner, not a boxed-in photo. Absolutely positioned rather than sized
   in normal flow: a plain oversized <img> in flow would grow the column
   trying to fit it, the opposite of "overflow without disturbing the
   layout." html's own overflow-x:clip (see the html rule near the top of
   this file) is what keeps the horizontal spill from ever producing a
   scrollbar.

   Faded uniformly (plain opacity, not a directional mask like an earlier
   version of this rule used) — the whole mark reads as a soft watermark
   behind the copy rather than a graphic with any harder edge to it.

   .32 measured out as too dark specifically where the O ring's black
   overlaps the lead paragraph, which uses --text-2 rather than the
   darker --text-1 the heading/body copy use: blending #000 at 32% into
   --page-bg (#FBF8F3) lands around L≈0.39, giving --text-2 only a ~3.9:1
   contrast ratio there — under WCAG AA's 4.5:1 for normal-weight text at
   this size. The orange plug portion and --text-1 both stay well clear
   (≥6.5:1 and ≥7.9:1) at that same opacity, so the ring-over-text-2 case
   was the actual binding constraint; .2 keeps every pairing at 4.6:1+.

   Tall enough now to reach up behind .site-header itself: nothing between
   here and <body> sets overflow-y:hidden (only html's own overflow-x:clip,
   which is horizontal-only — see the html rule near the top of this file),
   so the vertical overflow this `top` offset creates isn't clipped, and
   .site-header's z-index:40 already outranks .about-illustration's z-index:0
   at the same root stacking level — no separate z-index bump was needed to
   make the header win, it already did. .about-intro-text keeps its own
   stacking context (z-index:1) so it also stays above the mark for the
   stretch where the two overlap. */
.about-illustration { position: relative; z-index: 0; height: clamp(280px, 32vw, 400px); }
.about-illustration img {
  position: absolute;
  top: -160px;
  right: -5%;
  width: clamp(320px, 40vw, 560px);
  height: auto;
  z-index: -1;
  opacity: .2;
  filter: drop-shadow(0 20px 36px rgba(20,22,26,.14));
}
.about-intro-text { position: relative; z-index: 1; }

@media (max-width: 700px) {
  /* .grid-2 stacks to one column here, so "top-right corner, reaching up
     behind the header" stops making sense — there's no second column left
     to be a corner of. Instead .about-intro-text and .about-illustration
     are placed into the SAME grid cell (both row 1 / column 1, which
     .grid-2's single mobile column would otherwise stack into two separate
     rows one after the other) so the mark renders as a watermark directly
     behind the heading and copy, not a separate block below them. Which one
     is "on top" is still the z-index already set on each — .about-intro-text
     at 1, the mark's own z-index:-1 within .about-illustration's z-index:0
     context — this only changes WHERE they sit, not which wins. */
  .about-intro-text, .about-illustration { grid-row: 1; grid-column: 1; }
  /* overflow:visible here (and no such rule stopping it further up the
     tree — .wrap, .grid-2 and section.bg-page are all plain, unclipped
     boxes) is what lets width:110vw below actually spill past .wrap's own
     side padding instead of getting cut off at .about-illustration's edge.
     html's overflow-x:clip (see the html rule near the top of this file)
     is still the backstop that keeps that from ever showing a horizontal
     scrollbar. */
  .about-illustration { height: auto; align-self: stretch; overflow: visible; }
  .about-illustration img {
    top: 50%; right: auto; left: 50%; translate: -50% -50%;
    width: clamp(300px, 110vw, 480px);
    opacity: .16;
  }
}

/* ---------- contact ---------- */

.contact-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: clamp(28px, 4vw, 56px); align-items: start; }
.contact-facts { display: flex; flex-direction: column; gap: 20px; font-size: 18px; }
.contact-fact { border-top: 2px solid var(--text-1); padding-top: 12px; }
.contact-fact:first-child { border-top: none; padding-top: 0; }
.contact-fact-label { margin: 0 0 4px; font-size: 16px; color: var(--text-3); }
.contact-actions { display: flex; gap: 12px; flex-wrap: wrap; }

/* At this width the three buttons no longer fit side by side, so each ends
   up alone on its own wrapped line — flex (any positive value) stretches
   each one to fill that line, instead of sitting at its own text width. */
@media (max-width: 700px) {
  .contact-actions [data-whatsapp-link] { flex: 2; }
  .contact-actions [data-email-link] { flex: 1; }
  .contact-actions .btn-primary { flex-grow: 1; }
}

.form-card { background: var(--card-bright); border: 1px solid var(--border); border-radius: var(--r-md); box-shadow: var(--shadow-1); padding: clamp(20px, 2.4vw, 28px); display: flex; flex-direction: column; gap: 14px; }
.form-card label { display: flex; flex-direction: column; gap: 6px; font-size: 16px; color: var(--text-2); }
.field-label { font-weight: 600; color: var(--text-1); }
.req { color: var(--orange-border); }
.form-card input, .form-card textarea {
  border: 1px solid var(--border-control);
  border-radius: var(--r-sm);
  padding: 10px 12px;
  background: var(--card-bright);
  color: var(--text-1);
}
.form-card textarea { resize: vertical; font-family: inherit; }

/* ---------- partner hero ---------- */

/* Light, not dark — only .partner-spotlight below is --dark now (one
   featured card, the same "dark only for what's meant to stand out"
   principle .partner-card.is-lead on the home page already uses), not the
   whole section wrapping it. That also means this no longer touches
   .site-footer (--dark) directly, unlike the earlier all-dark version of
   this page did — nothing extra needed here to keep the two apart. */
.partner-hero { background: var(--page-bg); }

/* ---------- partner spotlight (partners.html hero) ---------- */

/* A single "spotlight" card for whichever partner is currently selected
   (Emtrons by default), plus a list of the others below it that promote
   themselves into the spotlight on click (see render()/selectedId in
   partners.js) — replaces the earlier expanding-tab-row design entirely
   rather than layering on top of it. Mobile-first: the base rules here are
   the full-detail stacked layout: the @media (min-width:701px) block
   further down is what collapses .other-partner into a compact chip. */
.partner-spotlight {
  background: var(--dark);
  border-radius: var(--r-lg);
  padding: clamp(28px, 4vw, 48px) clamp(20px, 4vw, 40px);
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 14px;
}
.spotlight-eyebrow { font-size: 13px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: #A9AFB4; }
.spotlight-logo { display: block; max-width: min(220px, 60%); max-height: 56px; width: auto; height: auto; object-fit: contain; }
.spotlight-name { font-size: clamp(26px, 3vw, 34px); font-weight: 700; color: #FFFFFF; }
/* --orange-link (badge-sole's own default colour, contrast-checked for the
   site's light surfaces) measures ~2.5:1 on --dark — well under WCAG AA —
   so this swaps it for --orange-fill, already relied on for orange-on-dark
   everywhere else on the site (nav, topbar, .spotlight-cta below).
   align-self:center overrides badge-sole's own flex-start (built for the
   left-aligned partner cards on home/about) — this card is centred
   end-to-end. */
.partner-spotlight .badge-sole { align-self: center; color: var(--orange-fill); }
.spotlight-desc { font-size: 16px; line-height: 1.5; color: #C3C8CC; max-width: 46ch; }
.spotlight-stat { font-size: 14px; color: #A9AFB4; }
.spotlight-stat strong { color: #FFFFFF; font-size: 17px; font-weight: 700; }
/* Explicit colours in both states rather than leaning on the site-wide
   a/a:hover rule — that pair is tuned for --orange-link on light surfaces,
   ~2.5:1 on --dark (see the badge-sole comment above for the same
   measurement), so it needs its own dark-appropriate pair here too. */
.spotlight-cta { color: var(--orange-fill); font-weight: 700; font-size: 15px; }
.spotlight-cta:hover { color: #FFFFFF; text-decoration: underline; }

.other-partners { margin-top: clamp(24px, 4vw, 40px); }
.other-partners-label { font-size: 13px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--text-3); margin: 0 0 14px; }
.other-partners-list { display: flex; flex-direction: column; gap: 14px; }

/* Light, not dark — only the spotlight above is --dark. A page of three
   equally-dark blocks (spotlight, then two more) would drown out the one
   card that's actually meant to stand out, and there's nothing left to
   separate from the footer below if every card up here is already black. */
.other-partner { background: var(--card-bright); border: 1px solid var(--border); border-radius: var(--r-md); overflow: hidden; }
.other-partner:hover { border-color: var(--orange-border); }
/* Just the logo, full-width and generously padded — a real card header, not
   a cramped label. Permanently static now, not a collapse toggle: every
   card's .other-partner-detail below is always fully shown (the client
   wants every partner's range visible up front, permanently), so this is
   plain header content rather than a control — the actual navigation lives
   entirely in .other-partner-cta further down. */
.other-partner-trigger {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 96px;
  padding: 20px;
}
/* Every logo file already fills its own canvas edge to edge (checked by
   scanning each PNG/WebP for its actual opaque bounding box — none of them
   have transparent padding baked in), so an explicit height with
   object-fit:contain is enough to bring them all to the same canvas size.
   That still isn't the same as equal visual WEIGHT — a thin wordmark next
   to a bold solid icon still reads smaller at a matching height regardless.
   --other-partner-logo-scale (set per-instance in partners.js, see
   OTHER_PARTNER_LOGO_SCALE there) is the deliberate correction for that —
   not a bug fix, a judgement call about visual balance across the row. */
.other-partner-logo {
  display: block;
  width: min(220px, 80%);
  height: 44px;
  object-fit: contain;
  transform: scale(var(--other-partner-logo-scale, 1));
}
.other-partner-name { font-weight: 700; font-size: 19px; color: var(--text-1); }
.other-partner-detail-inner { padding-top: 4px; }
.other-partner-desc { font-size: 15px; line-height: 1.5; color: var(--text-2); padding: 0 20px; }
.other-partner-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; padding: 12px 20px 20px; }
.other-partner-stat { font-size: 14px; color: var(--text-3); }
.other-partner-stat strong { color: var(--text-1); font-weight: 700; }
/* margin-left:auto rather than relying on .other-partner-foot's own
   justify-content:space-between alone — Philips has no stat paragraph any
   more (see statHTML in partners.js), so this is sometimes the only child
   in that flex row, and space-between only pushes an item to the far edge
   when there's a sibling to space it FROM. The auto margin keeps it pinned
   to the right in both the one-child and two-child case alike. */
.other-partner-cta { font-size: 14px; font-weight: 700; margin-left: auto; }
.other-partner-cta:hover { color: var(--orange-fill); text-decoration: none; }

/* Desktop: arranged as a row instead of a stack, and sized up — bigger
   padding, bigger logo — so a card reads as a real, substantial thing
   rather than a small label. */
@media (min-width: 701px) {
  .other-partners-list { flex-direction: row; flex-wrap: wrap; align-items: stretch; }
  .other-partner { flex: 1 1 260px; }
  .other-partner-trigger { min-height: 140px; padding: 28px; }
  .other-partner-logo { height: 60px; width: 85%; }
}

/* ---------- load error banner ---------- */

.load-error {
  position: sticky;
  top: 0;
  z-index: 200;
  background: #B3261E;
  color: #FFFFFF;
  padding: 12px clamp(16px, 4vw, 32px);
  font-size: 16px;
  line-height: 1.5;
}
.load-error .mono { background: rgba(255,255,255,.15); padding: 1px 5px; border-radius: var(--r-xs); }

/* ---------- anchor scroll offset ---------- */

#partners, #why-us { scroll-margin-top: calc(var(--header-h) + 12px); }
/* Where pagination on products.html scrolls back to (see the pager's click
   handler in products.js) — .filter-bar is sticky at var(--header-h) itself
   and isn't a fixed height (its search/chip row can wrap), so this is a
   generous estimate rather than a measured value like --header-h is. */
[data-result-label] { scroll-margin-top: calc(var(--header-h) + 90px); }

/* ---------- responsive view switching (table / cards / mobile rows) ---------- */

.desktop-only { display: block; }
.mobile-only { display: none; }

/* Card view now works at every width — .desktop-only stays visible so
   [data-view-mode="cards"] .view-cards below can show through it, and the
   grid's own auto-fill/minmax already collapses to one column on a narrow
   screen. A real data table needs its own, wider cutoff: .table-min forces
   it to 960px (the widest of .table-min/.table-min-sm), so it substitutes
   the compact mobile row list below that regardless of the general
   699.98px mobile/desktop breakpoint used everywhere else. Below 960px a
   phone in landscape (typically 700–900px) is past that general breakpoint
   but still narrower than the table needs — showing the real table there
   just forces it to scroll sideways, which is the "rotate makes the table
   view look wrong" bug this guards against. The !important is needed
   because [data-view-mode="table"] .view-table below matches with equal
   specificity and comes later in the file, so it would otherwise win
   outright. */
@media (max-width: 959.98px) {
  .view-table { display: none !important; }
  [data-view-mode="table"] .mobile-only { display: grid; grid-template-columns: minmax(0,1fr); gap: 10px; }
}

.view-table, .view-cards { display: none; }
[data-view-mode="table"] .view-table { display: block; }
[data-view-mode="cards"] .view-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(272px, 1fr)); gap: 16px; }
