/* ==========================================================================
   Abu Najm Trading — site styles
   Compiled from the Claude Design prototypes in /design-source.
   The prototypes use inline styles + `style-hover` attributes; those become
   real classes and :hover rules here.
   ========================================================================== */

:root {
  --bg: #F6F7F9;
  --surface: #FFFFFF;
  --surface-soft: #F6F8FB;

  --navy: #1C2C4C;
  --ink: #0B1F3A;
  --ink-2: #233650;
  --ink-3: #3B4A63;
  --muted: #51607A;
  /* Was #8A96A8, which measured 2.79-3.00:1 — under the 4.5:1 floor on every
     surface it is painted on, and this token is text in all six places it is
     used (.spec-list__brands, .brand-board__label, .stat-card__sub,
     .roster-caption and two in product-detail.css), not decoration. #697280
     clears 4.52:1 against the darkest of those backgrounds while staying
     lighter than --muted, so the two-step hierarchy survives. */
  --muted-2: #697280;

  --blue: #2B6BE4;
  /* The focus ring alone. --blue is 4.53:1 on the light surfaces but only
     2.86:1 against --navy, and the ring lands on navy every time a footer or
     hero control is tabbed to — under the 3:1 non-text floor. One colour has
     to clear 3:1 on BOTH ends, which pins it to a narrow luminance band;
     #4880E0 sits in it at 3.59:1 either way and stays on-brand. */
  --focus: #4880E0;
  --blue-dark: #1E56C4;
  --blue-soft: #7FA8F5;

  --amber: #DC8C34;
  --amber-light: #E8A04C;
  --amber-dark: #C77A26;
  --amber-tint: #F5EDDE;
  /* Amber as *text on light ground*. The ramp above is pitched for dark ground
     and none of it carries a glyph on a light one: measured on white, --amber
     is 2.7:1 and --amber-dark 3.4:1 — under AA for body copy, and --amber-dark
     only scrapes the 3:1 large-text bar. So --amber-dark is now used by nothing
     at all; don't reach for it because the name sounds right. Fills, borders,
     rules and icon strokes still want --amber.
     --amber-ink measures 4.68:1 on white, 4.43:1 on the --card-face gradient
     and 4.37:1 on --bg — so it clears AA for body copy on a white card, but on
     the page ground it is a hair short and only clears the large-text bar.
     See HANDOVER §8. */
  --amber-ink: #A9631A;

  --line: rgba(11, 31, 58, 0.08);
  --line-strong: rgba(11, 31, 58, 0.14);

  /* Type ------------------------------------------------------------------
     Four faces, each with exactly one job. The pair this replaced (Sora +
     Manrope) is the default pairing of a thousand SaaS templates, which is
     precisely what the client saw when they said the home page looked generic.

     --display  Bricolage Grotesque. An editorial grotesque with real opsz and
                wdth axes. Picked over a neutral geometric because at 800 it
                still has a voice — the angled Y junction, the tailed a, the
                tight tt — and voice is what stops a headline reading as stock.
                It is also narrower than Sora at the same size, which is what
                lets the hero's first line hold on one line at the measure.
     --serif    Instrument Serif, italic, 400 only — and the request really is
                `ital@1`, not `ital@0;1`. Asking for both styles pulled the
                roman down too, 21,032 B for a face nothing on the site sets.
                It appears in ONE role: the accent clause inside a headline.
                That single mixed-face gesture is the cheapest thing here that
                makes the page read as art-directed rather than assembled — and
                it only stays cheap while it stays rare. Do not spread it
                around; there is no non-italic instance to fall back on.
     --body     Inter. Much larger x-height than the Manrope it replaces, so
                every body size in the scale below is ~1px smaller than the
                value it replaced and still measures bigger on screen. Don't
                "restore" the old numbers.
     --mono     IBM Plex Mono. Carries the figures that should read as
                spec-sheet data. This was a system stack, which meant part
                numbers rendered in Consolas on Windows and Menlo on macOS —
                the one place on the site where two visitors saw different
                type. */
  --display: 'Bricolage Grotesque', 'Segoe UI', Helvetica, Arial, sans-serif;
  --serif: 'Instrument Serif', Georgia, 'Times New Roman', serif;
  --body: 'Inter', 'Segoe UI', Helvetica, Arial, sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, Menlo, Consolas, monospace;

  /* Type scale ------------------------------------------------------------
     Before this existed there were ~70 distinct hardcoded px font-sizes across
     the two stylesheets — 14, 14.5, 15, 15.5, 16.5, 17, 17.5 and 18 all in use
     as separate "body" sizes. That is not a scale, it is sediment. Everything
     now picks a step, and a step is only added if a real role needs it.

     rem, not px, so the whole site honours a reader's browser font-size
     setting — it previously ignored it completely. */
  --fs-micro: 0.6875rem;  /* 11px — uppercase micro labels */
  --fs-xs:    0.8125rem;  /* 13px — fine print, form labels, hours rows */
  --fs-sm:    0.875rem;   /* 14px — card body, nav, footer */
  --fs-md:    0.9375rem;  /* 15px — dense body, buttons, inputs */
  --fs-lg:    1.0625rem;  /* 17px — small lede */
  --fs-xl:    1.1875rem;  /* 19px — lede */

  /* Display steps are fluid; the ratio between them is ~1.28 throughout, which
     is what makes an h2 read as one clear rung below an h1 rather than as "a
     slightly different size". */
  --fs-d1: clamp(2.75rem, 5.5vw, 4.875rem);   /* 44 -> 78  hero only */
  --fs-d2: clamp(2.125rem, 4.2vw, 3.5rem);    /* 34 -> 56  h1, cta headline */
  --fs-d3: clamp(1.75rem, 3.2vw, 2.625rem);   /* 28 -> 42  h2 */
  --fs-d4: clamp(1.375rem, 2.4vw, 1.875rem);  /* 22 -> 30  product titles */
  --fs-d5: 1.25rem;                           /* 20px      card titles */
  --fs-d6: 1.0625rem;                         /* 17px      small card titles */

  /* Tracking. Optical, not decorative: the larger the type the tighter it is
     set, because tracking that looks right at 15px looks loose at 78px. */
  --tr-display: -0.032em;
  --tr-tight: -0.022em;
  --tr-snug: -0.012em;
  --tr-caps: 0.14em;
  --tr-caps-wide: 0.22em;

  --lh-display: 1.02;
  --lh-tight: 1.14;
  --lh-snug: 1.35;
  --lh-body: 1.65;

  --maxw: 1240px;
  --nav-h: 74px;
  --gutter: 24px;

  /* Card system ---------------------------------------------------------- */
  /* A gentle overshoot on the way up reads as "solid object", where a plain
     ease reads as "fading image". */
  --ease-out-quint: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1);

  /* Layered rather than single shadows: a tight contact shadow keeps the card
     anchored while the wide one does the lifting. */
  --shadow-rest: 0 1px 2px rgba(11, 31, 58, 0.04), 0 6px 16px rgba(11, 31, 58, 0.05);
  --shadow-hover: 0 2px 4px rgba(11, 31, 58, 0.05), 0 26px 56px rgba(11, 31, 58, 0.13);
  --shadow-hover-blue: 0 2px 4px rgba(11, 31, 58, 0.05), 0 26px 56px rgba(43, 107, 228, 0.20);
  --shadow-hover-amber: 0 2px 4px rgba(11, 31, 58, 0.06), 0 26px 56px rgba(220, 140, 52, 0.26);

  --card-face: linear-gradient(158deg, #FFFFFF 0%, #FCFDFE 48%, #F6F9FC 100%);
  --card-face-soft: linear-gradient(158deg, #F9FBFD 0%, #F4F7FB 100%);
}

*, *::before, *::after { box-sizing: border-box; }

/* The UA rule for this is just `[hidden] { display: none }`, so any component
   that sets its own `display` (.form and .form-success are both flex) silently
   out-specifies it and stays on screen. Everything toggled by the `hidden`
   attribute depends on this line. */
[hidden] { display: none !important; }

/* The X-axis clip has to be on the ROOT, not on body.
   `body { overflow-x: hidden }` alone looks like it does this job and doesn't.
   When the root's overflow is `visible`, the body's non-visible value is what
   propagates to the viewport — and the body itself then computes back to
   `visible`. So nothing was ever clipped: `[data-reveal="left|right"]` parks an
   element at translateX(±40px) until it reveals, and any of those sitting in a
   section that doesn't clip on its own grew the document 40px wider than the
   viewport. Measured at a 360px viewport: about, products and contact all
   reported scrollWidth 401 (index escaped only because its equivalent sections
   are .section--ambient, which already clips for the canvas). Under mobile
   emulation that also stretched the layout viewport to 401px, so the fixed nav
   became 401px wide and the burger's right edge landed 21px past the screen —
   the menu button, clipped off the edge of a small phone.
   `clip` rather than `hidden` on purpose: it clips without making the root a
   scroll container, which keeps `scroll-behavior: smooth`, the #anchor jumps and
   scroll-margin-top behaving exactly as before. `hidden` first as the Safari <16
   fallback — it also fixes it, just at the cost of a scroll container. */
html {
  overflow-x: hidden;
  overflow-x: clip;
  scroll-behavior: smooth;
  /* Reserve the scrollbar gutter permanently. The nav drawer and the product
     modals lock scrolling, and BECAUSE of the overflow-x above they have to do it
     on <html> — `body { overflow: hidden }` does nothing here, since a non-visible
     overflow on the root stops the body's value propagating to the viewport, so
     <html> is the scroll container and the body rule locks an element that was
     never scrolling. (Measured: with the old body lock in place the page still
     scrolled 400px under an open drawer.) Locking the root removes the scrollbar,
     which on a classic Windows scrollbar reflows the whole page ~15px sideways at
     the moment a modal opens; reserving the gutter always means there is nothing
     to reclaim and the lock is visually silent. */
  scrollbar-gutter: stable;
}
/* Set by site.js (drawer) and product-detail.js (modals). A class, not an inline
   style, so unlocking cannot clobber the overflow-x rules above. */
html.is-scroll-locked {
  overflow-y: hidden;
}

body {
  margin: 0;
  /* Kept as a second line of defence for the same reason, and harmless. The
     rule that actually governs is on `html` above. */
  overflow-x: hidden;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--body);
  font-size: var(--fs-md);
  line-height: var(--lh-body);
  /* Bricolage ships an optical-size axis (12–96) and is set from 17px card
     titles up to the 78px hero, so letting the browser drive opsz is the
     difference between type that was *set* and type that was merely scaled.

     Inter is deliberately requested WITHOUT its opsz axis: it only ever runs
     11–19px here, a span too narrow for the axis to show, and the axis costs
     24,664 B of the latin subset — measured, not assumed. This declaration is
     therefore a no-op for the body face, which is the intended outcome. */
  font-optical-sizing: auto;
  /* Never let the browser fake a weight or a slant we did not load. The old
     build asked .btn--amber for Manrope 800 while loading only up to 700, so
     the home page's primary CTA shipped a synthesised bold — smeared, slightly
     too wide, and different on every platform. With synthesis off, a missing
     weight shows up as visibly wrong here instead of quietly wrong in
     production. */
  font-synthesis-weight: none;
  font-synthesis-style: none;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: var(--blue); }
a:hover { color: var(--blue-dark); }

img { max-width: 100%; }

::selection { background: var(--amber-light); color: var(--navy); }

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
  border-radius: 4px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  background: var(--navy);
  color: #fff;
  padding: 12px 20px;
  border-radius: 0 0 10px 0;
  font-weight: 600;
  font-size: var(--fs-sm);
  text-decoration: none;
}
.skip-link:focus {
  left: 0;
  color: #fff;
}

.container {
  max-width: var(--maxw);
  margin: 0 auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------------------
   Ambient canvas
   A <canvas> is a *replaced* element: `inset:0` alone does NOT stretch it —
   it falls back to its width/height attributes and paints into a sliver.
   The explicit 100%/100% here is what actually sizes it.
   -------------------------------------------------------------------------- */
canvas[data-particles] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   Card system — shared hover language
   Three layers, applied consistently so every card on the site reacts the
   same way:
     1. lift      — the card rises on a springy curve, shadow blooms
     2. spotlight — a soft glow tracks the cursor (ambient.js feeds --mx/--my)
     3. rail      — a hairline of brand colour draws itself across the top edge
   `isolation: isolate` gives each card its own stacking context, which is what
   lets the z-index:-1 spotlight sit above the card's background but below its
   content without escaping the card.
   -------------------------------------------------------------------------- */
.card {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--card-face);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-rest);
  transition:
    transform 0.5s var(--ease-out-quint),
    box-shadow 0.5s var(--ease-out-quint),
    border-color 0.4s ease;
}
.card:hover {
  transform: translateY(-6px);
  box-shadow: var(--shadow-hover);
  border-color: rgba(43, 107, 228, 0.3);
}

/* the cursor-tracked glow */
.card[data-spotlight]::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.45s ease;
  background: radial-gradient(
    320px circle at var(--mx, 50%) var(--my, 0%),
    rgba(43, 107, 228, 0.11),
    rgba(43, 107, 228, 0.05) 42%,
    transparent 72%
  );
}
.card[data-spotlight]:hover::before { opacity: 1; }

/* the accent rail */
.card--rail::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(90deg, var(--amber), var(--blue));
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.55s var(--ease-out-quint);
}
.card--rail:hover::after { transform: scaleX(1); }

/* dark variants: the same three layers, re-tinted for a navy surface */
.card--dark {
  background: linear-gradient(158deg, rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0.03));
  border-color: rgba(255, 255, 255, 0.1);
  box-shadow: none;
}
.card--dark:hover {
  background: linear-gradient(158deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.05));
  border-color: rgba(220, 140, 52, 0.55);
  box-shadow: 0 26px 56px rgba(0, 0, 0, 0.3);
}
.card--dark[data-spotlight]::before {
  background: radial-gradient(
    320px circle at var(--mx, 50%) var(--my, 0%),
    rgba(232, 160, 76, 0.16),
    rgba(43, 107, 228, 0.08) 45%,
    transparent 72%
  );
}

/* --------------------------------------------------------------------------
   Typography
   -------------------------------------------------------------------------- */
.h1 {
  margin: 0;
  font-family: var(--display);
  font-weight: 800;
  font-size: var(--fs-d2);
  line-height: var(--lh-tight);
  letter-spacing: var(--tr-tight);
  text-wrap: balance;
}

/* The accent clause inside a headline — the same gesture the hero makes with
   its <em>, carried to the inner pages so it reads as a system rather than as
   one page's flourish. Serif italic against the grotesque does the work; the
   colour is only there to finish it.

   1.06em because Instrument Serif's cap-height sits visibly below Bricolage's
   at a matched font-size, and a serif that measures shorter than the roman it
   sits beside reads as a mistake rather than as a change of voice.
   Weight is pinned to 400: the family ships one weight, and with
   font-synthesis-weight off an inherited 800 would silently do nothing here —
   better to say what is actually rendering. */
.h1 .accent,
.h2 .accent {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  font-size: 1.06em;
  letter-spacing: -0.005em;
  /* An italic leans into whatever precedes it, so the word space inherited
     from the roman is optically too tight — on the contact headline the L of
     "Let's" was crowding the full stop of "experts.". This is the missing
     optical space, and it is em-relative so it survives the fluid sizes. */
  padding-left: 0.07em;
  color: var(--amber-ink);
}

.h2 {
  margin: 0;
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--fs-d3);
  line-height: var(--lh-tight);
  letter-spacing: var(--tr-tight);
  text-wrap: balance;
}
.h2--cta {
  font-weight: 800;
  font-size: var(--fs-d2);
  line-height: 1.06;
  letter-spacing: var(--tr-display);
  color: #fff;
}
.h2--panel {
  font-weight: 800;
  font-size: var(--fs-d3);
  line-height: 1.08;
  letter-spacing: var(--tr-display);
  color: #fff;
}
/* On the dark panels the accent has to come off --amber-ink, which is pitched
   for light ground and measures under 3:1 on navy. */
.h2--cta .accent,
.h2--panel .accent,
.section-head--light .h2 .accent { color: var(--amber-light); }

.lede {
  margin: 0;
  font-size: var(--fs-xl);
  line-height: var(--lh-body);
  color: var(--muted);
  /* 62ch rather than a pixel width: the comfortable measure is a function of
     how many characters fit, not how many pixels, so this one value stays
     right if the face or the reader's base size ever changes. */
  max-width: 62ch;
  text-wrap: pretty;
}
.lede--sm { font-size: var(--fs-lg); }

/* The uppercase micro-label. It appears in a dozen places and used to be
   specified independently in each of them — 0.3em here, 0.24em in the hero,
   0.22em, 0.18em, 0.1em elsewhere. One rule now, one tracking token.

   0.22em, down from 0.3em: letterspacing that wide stops reading as emphasis
   and starts reading as a row of separate letters, and Inter's caps are
   already generously spaced. Weight drops 700 -> 600 for the same reason —
   Inter's 700 caps are heavier than Manrope's were at the same number. */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: var(--tr-caps-wide);
  color: var(--ink-3);
  text-transform: uppercase;
  /* Lining figures: a year or a count inside a caps label should sit on the
     cap line, not hang below it like text figures do. */
  font-variant-numeric: lining-nums tabular-nums;
}
.eyebrow::before {
  content: "";
  width: 26px;
  height: 2px;
  background: var(--amber);
  border-radius: 2px;
  flex: none;
}
.eyebrow--light { color: rgba(255, 255, 255, 0.66); }

.section-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  text-align: center;
}
.section-head .lede { max-width: 560px; }
.section-head--light .lede { color: rgba(255, 255, 255, 0.6); }
.section-head--light .h2 { color: #fff; }

/* --------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 16px 30px;
  border: 1px solid transparent;
  border-radius: 12px;
  font-family: var(--body);
  font-weight: 600;
  font-size: var(--fs-md);
  line-height: 1.2;
  /* A button label is a small run of words that has to read instantly, so it
     gets a touch of positive tracking — the opposite of the display sizes,
     and the reason a single global letter-spacing never works. */
  letter-spacing: 0.005em;
  text-decoration: none;
  cursor: pointer;
  transition: background 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease, color 0.25s ease;
}

.btn--primary {
  background: var(--navy);
  color: #fff;
  box-shadow: 0 12px 28px rgba(28, 44, 76, 0.24);
}
.btn--primary:hover {
  background: var(--blue);
  color: #fff;
  box-shadow: 0 18px 38px rgba(43, 107, 228, 0.32);
}

.btn--ghost {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--line-strong);
}
.btn--ghost:hover {
  color: var(--ink);
  border-color: rgba(43, 107, 228, 0.5);
  box-shadow: 0 10px 26px rgba(11, 31, 58, 0.1);
}

/* 700, not the 800 this used to ask for. The old build loaded Manrope only to
   700, so every amber CTA on the site shipped a synthesised bold; with
   font-synthesis-weight off that would now silently render as 400. Inter does
   have a real 800 and it is loaded — but 700 is the right weight for a label
   this small on a saturated fill, and one weight not requested is one weight
   not downloaded. */
.btn--amber {
  background: var(--amber);
  color: var(--navy);
  font-weight: 700;
  box-shadow: 0 14px 32px rgba(220, 140, 52, 0.3);
}
.btn--amber:hover {
  background: var(--amber-light);
  color: var(--navy);
  box-shadow: 0 20px 44px rgba(220, 140, 52, 0.42);
}

/* white button sitting on a navy panel */
.btn--light {
  background: #fff;
  color: var(--ink);
  padding: 15px 30px;
  box-shadow: 0 14px 32px rgba(0, 0, 0, 0.3);
}
.btn--light:hover {
  color: var(--ink);
  box-shadow: 0 20px 44px rgba(0, 0, 0, 0.4);
}

/* outlined button sitting on a navy panel */
.btn--outline-light {
  background: transparent;
  color: #fff;
  border-color: rgba(255, 255, 255, 0.28);
}
.btn--outline-light:hover {
  color: #fff;
  border-color: rgba(255, 255, 255, 0.6);
  background: rgba(255, 255, 255, 0.06);
}

.btn--sm { padding: 13px 24px; font-size: var(--fs-sm); border-radius: 11px; }

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  align-items: center;
}
.btn-row--center { justify-content: center; }

.link-arrow {
  font-weight: 600;
  font-size: var(--fs-md);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.link-arrow span { font-family: var(--display); }
.link-arrow--sm { font-size: var(--fs-sm); margin-top: 2px; }
/* Same WCAG 2.2 AA 2.5.8 floor the footer columns were given (see .footer__col a):
   a line box is all the height an inline anchor gets, and at --fs-sm that came to
   23px — "Open in Google Maps →" and "Chat on WhatsApp →" on the contact cards
   both measured 193x23. inline-flex is already the display, so a min-height with
   the text centred adds one pixel and moves nothing. */
.link-arrow { min-height: 24px; }

/* --------------------------------------------------------------------------
   Navigation
   -------------------------------------------------------------------------- */
.nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  background: rgba(248, 249, 251, 0.85);
  border-bottom: 1px solid var(--line);
}

.nav__inner {
  max-width: var(--maxw);
  margin: 0 auto;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 0 var(--gutter);
}

.nav__logo {
  display: flex;
  align-items: center;
  text-decoration: none;
  flex: none;
}
.nav__logo img {
  height: 40px;
  width: auto;
  display: block;
}

.nav__links {
  display: flex;
  align-items: center;
  gap: 32px;
}

.nav__item {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.nav__link {
  font-weight: 500;
  font-size: var(--fs-sm);
  letter-spacing: 0.005em;
  color: var(--ink-2);
  text-decoration: none;
  transition: color 0.2s ease;
}
.nav__link:hover { color: var(--blue); }

.nav__item--active .nav__link { color: var(--blue); }
.nav__item--active::after {
  content: "";
  position: absolute;
  bottom: -9px;
  width: 18px;
  height: 2.5px;
  background: var(--amber);
  border-radius: 2px;
}

.nav__cta {
  background: var(--navy);
  color: #fff;
  padding: 11px 22px;
  border-radius: 11px;
  font-weight: 600;
  font-size: var(--fs-sm);
  text-decoration: none;
  box-shadow: 0 6px 16px rgba(11, 31, 58, 0.18);
  transition: background 0.25s ease, box-shadow 0.25s ease;
  white-space: nowrap;
}
.nav__cta:hover {
  background: var(--blue);
  color: #fff;
  box-shadow: 0 10px 24px rgba(43, 107, 228, 0.32);
}
/* On the contact page the CTA *is* the current page — there is no separate
   "Contact Us" nav link — so it carries the active state itself. */
.nav__cta[aria-current="page"] {
  background: var(--blue);
  box-shadow: 0 10px 24px rgba(43, 107, 228, 0.32);
}

/* hamburger — hidden until the desktop nav runs out of room */
.nav__burger {
  display: none;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--line-strong);
  border-radius: 11px;
  background: var(--surface);
  cursor: pointer;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: 4px;
  transition: border-color 0.2s ease;
}
.nav__burger:hover { border-color: rgba(43, 107, 228, 0.5); }
.nav__burger span {
  display: block;
  width: 18px;
  height: 2px;
  border-radius: 2px;
  background: var(--ink-2);
  transition: transform 0.28s cubic-bezier(0.22, 0.61, 0.36, 1), opacity 0.2s ease;
}
.nav__burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.nav__burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav__burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

/* The drawer needs a scroller of its own. site.js:24 locks <html> while it is
   open, so anything that does not fit inside the drawer is unreachable — there
   is nothing behind it left to scroll. At 285px tall against a 360px landscape
   viewport it currently clears by one pixel, and that pixel is measured with no
   browser chrome; with Safari's bar on screen the visible height is nearer 330
   and the "Get in Touch" CTA at the bottom goes under the edge with no way to
   reach it. Capping it against the viewport and giving it its own overflow costs
   nothing while it fits and is the only thing that saves it when it doesn't.
   `100vh` first for engines without svh; overscroll-behavior stops a flick at
   the end of the list from chaining into the locked page behind. */
.nav__drawer {
  display: none;
  border-top: 1px solid var(--line);
  background: rgba(248, 249, 251, 0.98);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  max-height: calc(100vh - var(--nav-h));
  max-height: calc(100svh - var(--nav-h));
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
.nav__drawer[data-open="true"] { display: block; }
.nav__drawer-inner {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 14px var(--gutter) 22px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.nav__drawer .nav__link {
  padding: 14px 4px;
  font-size: var(--fs-lg);
  border-bottom: 1px solid var(--line);
}
.nav__drawer .nav__link[aria-current="page"] {
  color: var(--blue);
  border-left: 3px solid var(--amber);
  padding-left: 12px;
}
.nav__drawer .nav__cta {
  margin-top: 16px;
  text-align: center;
  padding: 15px 22px;
  font-size: var(--fs-md);
}

/* --------------------------------------------------------------------------
   Sections & shared surfaces
   -------------------------------------------------------------------------- */
.section { padding: 110px 0; }
.section--tight { padding: 96px 0; }
.section--band {
  background: var(--surface);
  border-top: 1px solid rgba(11, 31, 58, 0.06);
  border-bottom: 1px solid rgba(11, 31, 58, 0.06);
}
.section--dark {
  position: relative;
  overflow: hidden;
  background: var(--navy);
  padding: 110px 0;
}
.section--dark > .container { position: relative; z-index: 1; }

/* Opts a *light* section into hosting an ambient canvas — the pairing that
   `.section--dark` already carries inline above.
   Both properties are load-bearing, not cosmetic. `position: relative` is the
   containing block the canvas is absolutely positioned against, and it is also
   the box ambient.js measures to size the backing store (it reads the canvas's
   parentElement) — without it, both would resolve against some ancestor and the
   traces would be drawn for the wrong rectangle. `overflow: hidden` keeps a
   trace that wanders past the edge from widening the page.
   The content children need the positioned/z-index treatment so they paint
   *above* the canvas: the canvas is a positioned element, so a static sibling
   later in the DOM would otherwise render underneath it. Listed explicitly
   rather than as `> *` — the dark sections' .glow/.gridlines are absolutely
   positioned, and blanket `position: relative` would break them. */
.section--ambient { position: relative; overflow: hidden; }
.section--ambient > .container,
.section--ambient > .section-head,
.section--ambient > .marquee { position: relative; z-index: 1; }

.stack { display: flex; flex-direction: column; gap: 52px; }

/* `minmax(min(Npx, 100%), 1fr)` and NOT `minmax(Npx, 1fr)` — the bare form is a
   floor the track cannot go under, so on a screen narrower than N the track stays
   N wide inside a container that is not, and the overflow is INVISIBLE: the
   `html { overflow-x: clip }` that stops the page scrolling sideways also removes
   any way to reach what is now off-screen, so the content is simply chopped.
   Measured before the fix, with every reveal settled so no transform was in play:
   at a 320px viewport the container is 280px and `.split` still laid a 340px
   track, putting the right edge of every card, photo and form at 369px — 57px of
   about/contact/products gone, including the right side of the enquiry form. 340px
   viewport lost 37px, 360px lost 18px, 375px lost 3px, and 390px was the first
   width that was clean. `min()` makes the floor "N, or the full container if that
   is smaller", so above the threshold the layout is bit-identical (verified
   unchanged at 390/430/520/768/900/1024/1280/1440) and below it the track simply
   fits. Applied to every fixed-minimum grid, not just the two that were measurably
   clipping, because the failure is silent and the fix costs nothing where it does
   not bind. */
.grid { display: grid; gap: 20px; }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); }
.grid--cards { grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); gap: 22px; }
.grid--cats { grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); }
.grid--features { grid-template-columns: repeat(auto-fit, minmax(min(250px, 100%), 1fr)); }

.split {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));   /* min() — see .grid--3 */
  gap: 64px;
  align-items: center;
}
.split--wide { gap: 72px; }
.split--product { gap: 60px; }

.stack-left {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 22px;
}

/* --------------------------------------------------------------------------
   Ambient background decoration
   -------------------------------------------------------------------------- */
.glow {
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
}
.glow--blue {
  top: -180px;
  right: -120px;
  width: 580px;
  height: 580px;
  background: radial-gradient(circle, rgba(43, 107, 228, 0.11), rgba(43, 107, 228, 0) 65%);
  animation: glowDrift 16s ease-in-out infinite alternate;
}
.glow--amber {
  bottom: -220px;
  left: -160px;
  width: 540px;
  height: 540px;
  background: radial-gradient(circle, rgba(220, 140, 52, 0.09), rgba(220, 140, 52, 0) 65%);
  animation: glowDrift 21s ease-in-out infinite alternate-reverse;
}
.glow--sm {
  top: -180px;
  right: -120px;
  width: 540px;
  height: 540px;
  background: radial-gradient(circle, rgba(43, 107, 228, 0.1), rgba(43, 107, 228, 0) 65%);
  animation: glowDrift 17s ease-in-out infinite alternate;
}

.gridlines {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(rgba(11, 31, 58, 0.035) 1px, transparent 1px),
    linear-gradient(90deg, rgba(11, 31, 58, 0.035) 1px, transparent 1px);
  background-size: 56px 56px;
  -webkit-mask-image: radial-gradient(ellipse 90% 80% at 50% 30%, #000 40%, transparent 100%);
  mask-image: radial-gradient(ellipse 90% 80% at 50% 30%, #000 40%, transparent 100%);
}
.gridlines--light {
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
  -webkit-mask-image: none;
  mask-image: none;
}

/* --------------------------------------------------------------------------
   Hero
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  overflow: hidden;
  padding: 158px 0 110px;
}
.hero__inner {
  position: relative;
  z-index: 1;
}
.hero__col {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 26px;
}

/* `.pill-badge`, `.hero__note` and `.h1--hero` used to sit here. All three were
   type rules belonging to the light hero that hero-cinematic.css replaced on
   16 Jul 2026, and none of them was referenced by any HTML file — checked
   across all six pages. They were removed with the retype rather than carried
   forward, because stale hardcoded px sizes in a file whose whole premise is
   now "every size comes from the scale" are worse than no rule at all: the
   next person reads them as part of the system.
   `.scroll-cue` is also unreferenced but was left alone — it is a whole
   component with its own markup contract, not a loose type rule. */

/* framed photo with its offset outline + floating stat cards */
.framed {
  position: relative;
  margin: 20px 0 30px;
}
/* product rows have no floating stat cards to make room for */
.framed--flush { margin: 0; }

.framed__outline {
  position: absolute;
  top: 30px;
  left: 30px;
  right: -26px;
  bottom: -26px;
  border: 2px solid rgba(43, 107, 228, 0.22);
  border-radius: 22px;
}
/* offset toward the bottom-right, for a photo on the left of its row */
.framed__outline--sm {
  top: 24px;
  left: 24px;
  right: -22px;
  bottom: -22px;
  border-radius: 20px;
}
/* mirrored: offset toward the bottom-left, for a photo on the right */
.framed__outline--flip {
  top: 24px;
  left: -22px;
  right: 24px;
  bottom: -22px;
  border-radius: 20px;
}
.framed__photo {
  position: relative;
  background: repeating-linear-gradient(135deg, #E9EDF3 0px, #E9EDF3 12px, #F2F5F9 12px, #F2F5F9 24px);
  border: 1px solid var(--line);
  border-radius: 22px;
  aspect-ratio: 5 / 4;
  overflow: hidden;
}
.framed__photo--sm { border-radius: 20px; }
.framed__photo img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* The wrapper owns the position and the idle float; the body owns the surface
   and the hover. They have to be separate elements — a running `animation` on
   `transform` beats a `:hover { transform }` on the same element, so a card
   that bobs can't also lift. */
.float-card {
  position: absolute;
  bottom: -26px;
  left: -24px;
  animation: floatY 5.5s ease-in-out infinite alternate;
}
.float-card--dark {
  bottom: auto;
  left: auto;
  top: -18px;
  right: -10px;
  animation: floatY 6.5s ease-in-out 0.8s infinite alternate;
}

.float-card__body {
  border-radius: 16px;
  padding: 18px 24px 18px 27px;
  box-shadow: 0 20px 44px rgba(11, 31, 58, 0.14);
  cursor: default;
}
.float-card__body:hover {
  transform: translateY(-6px) scale(1.035);
  box-shadow: var(--shadow-hover-blue);
  border-color: rgba(43, 107, 228, 0.42);
}

/* a standing amber rail down the leading edge, which thickens on hover */
.float-card__body::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  z-index: 1;
  background: linear-gradient(180deg, var(--amber), var(--amber-light));
  opacity: 0.75;
  transition: opacity 0.4s ease, width 0.45s var(--ease-out-quint);
}
.float-card__body:hover::after { opacity: 1; width: 5px; }

/* This one currently carries a word ("Mussafah"), not a figure — tabular is
   set anyway so the slot behaves like every other display figure if it is ever
   pointed at a number. */
.float-card__num {
  font-family: var(--display);
  font-weight: 800;
  font-size: var(--fs-d4);
  line-height: 1.05;
  letter-spacing: var(--tr-tight);
  font-variant-numeric: tabular-nums lining-nums;
  transition: color 0.4s ease;
}
/* The "+" springs up as the card lifts. It used to warm to --amber on hover as
   well, but this card face is light: --amber is 2.7:1 there and --amber-dark
   3.4:1, so the hover was making the glyph *less* legible than its resting
   state. --amber-ink is the only text-safe amber on light ground, and it holds
   through the hover — the spring is what carries the interaction now. */
.float-card__num .plus {
  color: var(--amber-ink);
  display: inline-block;
  transition: transform 0.55s var(--ease-spring);
}
.float-card__body:hover .float-card__num .plus {
  transform: translateY(-3px) scale(1.16);
}
.float-card__label {
  font-size: var(--fs-micro);
  font-weight: 600;
  color: var(--muted);
  letter-spacing: var(--tr-caps);
  text-transform: uppercase;
}

/* the dark badge sits on the *light* hero, so it needs a solid navy face
   rather than the translucent .card--dark used on the dark sections */
.float-card--dark .float-card__body {
  background: linear-gradient(158deg, #26395E 0%, #1C2C4C 58%, #172640 100%);
  border-color: rgba(255, 255, 255, 0.12);
  color: #fff;
  border-radius: 14px;
  padding: 13px 20px 13px 23px;
  box-shadow: 0 16px 36px rgba(11, 31, 58, 0.28);
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--fs-d6);
  line-height: 1.2;
  letter-spacing: var(--tr-snug);
}
.float-card--dark .float-card__body:hover {
  transform: translateY(-6px) scale(1.04);
  box-shadow: var(--shadow-hover-amber);
  border-color: rgba(220, 140, 52, 0.65);
}
.float-card--dark .float-card__body::before {
  background: radial-gradient(
    240px circle at var(--mx, 50%) var(--my, 0%),
    rgba(232, 160, 76, 0.24),
    rgba(232, 160, 76, 0.09) 45%,
    transparent 72%
  );
}

.scroll-cue {
  position: absolute;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 9px;
}
.scroll-cue__label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.32em;
  color: var(--muted-2);
}
.scroll-cue__rail {
  width: 2px;
  height: 40px;
  background: rgba(11, 31, 58, 0.1);
  border-radius: 2px;
  position: relative;
  overflow: hidden;
  display: block;
}
.scroll-cue__dot {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 11px;
  background: var(--amber);
  border-radius: 2px;
  animation: scrollDot 1.9s ease-in-out infinite;
  display: block;
}

/* --------------------------------------------------------------------------
   Cards
   -------------------------------------------------------------------------- */
.stat-card {
  border-radius: 18px;
  padding: 30px 34px;
}
/* Tabular, lining figures — this is a live counter. ambient.js rewrites the
   text from "0" up to the target one frame at a time, and with proportional
   figures every digit has its own advance width, so the number visibly jitters
   and shoves the "+" sideways the whole way up. Tabular locks all ten digits
   to one width, which is exactly what the feature is for. */
.stat-card__num {
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(2.375rem, 3.4vw, 2.875rem); /* 38 -> 46 */
  line-height: 1.02;
  letter-spacing: var(--tr-tight);
  font-variant-numeric: tabular-nums lining-nums;
  color: var(--ink);
}
/* the "+" springs up as the card lifts — it no longer warms on hover, for the
   same reason as .float-card__num .plus above: on this light card face every
   amber but --amber-ink is under the 3:1 bar even for large type. */
.stat-card__num .plus {
  color: var(--amber-ink);
  display: inline-block;
  transition: transform 0.55s var(--ease-spring);
}
.stat-card:hover .stat-card__num .plus {
  transform: translateY(-5px) scale(1.18);
}
.stat-card__num--amber { color: var(--amber-ink); }
.stat-card__title { font-weight: 600; font-size: var(--fs-md); line-height: var(--lh-snug); margin-top: 8px; }
.stat-card__sub { font-size: var(--fs-xs); line-height: var(--lh-snug); color: var(--muted-2); margin-top: 5px; }

.value-card {
  border-radius: 20px;
  padding: 36px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.value-card__icon {
  width: 54px;
  height: 54px;
  background: var(--amber-tint);
  border: 1px solid rgba(220, 140, 52, 0.25);
  border-radius: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition:
    transform 0.6s var(--ease-spring),
    background 0.4s ease,
    border-color 0.4s ease,
    box-shadow 0.4s ease;
}
/* the icon tile cants over and warms up — the card's one bit of personality */
.value-card:hover .value-card__icon {
  transform: translateY(-3px) rotate(-6deg) scale(1.07);
  background: linear-gradient(150deg, #FBEFDC, #F4E1C4);
  border-color: rgba(220, 140, 52, 0.5);
  box-shadow: 0 10px 22px rgba(220, 140, 52, 0.24);
}
.value-card__icon img {
  width: 32px;
  height: 32px;
  object-fit: contain;
  display: block;
}
.value-card h3 {
  margin: 0;
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--fs-d5);
  line-height: var(--lh-tight);
  letter-spacing: var(--tr-snug);
  transition: color 0.35s ease;
}
.value-card:hover h3 { color: var(--blue); }
.value-card p {
  margin: 0;
  font-size: var(--fs-md);
  line-height: var(--lh-body);
  color: var(--muted);
  text-wrap: pretty;
}

/* dark category tiles on the home page */
.cat-card {
  display: flex;
  flex-direction: column;
  gap: 14px;
  border-radius: 18px;
  padding: 30px;
  text-decoration: none;
}
.cat-card__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
/* Now a real mono rather than whatever the OS happened to supply, so the
   index numerals read as catalogue data — the same register as the part
   numbers on the products page. */
.cat-card__num {
  font-family: var(--mono);
  font-weight: 500;
  font-size: var(--fs-xs);
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums lining-nums;
  color: var(--amber-light);
  transition: letter-spacing 0.45s var(--ease-out-quint);
}
.cat-card:hover .cat-card__num { letter-spacing: 0.16em; }
/* these tiles are links — give them the affordance the design never had */
.cat-card__go {
  font-family: var(--display);
  font-size: var(--fs-d6);
  line-height: 1;
  color: var(--amber-light);
  opacity: 0;
  transform: translateX(-10px);
  transition: opacity 0.4s ease, transform 0.5s var(--ease-out-quint);
}
.cat-card:hover .cat-card__go { opacity: 1; transform: translateX(0); }
.cat-card__title {
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--fs-d5);
  line-height: var(--lh-tight);
  letter-spacing: var(--tr-snug);
  color: #fff;
}
.cat-card__desc {
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.6);
  text-wrap: pretty;
}
.cat-card__meta {
  font-size: var(--fs-xs);
  font-weight: 500;
  color: rgba(255, 255, 255, 0.45);
  transition: color 0.4s ease;
}
.cat-card:hover .cat-card__meta { color: rgba(255, 255, 255, 0.66); }

/* about page: "what sets us apart" */
.feature-card {
  background: var(--card-face-soft);
  border-radius: 18px;
  padding: 30px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.feature-card__title {
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--fs-d6);
  line-height: var(--lh-tight);
  letter-spacing: var(--tr-snug);
  transition: color 0.35s ease;
}
.feature-card:hover .feature-card__title { color: var(--blue); }
/* the bullet keeps turning past its resting 45° */
.feature-card .diamond {
  transition: transform 0.6s var(--ease-spring), background 0.4s ease;
}
.feature-card:hover .diamond {
  transform: rotate(135deg) scale(1.25);
  background: var(--amber);
}
.feature-card__desc {
  font-size: var(--fs-sm);
  line-height: 1.62;
  color: var(--muted);
  text-wrap: pretty;
}

/* the little rotated-square bullet used throughout */
.diamond {
  width: 9px;
  height: 9px;
  background: var(--amber);
  transform: rotate(45deg);
  border-radius: 2px;
  flex: none;
  display: block;
}
.diamond--blue { background: var(--blue); width: 10px; height: 10px; }
.diamond--sm { width: 8px; height: 8px; background: var(--blue); }

.check-panel {
  background: var(--card-face-soft);
  border: 1px solid var(--line);
  border-radius: 20px;
  padding: 38px 40px;
  display: flex;
  flex-direction: column;
  gap: 22px;
}
.check-panel__row {
  display: flex;
  align-items: flex-start;
  gap: 14px;
}
.check-panel__row .diamond {
  margin-top: 6px;
  transition: transform 0.6s var(--ease-spring), background 0.35s ease;
}
.check-panel__row:hover .diamond {
  transform: rotate(135deg) scale(1.25);
  background: var(--blue);
}
.check-panel__row span:last-child {
  font-weight: 500;
  font-size: var(--fs-md);
  line-height: 1.55;
  transition: color 0.3s ease;
}
.check-panel__row:hover span:last-child { color: var(--blue); }

/* --------------------------------------------------------------------------
   Brand marquee + roster
   -------------------------------------------------------------------------- */
.brands { padding: 104px 0; overflow: hidden; }
.brands__head { margin-bottom: 48px; padding: 0 var(--gutter); }
.marquee { position: relative; }
.marquee::before,
.marquee::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 140px;
  z-index: 2;
  pointer-events: none;
}
.marquee::before { left: 0; background: linear-gradient(90deg, var(--bg), rgba(246, 247, 249, 0)); }
.marquee::after { right: 0; background: linear-gradient(270deg, var(--bg), rgba(246, 247, 249, 0)); }

/* The track used to be driven by a CSS transform, which a scroll container
   can't share — a transformed row and a native scroll fight each other. So the
   viewport is a real scroller and ambient.js advances its scrollLeft instead.
   That buys touch-swipe, trackpad and drag for free, and the row still loops
   seamlessly because the list is rendered twice. */
.marquee__viewport {
  overflow-x: auto;
  overflow-y: hidden;
  /* vertical room for the tile's hover lift and its shadow — a scroll container
     clips on both axes, so without this the lift would be cut off */
  padding: 16px 0 30px;
  cursor: grab;
  scrollbar-width: none;              /* Firefox */
  -ms-overflow-style: none;
  overscroll-behavior-x: contain;     /* don't let an overscroll trigger back-nav */
}
.marquee__viewport::-webkit-scrollbar { display: none; }
.marquee__viewport.is-dragging {
  cursor: grabbing;
  user-select: none;
}
.marquee__viewport--second { padding: 6px 0 10px; }

.marquee__row {
  display: flex;
  width: max-content;
}

/* Registering the angle is what lets it be *animated* — an unregistered custom
   property can only flip between keyframes, not interpolate. Where @property
   isn't supported the ring simply sits still instead of tracing, which is a fine
   place to land. */
@property --trace {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}

.brand-tile {
  position: relative;
  isolation: isolate;
  overflow: hidden;            /* keeps the sheen inside the tile */
  flex: none;
  margin-right: 16px;
  width: 172px;
  height: 82px;
  background: var(--card-face);
  border-radius: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 30px;
  /* the resting hairline is an inset shadow, not a border: an absolutely
     positioned ring sits against the padding box, so a real border would leave
     it floating a pixel inside the edge instead of on it */
  box-shadow: inset 0 0 0 1px rgba(11, 31, 58, 0.09);
  transition:
    transform 0.5s var(--ease-out-quint),
    box-shadow 0.5s var(--ease-out-quint);
}

/* Logos keep their own colours at rest — no grayscale. */
.brand-tile img {
  max-width: 100%;
  max-height: 36px;
  width: auto;
  height: auto;
  object-fit: contain;
  display: block;
  transition: transform 0.6s var(--ease-out-quint);
}

.brand-tile:hover {
  transform: translateY(-6px);
  box-shadow:
    inset 0 0 0 1px rgba(43, 107, 228, 0.16),
    0 18px 40px rgba(11, 31, 58, 0.16);
}
.brand-tile:hover img { transform: scale(1.07); }

/* A current runs the tile's edge — the same idea as the PCB pulses in the
   canvas behind the hero. A conic gradient masked down to just the border ring,
   with the gradient's start angle spinning. */
.brand-tile::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: inherit;
  padding: 1.5px;
  pointer-events: none;
  background: conic-gradient(
    from var(--trace),
    transparent 0deg,
    transparent 205deg,
    var(--amber) 285deg,
    var(--blue) 335deg,
    transparent 360deg
  );
  /* paint the padding box and the border box, then subtract one from the other:
     what's left is a ring exactly `padding` thick */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  opacity: 0;
  transition: opacity 0.35s ease;
}
.brand-tile:hover::before {
  opacity: 1;
  animation: traceRun 1.5s linear infinite;
}

/* ...and a specular sweep across the glass as it lifts */
.brand-tile::after {
  content: "";
  position: absolute;
  top: -60%;
  bottom: -60%;
  left: 0;
  width: 45%;
  z-index: 1;
  pointer-events: none;
  opacity: 0;
  transform: translateX(-280%) rotate(18deg);
  background: linear-gradient(
    90deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, 0.62) 50%,
    rgba(255, 255, 255, 0) 100%
  );
}
.brand-tile:hover::after { animation: sheenSweep 0.95s var(--ease-out-quint) forwards; }

@keyframes traceRun {
  to { --trace: 360deg; }
}
@keyframes sheenSweep {
  0%   { opacity: 0; transform: translateX(-280%) rotate(18deg); }
  22%  { opacity: 1; }
  100% { opacity: 0; transform: translateX(560%) rotate(18deg); }
}

.brand-chip {
  flex: none;
  margin-right: 14px;
  padding: 13px 26px;
  background: var(--surface);
  border: 1px solid rgba(11, 31, 58, 0.09);
  border-radius: 999px;
  font-family: var(--body);
  font-weight: 600;
  font-size: var(--fs-xs);
  letter-spacing: var(--tr-caps);
  text-transform: uppercase;
  color: var(--ink-3);
  white-space: nowrap;
}

.roster {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: center;
  max-width: 1000px;
  margin: 0 auto;
}
.roster__pill {
  padding: 12px 24px;
  background: var(--card-face-soft);
  border: 1px solid rgba(11, 31, 58, 0.09);
  border-radius: 999px;
  /* Caps at 12–13px are a body-face job, not a display-face one: Bricolage's
     heavy display forms were drawn for 40px+ and go muddy this small, while
     Inter is drawn with an optical-size axis that opens the letterforms up as
     they shrink. Both pill sets moved off --display for that reason. */
  font-family: var(--body);
  font-weight: 600;
  font-size: var(--fs-xs);
  letter-spacing: var(--tr-caps);
  text-transform: uppercase;
  color: var(--ink-3);
  transition:
    border-color 0.25s ease,
    color 0.25s ease,
    transform 0.45s var(--ease-out-quint),
    box-shadow 0.45s var(--ease-out-quint);
}
.roster__pill:hover {
  border-color: rgba(43, 107, 228, 0.55);
  color: var(--blue);
  transform: translateY(-3px);
  box-shadow: 0 10px 22px rgba(43, 107, 228, 0.14);
}

/* --------------------------------------------------------------------------
   Logo roster (About) — the same stadium pill as .roster__pill, carrying the
   real logo instead of the brand's name.

   The pill has to size itself to the artwork, because these logos are nowhere
   near a common aspect ratio: MESC is 0.59:1 and Raychem is 8.72:1, a 15x
   spread. So the tile is a fixed HEIGHT with a min-width floor and free width,
   and the image is bounded on BOTH axes — max-height keeps the short-and-wide
   ones from towering, max-width keeps the long ones from stretching the pill
   across the row. object-fit: contain means whichever bound binds first, the
   logo stays in proportion.
   -------------------------------------------------------------------------- */
.logo-roster {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  justify-content: center;
  max-width: 1000px;
  margin: 0 auto;
}
.logo-roster__tile {
  flex: none;
  height: 62px;
  min-width: 126px;
  padding: 0 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--card-face-soft);
  border: 1px solid rgba(11, 31, 58, 0.09);
  border-radius: 999px;
  transition:
    border-color 0.25s ease,
    transform 0.45s var(--ease-out-quint),
    box-shadow 0.45s var(--ease-out-quint);
}
.logo-roster__tile img {
  max-height: 26px;
  max-width: 148px;
  width: auto;
  height: auto;
  object-fit: contain;
  display: block;
  transition: transform 0.5s var(--ease-out-quint);
}
.logo-roster__tile:hover {
  border-color: rgba(43, 107, 228, 0.55);
  transform: translateY(-3px);
  box-shadow: 0 10px 22px rgba(43, 107, 228, 0.14);
}
.logo-roster__tile:hover img { transform: scale(1.06); }

/* Optical sizing, and it is not optional on a wall this mixed. Capping every
   logo at one max-height means the cap only binds on the WIDE marks; a square
   one is limited by nothing and ends up a speck. Siemens is 6.07:1 and MESC is
   0.59:1 — a 10x spread — so at a shared 26px height MESC carried roughly a
   tenth of Siemens' ink and read as a rendering fault rather than a logo.
   Near-square marks are therefore allowed more height for the same optical
   weight. Tiers rather than per-logo values, so a new logo just picks a class:
     --sq   aspect < 1.6   (MESC, MK, D-Link, KDK)
     --cmp  1.6 - 2.6      (Flextop, 3M, Crane, Belden, Gulf, Elsewedy)
     default, 2.6+         everything else */
.logo-roster__tile--sq img  { max-height: 42px; }
.logo-roster__tile--cmp img { max-height: 34px; }

/* The brands with no logo artwork follow underneath as the plain name pills
   they already were. Small caption so the split reads as deliberate rather
   than as a row that failed to load. */
.roster--rest { margin-top: 30px; }
.roster-caption {
  display: block;
  text-align: center;
  margin: 0 auto 16px;
  font-family: var(--body);
  font-weight: 600;
  font-size: var(--fs-micro);
  letter-spacing: var(--tr-caps-wide);
  text-transform: uppercase;
  color: var(--muted-2);
}

/* --------------------------------------------------------------------------
   CTA panel (rounded navy card used on About / Products)
   -------------------------------------------------------------------------- */
.cta-panel {
  max-width: var(--maxw);
  margin: 0 auto;
  background: var(--navy);
  border-radius: 24px;
  padding: 72px 48px;
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  text-align: center;
}
.cta-panel > *:not(canvas) { position: relative; z-index: 1; }
.cta-panel p {
  margin: 0;
  font-size: var(--fs-lg);
  line-height: var(--lh-body);
  color: rgba(255, 255, 255, 0.66);
  max-width: 56ch;
  text-wrap: pretty;
}

.cta-wide {
  position: relative;
  z-index: 1;
  max-width: 800px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  text-align: center;
}
.cta-wide p {
  margin: 0;
  font-size: var(--fs-lg);
  line-height: var(--lh-body);
  color: rgba(255, 255, 255, 0.66);
  max-width: 58ch;
  text-wrap: pretty;
}

/* --------------------------------------------------------------------------
   Products page
   -------------------------------------------------------------------------- */
.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 6px;
}
.chip {
  padding: 10px 20px;
  background: var(--surface);
  border: 1px solid rgba(11, 31, 58, 0.12);
  border-radius: 999px;
  font-weight: 500;
  font-size: var(--fs-sm);
  color: var(--ink-2);
  text-decoration: none;
  transition: border-color 0.2s ease, color 0.2s ease;
}
.chip:hover { border-color: rgba(43, 107, 228, 0.6); color: var(--blue); }

.prod { scroll-margin-top: 96px; padding: 88px 0; }
.prod--first { padding: 64px 0; }

.prod__copy {
  display: flex;
  flex-direction: column;
  gap: 18px;
}
/* The section kicker reads as a catalogue reference, so it is set in the mono
   and given caps — the one register on this site that is allowed to look like
   it came off a datasheet. */
.prod__kicker {
  font-family: var(--mono);
  font-weight: 500;
  font-size: var(--fs-xs);
  color: var(--blue);
  letter-spacing: var(--tr-caps);
  text-transform: uppercase;
}
.prod__title {
  margin: 0;
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--fs-d4);
  line-height: var(--lh-tight);
  letter-spacing: var(--tr-tight);
  text-wrap: balance;
}
.prod__desc {
  margin: 0;
  font-size: var(--fs-lg);
  line-height: var(--lh-body);
  color: var(--muted);
  max-width: 58ch;
  text-wrap: pretty;
}

.spec-list {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  list-style: none;
}
.spec-list li {
  display: flex;
  align-items: flex-start;
  gap: 13px;
  padding: 13px 0;
  border-bottom: 1px solid rgba(11, 31, 58, 0.07);
}
.spec-list li:last-child { border-bottom: none; }
.spec-list .diamond--sm { margin-top: 7px; }
.spec-list__name { font-weight: 600; font-size: var(--fs-md); line-height: var(--lh-snug); }
.spec-list__brands {
  font-family: var(--mono);
  font-weight: 500;
  font-size: var(--fs-micro);
  line-height: 1.5;
  color: var(--muted-2);
  margin-top: 3px;
}

/* --------------------------------------------------------------------------
   Per-category brand board (Products)

   This started life as a pill cloud tucked into the dead space under the section
   photo, and it looked wrong for three compounding reasons:
     - `.split` is `align-items: center`, so the photo floated in the middle of
       row 1 while the board sat in row 2 — leaving a ~250px hole between them.
     - 24 logos in a 566px column wrapped to five ragged rows with a single
       orphan on the last, because free-width pills never line up.
     - the column then ran far past the copy beside it, so the whole section
       went lopsided.
   Half the width was simply the wrong container. Spanning the full section
   fixes all three at once: the same 24 logos need three rows instead of five,
   the photo goes back to sitting quietly centred in its own row, and the band
   reads as a deliberate footer to the section rather than a spill.

   Two structural notes:
     - `grid-column: 1 / -1` spans whatever auto-fit produced (three tracks at
       1440 where the third collapses to 0, two below that, one on a phone), so
       there is no breakpoint bookkeeping. This also retires the old --right
       modifier and the `order: -1` interaction that came with pinning columns.
     - the logo cells are a uniform auto-fill grid, NOT wrapping flex: equal
       tracks are what make the rows flush and the last row read as a partly
       filled grid instead of a raggedly abandoned one.
   -------------------------------------------------------------------------- */
.brand-board {
  grid-column: 1 / -1;
  margin-top: 12px;
  padding-top: 30px;
  border-top: 1px solid var(--line);
  display: grid;
  grid-template-columns: minmax(140px, 180px) 1fr;
  gap: 16px 32px;
  align-items: start;
}
.brand-board__label {
  margin: 0;
  padding-top: 9px;             /* optically centres it on the first logo row */
  font-family: var(--body);
  font-weight: 600;
  font-size: var(--fs-micro);
  letter-spacing: var(--tr-caps-wide);
  text-transform: uppercase;
  color: var(--muted-2);
  line-height: 1.5;
}
.brand-board__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(94px, 1fr));
  gap: 8px;
}
.prod__brand {
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 10px;
  background: var(--card-face-soft);
  border: 1px solid rgba(11, 31, 58, 0.09);
  border-radius: 999px;
  transition:
    border-color 0.25s ease,
    transform 0.4s var(--ease-out-quint),
    box-shadow 0.4s var(--ease-out-quint);
}
.prod__brand img {
  max-height: 16px;
  max-width: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  display: block;
}
.prod__brand:hover {
  border-color: rgba(43, 107, 228, 0.5);
  transform: translateY(-2px);
  box-shadow: 0 8px 18px rgba(43, 107, 228, 0.12);
}
/* Same optical tiers as the About wall, and they matter more at this size: at a
   flat 16px cap the square marks were barely legible. See .logo-roster.
   Raised from 26/20 (and the pill from 38px to 44px to house them) so the
   near-square marks carry ink comparable to the widest wordmarks rather than a
   fraction of it: measured against the same 100px cell, a wide mark like Hager
   draws ~1240px2, and KDK now draws ~1090px2 where at the old 26px cap it drew
   ~720px2 — and before its canvas was trimmed, only 31% of even that was ink. */
.prod__brand--sq img  { max-height: 32px; }
.prod__brand--cmp img { max-height: 24px; }

/* `auto-fit` COLLAPSES a track holding no items, and that is precisely how
   `.split` gets its tidy two columns on a wide screen: it lays out three
   340px-minimum tracks and folds the empty third to 0. A board spanning
   `1 / -1` puts an item in that track, so it stops collapsing and all three
   become real — the photo and copy fell from 566px to 357px at 1440, and from
   546 to 344 at 1200. Pinning two explicit tracks is what auto-fit already
   resolved to at every width from 800 up to ~1188 (verified identical at 1024
   and 900), so nothing else shifts; it just denies the third track a reason to
   exist. `minmax(0, 1fr)` rather than `1fr` so a long unbroken string in the
   copy can never push a track past its share. */
@media (min-width: 800px) {
  .split--product { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Same trap, second instance. The 07-09 supply cards share ONE board, so its
   `1 / -1` span puts an item in every track of their `.grid--cards` and not one
   of them may collapse — at 1440 the grid lays out four 280px-minimum tracks
   and the three cards would drop from 385px to 288px each to make room for a
   fourth that holds nothing but the board. Tracks are therefore explicit.
   Three-or-one, with no two-column band on purpose: three cards across two
   columns always strand an orphan, and the client asked for a single column on
   phones. 1000px is where three 280px-minimum tracks stop fitting the
   container (952px of content - 44px of gaps = 302px each), so the switch
   happens exactly when the three-up would otherwise start to cramp. */
.grid--supply { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 1000px) {
  .grid--supply { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* Below this the label moves above the logos and the grid takes the full width.
   The line is at 1100 because the gutter costs the grid ~210px and therefore
   whole ROWS, and the jump is what you notice: at 700 it went 4 rows -> 6, at
   820 still 3 -> 5. At 1100 the 24-logo board is 3 rows on both sides of the
   boundary, so nothing visibly reflows. Anything narrower gives the logos the
   full width and stacks the label on top. */
@media (max-width: 1100px) {
  .brand-board {
    grid-template-columns: 1fr;
    gap: 12px;
    padding-top: 26px;
  }
  .brand-board__label { padding-top: 0; }
  .brand-board__grid { grid-template-columns: repeat(auto-fill, minmax(86px, 1fr)); }
}

/* Phones only. PINNED rather than auto-filled, for the reason the five-across
   version of this rule recorded and which still holds: `auto-fill` derives the
   count from a minimum track width, so any single min changes the count across
   the phone range (the 71px that gave five at 430 gave six by 500 and four at
   390) and the board would change shape from handset to handset. An explicit
   count is the only way it keeps one shape on every phone.

   THREE, not five. Five was chosen to keep the board short, and it did — but
   measured on a 375px screen it made each track 60.6px, which after 7px of side
   padding leaves 46.6px of usable width, and a 12px cap then renders a wordmark
   at 45x10 CSS px. Ten pixels of x-height is not a small logo, it is an
   unreadable one: ELSEWEDY ELECTRIC, CELLPACK, hagergroup and NATIONAL PAINTS
   were illegible mush, and the electrical board carries 24 of them. A brand wall
   nobody can read is doing none of the work it is on the page to do.

   Three tracks give 106px at 375 and 88px at 320, so the caps go back to the
   desktop tier (16 / 24 / 32, the 1 : 1.5 : 2 ratio held exactly) and the pill
   back to the 44px that houses a 32px square mark. Checked at the narrow end:
   at 320 a track is 88px, 74px of it usable, and the widest wordmark in the set
   (340x79, so 4.3:1) wants 69px at a 16px cap — it fits with room to spare.
   The cost is height: the 24-logo board goes from 5 rows to 8, about 155px more
   on the electrical section. That is the right trade — the page already scrolls,
   and a shorter board that cannot be read is not shorter, it is wasted. */
@media (max-width: 520px) {
  .brand-board__grid    { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .prod__brand          { height: 44px; padding: 0 10px; }
  .prod__brand img      { max-height: 16px; }
  .prod__brand--sq img  { max-height: 32px; }
  .prod__brand--cmp img { max-height: 24px; }
}

/* --------------------------------------------------------------------------
   Contact page
   -------------------------------------------------------------------------- */
.contact-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));   /* min() — see .grid--3 */
  gap: 28px;
  align-items: start;
}
.contact-col { display: flex; flex-direction: column; gap: 16px; }

.info-card {
  border-radius: 18px;
  padding: 26px 30px;
  display: flex;
  align-items: flex-start;
  gap: 18px;
}
.info-card__icon {
  width: 46px;
  height: 46px;
  background: var(--amber-tint);
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  transition: transform 0.6s var(--ease-spring), background 0.4s ease, box-shadow 0.4s ease;
}
.info-card:hover .info-card__icon {
  transform: translateY(-2px) rotate(-5deg) scale(1.08);
  background: linear-gradient(150deg, #FBEFDC, #F4E1C4);
  box-shadow: 0 8px 18px rgba(220, 140, 52, 0.22);
}
.info-card__body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}
.info-card__title {
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--fs-d6);
  line-height: var(--lh-tight);
  letter-spacing: var(--tr-snug);
}
.info-card__body span:not(.info-card__title) {
  font-size: var(--fs-sm);
  line-height: 1.62;
  color: var(--muted);
}
/* Phone numbers and the PO box are figures the reader may need to transcribe —
   tabular keeps the digits in even columns so they are easy to read off. */
.info-card__body a:not(.link-arrow) {
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums lining-nums;
  color: var(--muted);
  text-decoration: none;
  /* The tel: and mailto: links on the contact cards, i.e. the two things a
     visitor on a phone is most likely to reach for. They were 23px tall for the
     same reason the footer links were — see .footer__col a for the 24x24 rule
     and why a min-height is all this needs. */
  display: flex;
  align-items: center;
  min-height: 24px;
  transition: color 0.2s ease;
}
.info-card__body a:not(.link-arrow):hover { color: var(--blue); }

/* the icon glyphs are pure CSS in the design — keep them that way */
.glyph-pin { width: 14px; height: 14px; background: var(--blue); transform: rotate(45deg); border-radius: 3px; display: block; }
.glyph-ring { width: 15px; height: 15px; border: 4px solid var(--blue); border-radius: 50%; display: block; }
.glyph-play {
  width: 0; height: 0;
  border-top: 9px solid transparent;
  border-bottom: 9px solid transparent;
  border-left: 15px solid var(--blue);
  margin-left: 3px;
  display: block;
}

.hours-card {
  background: linear-gradient(158deg, #26395E 0%, #1C2C4C 58%, #172640 100%);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 18px;
  padding: 26px 30px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.hours-card__title {
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--fs-d6);
  line-height: var(--lh-tight);
  letter-spacing: var(--tr-snug);
  color: #fff;
}
/* Opening hours are a table of figures in disguise: tabular keeps "8:00 AM"
   and "12:30 PM" the same width so the column edges line up down the card. */
.hours-card__row {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums lining-nums;
}
.hours-card__row dt { color: rgba(255, 255, 255, 0.6); margin: 0; }
.hours-card__row dd { color: #fff; font-weight: 600; margin: 0; }
.hours-card__row dd.is-closed { color: rgba(255, 255, 255, 0.8); }
.hours-card dl { margin: 0; display: flex; flex-direction: column; gap: 12px; }

/* form */
.form-card {
  background: var(--card-face);
  border: 1px solid var(--line);
  border-radius: 22px;
  padding: 40px;
  box-shadow: 0 24px 60px rgba(11, 31, 58, 0.08);
}
.form {
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.form__title {
  margin: 0 0 4px;
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--fs-d4);
  line-height: var(--lh-tight);
  letter-spacing: var(--tr-tight);
}
.form__pair {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));   /* min() — see .grid--3 */
  gap: 16px;
}
/* The field label joins the site's caps micro-label register rather than
   sitting outside it as the one piece of small bold sentence case. Safe to set
   in caps because every label here is one or two short words — this is not a
   treatment to reach for if the form ever grows a real sentence. */
.field {
  display: flex;
  flex-direction: column;
  gap: 9px;
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: var(--tr-caps);
  text-transform: uppercase;
  color: var(--ink-3);
}
.field input,
.field textarea {
  width: 100%;
  padding: 13px 16px;
  border: 1px solid var(--line-strong);
  border-radius: 11px;
  /* Form controls do not inherit type, so the family, size, tracking and case
     all have to be restated — otherwise the caps/tracking set on the .field
     label above would leak nowhere and the input would fall back to the UA's
     13px system font. */
  font-family: var(--body);
  font-size: var(--fs-md);
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
  line-height: 1.5;
  color: var(--ink);
  background: #FBFCFE;
  outline: none;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.field textarea { resize: vertical; min-height: 120px; }
.field input:focus,
.field textarea:focus {
  border-color: var(--blue);
  box-shadow: 0 0 0 3.5px rgba(43, 107, 228, 0.14);
}
.field input:user-invalid,
.field textarea:user-invalid {
  border-color: #DC2626;
  box-shadow: 0 0 0 3.5px rgba(220, 38, 38, 0.12);
}

/* Consent text is a real sentence, so it opts back out of the caps treatment
   the .field labels above carry. */
.field-check {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  font-size: var(--fs-xs);
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
  color: var(--muted);
  line-height: 1.6;
  cursor: pointer;
}
.field-check input {
  margin-top: 2px;
  width: 17px;
  height: 17px;
  accent-color: var(--blue);
  flex: none;
}

/* honeypot — must stay reachable to bots but invisible & unfocusable to humans */
.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form__submit {
  margin-top: 6px;
  background: var(--navy);
  color: #fff;
  border: none;
  box-shadow: 0 12px 28px rgba(28, 44, 76, 0.24);
}
.form__submit:hover {
  background: var(--blue-dark);
  box-shadow: 0 18px 38px rgba(43, 107, 228, 0.4);
}
.form__submit[disabled] {
  opacity: 0.65;
  cursor: progress;
}

.form__error {
  margin: 0;
  padding: 13px 16px;
  border-radius: 11px;
  background: #FEF2F2;
  border: 1px solid rgba(220, 38, 38, 0.25);
  color: #B91C1C;
  font-size: var(--fs-sm);
  font-weight: 500;
  line-height: 1.5;
}

.form-success {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  text-align: center;
  padding: 48px 12px;
}
.form-success__mark {
  width: 64px;
  height: 64px;
  background: #EAF9F0;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.75rem;   /* glyph sizing, not a text step — no scale token */
  color: #16A34A;
  font-weight: 700;
}
.form-success h2 {
  margin: 0;
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--fs-d4);
  line-height: var(--lh-tight);
  letter-spacing: var(--tr-tight);
}
.form-success p {
  margin: 0;
  font-size: var(--fs-md);
  line-height: var(--lh-body);
  color: var(--muted);
  max-width: 44ch;
  text-wrap: pretty;
}

/* map */
.map-frame {
  position: relative;
  border: 1px solid var(--line);
  border-radius: 22px;
  height: 380px;
  overflow: hidden;
  box-shadow: 0 20px 44px rgba(11, 31, 58, 0.08);
}
.map-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* --------------------------------------------------------------------------
   Footer + WhatsApp FAB
   -------------------------------------------------------------------------- */
.footer {
  background: var(--navy);
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  color: rgba(255, 255, 255, 0.62);
}
.footer__inner {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 78px var(--gutter) 56px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr));   /* min() — see .grid--3 */
  gap: 48px;
}
.footer__col { display: flex; flex-direction: column; gap: 14px; }
.footer__col--brand { gap: 18px; }
.footer__logo {
  height: 44px;
  width: auto;
  display: block;
  align-self: flex-start;
  filter: brightness(0) invert(1);
  opacity: 0.92;
}
.footer__blurb {
  margin: 0;
  font-size: var(--fs-sm);
  line-height: 1.7;
  max-width: 38ch;
  text-wrap: pretty;
}
.footer__hours {
  margin: 0;
  font-size: var(--fs-xs);
  line-height: 1.7;
  font-variant-numeric: tabular-nums lining-nums;
  color: rgba(255, 255, 255, 0.45);
}
/* The column headings were hardcoded in caps in the markup ("QUICK LINKS") and
   styled with no text-transform, so the CSS had no say in the case at all.
   text-transform is set here so the rule owns the treatment — if the markup is
   ever fixed to sentence case, the design does not silently change with it. */
.footer__title {
  font-family: var(--body);
  font-weight: 600;
  font-size: var(--fs-micro);
  letter-spacing: var(--tr-caps-wide);
  text-transform: uppercase;
  color: var(--amber-light);
}
.footer a {
  color: rgba(255, 255, 255, 0.62);
  text-decoration: none;
  font-size: var(--fs-sm);
  transition: color 0.2s ease;
}
.footer a:hover { color: #fff; }
/* WCAG 2.2 AA 2.5.8 wants a 24x24 CSS-px target. These sat at 320x23 — wide
   enough, one pixel short vertically, purely because the line box is all the
   height an inline anchor gets. Scoped to .footer__col, not .footer a, so the
   bar and any future inline link in a sentence keep their inline layout: these
   are already stretch-width flex items in a column, so giving them a min-height
   and centring the text costs 1px each and moves nothing else. */
.footer__col a {
  display: flex;
  align-items: center;
  min-height: 24px;
}
.footer__addr {
  font-size: var(--fs-sm);
  line-height: 1.7;
  font-style: normal;
  font-variant-numeric: tabular-nums lining-nums;
}
.footer__bar { border-top: 1px solid rgba(255, 255, 255, 0.08); }
.footer__bar-inner {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 22px var(--gutter);
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  justify-content: space-between;
}
.footer__bar span { font-size: var(--fs-xs); color: rgba(255, 255, 255, 0.4); }

/* z-index 120 puts this ABOVE the nav's 100, which is what a floating action
   button wants everywhere except the one place the nav grows into it. Open the
   drawer on a phone in landscape and the button lands on the right-hand third of
   the full-width "Get in Touch" CTA: hit-testing the CTA at 740x360 returns
   .wa-fab__mark, so that tap opens WhatsApp instead of the contact page, with
   nothing on screen to say the button is covering anything.
   Raising the nav instead would fix the tap and leave the button sitting over an
   open menu, which is not what it should do anyway — a floating shortcut has no
   job while a menu or a modal is open, and both of those already set
   is-scroll-locked (site.js:24, product-detail.js:32). Fading rather than
   display:none so it comes back the way it left, and pointer-events makes the
   tap fall through to the CTA underneath for the whole transition. */
html.is-scroll-locked .wa-fab {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}
.wa-fab {
  position: fixed;
  bottom: 28px;
  right: 28px;
  z-index: 120;
  display: flex;
  align-items: center;
  gap: 13px;
  background: rgba(255, 255, 255, 0.94);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border: 1px solid rgba(11, 31, 58, 0.1);
  border-radius: 16px;
  padding: 13px 22px 13px 17px;
  text-decoration: none;
  box-shadow: 0 8px 28px rgba(11, 31, 58, 0.16), 0 1.5px 4px rgba(11, 31, 58, 0.08);
  /* opacity/visibility are here and not in a second declaration further up: this
     one is later in the block and `transition` is not a list that merges, so a
     separate line would simply be discarded. See html.is-scroll-locked .wa-fab. */
  transition: border-color 0.25s ease, box-shadow 0.25s ease,
              opacity 0.2s ease, visibility 0.2s ease;
}
.wa-fab:hover {
  border-color: rgba(34, 197, 94, 0.55);
  box-shadow: 0 22px 52px rgba(11, 31, 58, 0.24), 0 0 0 4px rgba(34, 197, 94, 0.1);
}
/* The WhatsApp glyph, keeping the pulse the green dot used to carry:
   ::before is the ripple that expands and fades, ::after is the solid disc,
   and the mark itself has no background so the ripple reads outside it. */
.wa-fab__mark {
  position: relative;
  width: 38px;
  height: 38px;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
}
.wa-fab__mark::before,
.wa-fab__mark::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: #25D366;
}
.wa-fab__mark::before { animation: waPulse 2.2s ease-out infinite; }
.wa-fab__mark::after {
  box-shadow: 0 4px 12px rgba(37, 211, 102, 0.4);
  transition: background 0.25s ease;
}
.wa-fab:hover .wa-fab__mark::after { background: #1EBE5B; }
/* pseudo-elements paint over child content, so the glyph needs its own layer */
.wa-fab__mark svg {
  position: relative;
  z-index: 1;
  width: 22px;
  height: 22px;
  display: block;
  fill: #fff;
}
.wa-fab__text { display: flex; flex-direction: column; line-height: 1.3; }
.wa-fab__title {
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--fs-sm);
  letter-spacing: var(--tr-snug);
  color: var(--ink);
}
.wa-fab__sub {
  font-size: var(--fs-micro);
  font-weight: 400;
  color: var(--muted);
}

/* --------------------------------------------------------------------------
   Simple content page (thanks / 404)
   -------------------------------------------------------------------------- */
.plain {
  min-height: 72vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 20px;
  padding: 180px var(--gutter) 120px;
}
.plain p {
  margin: 0;
  font-size: var(--fs-lg);
  line-height: var(--lh-body);
  color: var(--muted);
  max-width: 52ch;
  text-wrap: pretty;
}
/* 13px is "normal" text to WCAG, so this owes the full 4.5:1 — and it sits on
   the body's --bg (#F6F7F9), not white, which costs it ~0.3. --amber-ink lands
   at 4.37:1 here: a big lift on --amber-dark's 3.14:1 but still a hair short.
   See HANDOVER §8 — clearing it needs a darker token, not another swap. */
.plain__code {
  font-family: var(--mono);
  font-weight: 500;
  font-size: var(--fs-xs);
  letter-spacing: var(--tr-caps-wide);
  font-variant-numeric: tabular-nums lining-nums;
  color: var(--amber-ink);
}

/* --------------------------------------------------------------------------
   Keyframes
   -------------------------------------------------------------------------- */
@keyframes floatY {
  from { transform: translateY(0); }
  to   { transform: translateY(-12px); }
}
@keyframes glowDrift {
  from { transform: translate(0, 0); }
  to   { transform: translate(44px, 26px); }
}
@keyframes scrollDot {
  0%   { transform: translateY(-10px); opacity: 0; }
  20%  { opacity: 1; }
  80%  { opacity: 1; }
  100% { transform: translateY(34px); opacity: 0; }
}
/* Scale is tuned to the mark's size: the old 2.7x was set against an 11px dot,
   and would throw a 100px ring off the 38px badge. 1.85x keeps the ripple
   reaching about as far past the edge as it used to. */
@keyframes waPulse {
  0%   { transform: scale(1);    opacity: 0.5; }
  80%  { transform: scale(1.85); opacity: 0; }
  100% { transform: scale(1.85); opacity: 0; }
}

/* --------------------------------------------------------------------------
   Responsive
   -------------------------------------------------------------------------- */
@media (max-width: 980px) {
  .nav__links { display: none; }
  .nav__burger { display: flex; }
}

@media (max-width: 900px) {
  :root { --gutter: 20px; }

  /* Collapse the WhatsApp FAB to just the mark — the universally understood
     affordance — and do it HERE, not at 520px where it used to live.
     520 was far too late: the label pill is a fixed 256px, so between 521 and
     900 it covered 28% of the screen at 900px and 43% at 600px, floating over
     content, while the nav had already been a burger since 980px. On the
     contact page at 820-900 it was landing on top of a form input. 900 is the
     right line because it's where the whole layout already switches to its
     narrow treatment. */
  .wa-fab {
    bottom: 18px;
    right: 18px;
    padding: 12px;
    border-radius: 50%;
    gap: 0;
  }
  .wa-fab__text { display: none; }

  .hero { padding: 118px 0 84px; }
  .section, .section--dark { padding: 78px 0; }
  .section--tight { padding: 68px 0; }
  .brands { padding: 78px 0; }
  .prod, .prod--first { padding: 60px 0; }

  .split, .split--wide, .split--product { gap: 44px; }
  .stack { gap: 40px; }

  /* the offset outline frames hang outside the container — pure decoration,
     and on narrow screens they'd push the layout sideways */
  .framed__outline { display: none; }
  .framed { margin: 8px 0 34px; }
  .framed--flush { margin: 0; }

  /* keep the floating stat cards inside the viewport.
     `bottom: auto` has to be repeated here: the .float-card rule above resets
     `bottom` at equal specificity and later in the source, so without it the
     dark card gets both a top and a bottom and stretches down the whole photo. */
  .float-card { left: 12px; bottom: -18px; }
  .float-card--dark {
    top: -14px;
    right: 12px;
    bottom: auto;
    left: auto;
  }
  .float-card__body { padding: 14px 18px 14px 21px; }
  .float-card--dark .float-card__body { padding: 11px 16px 11px 19px; font-size: var(--fs-md); }

  .scroll-cue { display: none; }

  .cta-panel { padding: 56px 26px; border-radius: 20px; }
  .form-card { padding: 28px 22px; }
  .marquee::before, .marquee::after { width: 64px; }

  /* stacked product rows read best photo-first, then the copy */
  .prod__media { order: -1; }
}

@media (max-width: 520px) {
  /* One step down the scale rather than a bespoke phone size. The display
     steps are already fluid and need no override here. */
  .lede { font-size: var(--fs-lg); }
  .eyebrow { letter-spacing: var(--tr-caps); }

  .btn { padding: 15px 24px; }
  .btn-row .btn { flex: 1 1 100%; }

  .value-card { padding: 28px 24px; }
  .check-panel { padding: 28px 24px; }
  .stat-card { padding: 26px 24px; }
  .info-card { padding: 22px 20px; }
  .form-card { padding: 24px 18px; }

  .map-frame { height: 300px; }
  .footer__inner { padding: 56px var(--gutter) 40px; gap: 36px; }
}

/* --------------------------------------------------------------------------
   Reduced motion — ambient.js also checks this, but CSS animations
   (marquee, glow drift, float, pulse) need switching off here.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  /* ambient.js stops advancing the marquee, but it stays hand-scrollable */
  .scroll-cue { display: none; }
}

/* --------------------------------------------------------------------------
   Print
   -------------------------------------------------------------------------- */
@media print {
  .nav, .wa-fab, .scroll-cue, canvas[data-particles], .glow, .gridlines { display: none !important; }
  body { background: #fff; }
  .section, .section--dark, .hero { padding: 24px 0; }
  .section--dark, .footer, .cta-panel { background: #fff !important; color: #000 !important; }
  a { color: #000; text-decoration: underline; }
}
