/* ==========================================================================
   LABTECH SUPPLY — site theme
   Layered on top of Bootstrap 5. Written so that it works unchanged in both
   LTR (English) and RTL (Arabic): use logical properties (inline-start/end)
   rather than left/right wherever direction matters.
   ========================================================================== */

/* ==========================================================================
   Self-hosted webfonts — IBM Plex Sans + IBM Plex Sans Arabic
   --------------------------------------------------------------------------
   Served from static/fonts/, not a CDN. Two reasons beyond speed: a Google
   Fonts request is a third-party request that would need a consent banner
   under GDPR, and this site deliberately has none (see the OpenStreetMap
   comment in pages/contact.html for the same reasoning applied to the map).

   Licence: SIL OFL 1.1 — see static/fonts/OFL.txt, which must ship with the
   binaries.

   The unicode-range on every block is what keeps this cheap. An English page
   downloads the two Latin faces it uses and never touches the Arabic ones;
   an Arabic page does the reverse. Do not merge these blocks or strip the
   ranges — that would turn a ~90KB page cost into ~485KB.
   ========================================================================== */

@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/ibm-plex-sans-latin-ext-400.f0e2925b3fa6.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/ibm-plex-sans-latin-400.b2c9031d9fd6.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/ibm-plex-sans-latin-ext-500.f0e2925b3fa6.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/ibm-plex-sans-latin-500.b2c9031d9fd6.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/ibm-plex-sans-latin-ext-600.f0e2925b3fa6.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/ibm-plex-sans-latin-600.b2c9031d9fd6.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/ibm-plex-sans-latin-ext-700.f0e2925b3fa6.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/ibm-plex-sans-latin-700.b2c9031d9fd6.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "IBM Plex Sans Arabic";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/ibm-plex-sans-arabic-400.4fa3d81d41ab.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC, U+102E0-102FB, U+10E60-10E7E, U+10EC2-10EC4, U+10EFC-10EFF, U+1EE00-1EE03, U+1EE05-1EE1F, U+1EE21-1EE22, U+1EE24, U+1EE27, U+1EE29-1EE32, U+1EE34-1EE37, U+1EE39, U+1EE3B, U+1EE42, U+1EE47, U+1EE49, U+1EE4B, U+1EE4D-1EE4F, U+1EE51-1EE52, U+1EE54, U+1EE57, U+1EE59, U+1EE5B, U+1EE5D, U+1EE5F, U+1EE61-1EE62, U+1EE64, U+1EE67-1EE6A, U+1EE6C-1EE72, U+1EE74-1EE77, U+1EE79-1EE7C, U+1EE7E, U+1EE80-1EE89, U+1EE8B-1EE9B, U+1EEA1-1EEA3, U+1EEA5-1EEA9, U+1EEAB-1EEBB, U+1EEF0-1EEF1;
}

@font-face {
  font-family: "IBM Plex Sans Arabic";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/ibm-plex-sans-arabic-500.106892212ea7.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC, U+102E0-102FB, U+10E60-10E7E, U+10EC2-10EC4, U+10EFC-10EFF, U+1EE00-1EE03, U+1EE05-1EE1F, U+1EE21-1EE22, U+1EE24, U+1EE27, U+1EE29-1EE32, U+1EE34-1EE37, U+1EE39, U+1EE3B, U+1EE42, U+1EE47, U+1EE49, U+1EE4B, U+1EE4D-1EE4F, U+1EE51-1EE52, U+1EE54, U+1EE57, U+1EE59, U+1EE5B, U+1EE5D, U+1EE5F, U+1EE61-1EE62, U+1EE64, U+1EE67-1EE6A, U+1EE6C-1EE72, U+1EE74-1EE77, U+1EE79-1EE7C, U+1EE7E, U+1EE80-1EE89, U+1EE8B-1EE9B, U+1EEA1-1EEA3, U+1EEA5-1EEA9, U+1EEAB-1EEBB, U+1EEF0-1EEF1;
}

@font-face {
  font-family: "IBM Plex Sans Arabic";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/ibm-plex-sans-arabic-600.64891a810259.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC, U+102E0-102FB, U+10E60-10E7E, U+10EC2-10EC4, U+10EFC-10EFF, U+1EE00-1EE03, U+1EE05-1EE1F, U+1EE21-1EE22, U+1EE24, U+1EE27, U+1EE29-1EE32, U+1EE34-1EE37, U+1EE39, U+1EE3B, U+1EE42, U+1EE47, U+1EE49, U+1EE4B, U+1EE4D-1EE4F, U+1EE51-1EE52, U+1EE54, U+1EE57, U+1EE59, U+1EE5B, U+1EE5D, U+1EE5F, U+1EE61-1EE62, U+1EE64, U+1EE67-1EE6A, U+1EE6C-1EE72, U+1EE74-1EE77, U+1EE79-1EE7C, U+1EE7E, U+1EE80-1EE89, U+1EE8B-1EE9B, U+1EEA1-1EEA3, U+1EEA5-1EEA9, U+1EEAB-1EEBB, U+1EEF0-1EEF1;
}

@font-face {
  font-family: "IBM Plex Sans Arabic";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/ibm-plex-sans-arabic-700.a4390abc98c2.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC, U+102E0-102FB, U+10E60-10E7E, U+10EC2-10EC4, U+10EFC-10EFF, U+1EE00-1EE03, U+1EE05-1EE1F, U+1EE21-1EE22, U+1EE24, U+1EE27, U+1EE29-1EE32, U+1EE34-1EE37, U+1EE39, U+1EE3B, U+1EE42, U+1EE47, U+1EE49, U+1EE4B, U+1EE4D-1EE4F, U+1EE51-1EE52, U+1EE54, U+1EE57, U+1EE59, U+1EE5B, U+1EE5D, U+1EE5F, U+1EE61-1EE62, U+1EE64, U+1EE67-1EE6A, U+1EE6C-1EE72, U+1EE74-1EE77, U+1EE79-1EE7C, U+1EE7E, U+1EE80-1EE89, U+1EE8B-1EE9B, U+1EEA1-1EEA3, U+1EEA5-1EEA9, U+1EEAB-1EEBB, U+1EEF0-1EEF1;
}

/* ==========================================================================
   Design tokens
   --------------------------------------------------------------------------
   Two layers, and it matters which one you reach for.

   PRIMITIVES (--lt-crimson-600, --lt-slate-200 …) name a colour and nothing
   else. Components should not use them directly. The one exception is the
   on-dark steps, where the choice is genuinely about contrast rather than
   meaning — see "Dark surfaces" below.

   SEMANTIC ALIASES (--lt-primary, --lt-border …) name a job. Everything in
   this stylesheet and in dashboard.css uses these. Re-theming the site is then
   a matter of repointing the aliases in one block, which is exactly how the
   crimson changeover was done — see THEME_PLAN.md §1.
   ========================================================================== */

:root {
  /* ---- Primitive: crimson -------------------------------------------------
     From the logo mark (logo/…bdag3k….png). 600 is the brand value; the ramp
     exists so tints, hovers and on-dark steps are decided once, here, instead
     of being improvised per component the way the old rgba() literals were. */
  --lt-crimson-50:  #fdf3f5;
  --lt-crimson-100: #fbe4e9;
  --lt-crimson-200: #f2c0cb;
  --lt-crimson-300: #e8657f;
  --lt-crimson-400: #c9315a;
  --lt-crimson-500: #b0113a;
  --lt-crimson-600: #930028;   /* ★ brand */
  --lt-crimson-700: #74001f;
  --lt-crimson-800: #550017;

  /* ---- Primitive: slate-teal ----------------------------------------------
     From the "elwan" wordmark, which samples at #354a4f. This replaces the old
     #10263a navy, which was a guess made before the logo was available. */
  --lt-slate-50:  #f6f8f9;
  --lt-slate-100: #e8edef;
  --lt-slate-200: #d5dde0;
  --lt-slate-300: #b4c0c4;
  --lt-slate-500: #5e7378;
  --lt-slate-700: #354a4f;   /* ★ wordmark */
  --lt-slate-900: #1b2b31;

  /* ---- Primitive: azure ---------------------------------------------------
     The site's original primary. Kept deliberately, with a narrower job: it
     carries quantitative and informational content, where crimson would read
     as alarm. See THEME_PLAN.md §2.2. */
  --lt-azure-50:  #eff7fc;
  --lt-azure-500: #0b6ea9;
  --lt-azure-700: #06527d;

  /* ---- Semantic: brand and action ---------------------------------------- */
  --lt-primary:        var(--lt-crimson-600);
  --lt-primary-hover:  var(--lt-crimson-700);
  --lt-primary-active: var(--lt-crimson-800);

  /* Crimson used as a *fill with white text on it* — buttons, the skip link,
     the dashboard badge, the selected pill, the lifecycle nodes — as opposed to
     crimson used as text or an icon. On this light theme the two are the same
     value; the split exists so a fill can be tuned independently of text
     without hunting down six rules. White-on-#930028 is 9.25:1. */
  --lt-primary-solid:  var(--lt-primary);
  --lt-on-primary:     #fff;

  /* Tint steps. These replace seven hand-written rgba() alphas that had drifted
     apart for no reason (0.06/0.07/0.08/0.09/0.1/0.12/0.16). color-mix keeps
     them following --lt-primary automatically, so a re-theme needs no sweep. */
  --lt-primary-wash: color-mix(in srgb, var(--lt-primary) 6%,  transparent);
  --lt-primary-tint: color-mix(in srgb, var(--lt-primary) 9%,  transparent);
  --lt-primary-tint-strong: color-mix(in srgb, var(--lt-primary) 12%, transparent);
  --lt-primary-edge: color-mix(in srgb, var(--lt-primary) 18%, transparent);

  /* ---- Semantic: information -------------------------------------------- */
  --lt-info:      var(--lt-azure-500);
  --lt-info-tint: var(--lt-azure-50);

  /* ---- Semantic: text and surfaces --------------------------------------- */
  --lt-ink:            var(--lt-slate-900);   /* body copy                    */
  --lt-ink-prose:      #334155;               /* long-form paragraphs         */
  --lt-ink-soft:       var(--lt-slate-500);   /* secondary and meta text      */
  --lt-heading:        var(--lt-slate-700);
  --lt-border:         var(--lt-slate-200);
  --lt-border-soft:    var(--lt-slate-100);
  --lt-surface:        var(--lt-slate-50);
  --lt-surface-raised: #fff;
  /* Plate behind supplier logos and product photography. Separate from
     --lt-surface-raised because brand artwork is usually dark-on-transparent
     and needs a guaranteed white ground, whatever a surface is doing. */
  --lt-media-bg:       #fff;

  /* ---- Semantic: status --------------------------------------------------
     Danger is deliberately brighter and warmer than the brand crimson so the
     two never read as the same signal. Destructive actions must still carry an
     explicit verb, never colour alone — see THEME_PLAN.md §2.4.

     These are darker than the obvious mid-tones because each one is used as
     text *on its own tint* — status pills, .apply-deadline, .thanks-icon. At
     the lighter values the pills measured 4.0–4.3:1 against their own
     backgrounds, just under AA. Darkening the ink rather than lightening the
     tint keeps the fills as quiet as they were. Measured on-tint results:
     danger 5.40:1, warning 5.50:1, success 5.95:1. */
  --lt-danger:  #b42318;
  --lt-warning: #92400e;
  --lt-success: #166534;

  /* ---- Dark surfaces -----------------------------------------------------
     #930028 on --lt-slate-900 is 1.58:1 — invisible. Anything crimson sitting
     on a dark band uses these steps instead. These are measured ratios against
     --lt-slate-900, not estimates:

       crimson-600 #930028   1.58:1   never, on any dark band
       crimson-400 #c9315a   2.83:1   decorative only — glows, gradient stops
       crimson-300 #e8657f   4.59:1   safe for text and icons

     On the mid slate --lt-slate-700 even crimson-300 reaches only 2.94:1, so a
     crimson accent there is decorative whatever the step. If it has to carry
     meaning on a dark band, use --lt-on-dark and let the crimson be atmosphere. */
  --lt-on-dark:        rgba(255, 255, 255, 0.88);  /* 11.67:1 on slate-900 */
  --lt-on-dark-soft:   rgba(255, 255, 255, 0.82);  /* 10.30:1 — holds the
                                                      legibility of the
                                                      #cbd5e1 it replaced   */
  --lt-on-dark-brand:  var(--lt-crimson-300);      /*  4.59:1 */
  --lt-on-dark-border: rgba(255, 255, 255, 0.15);

  /* ---- Gradients ---------------------------------------------------------
     Held as tokens because a slate→crimson blend goes muddy through the middle
     if the two ends are given equal distance; the slate has to carry most of
     the ramp. Phase D revisits these with the hero redesign. */
  --lt-gradient-hero: linear-gradient(135deg,
                        var(--lt-slate-900) 0%,
                        var(--lt-slate-700) 58%,
                        var(--lt-crimson-800) 100%);
  --lt-gradient-cta:  linear-gradient(135deg,
                        var(--lt-slate-900) 0%,
                        var(--lt-slate-700) 62%,
                        var(--lt-crimson-800) 100%);

  /* ---- Radius ------------------------------------------------------------
     Was seven ad-hoc values (0.3/0.375/0.5/0.625/0.75/1/2rem). Now four. */
  --lt-radius-xs:   0.375rem;
  --lt-radius-sm:   0.5rem;
  --lt-radius-md:   0.75rem;
  --lt-radius-lg:   1rem;
  --lt-radius-pill: 999px;

  /* ---- Elevation ---------------------------------------------------------
     Tinted with the slate hue rather than neutral grey: a shadow carrying a
     trace of the surface under it reads as depth, a grey one reads as dirt. */
  --lt-shadow-sm:    0 1px 2px rgba(27, 43, 49, 0.06);
  --lt-shadow-md:    0 6px 18px rgba(27, 43, 49, 0.08);
  --lt-shadow-lg:    0 12px 30px rgba(27, 43, 49, 0.10);
  --lt-shadow-brand: 0 8px 22px color-mix(in srgb, var(--lt-primary) 16%, transparent);
  --lt-shadow-float: 0 6px 18px rgba(0, 0, 0, 0.22);

  /* ---- Motion and rhythm ------------------------------------------------- */
  --lt-transition: 0.18s cubic-bezier(0.4, 0, 0.2, 1);
  --lt-section-y:  clamp(3rem, 6vw, 6rem);

  /* ---- Typeface ----------------------------------------------------------
     One stack for both scripts. "IBM Plex Sans" is listed first and its
     @font-face rules only claim Latin unicode ranges, so an Arabic character
     falls through to "IBM Plex Sans Arabic" automatically — per character, by
     the browser. That is why there is no [dir="rtl"] font-family override: a
     mixed sentence ("جهاز Thermo Scientific") gets the right face on each word
     without any markup. The two are one family drawn across both scripts, so
     the switch is invisible. */
  --lt-font-sans: "IBM Plex Sans", "IBM Plex Sans Arabic", system-ui,
                  -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial,
                  sans-serif;
  --lt-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* ---- Type scale --------------------------------------------------------
     Fluid, so one rule serves 375px and 1920px without breakpoint juggling.
     The clamp middles are `Nvw + Xrem` rather than plain vw: a pure-vw font
     size cannot be overridden by browser zoom, which is an accessibility
     failure (WCAG 1.4.4). Mixing in a rem term keeps zoom working. */
  --lt-text-xs:   0.8125rem;
  --lt-text-sm:   0.875rem;
  --lt-text-base: 1rem;
  --lt-text-lg:   clamp(1.0625rem, 0.3vw + 1rem, 1.125rem);

  --lt-h6:      1rem;
  --lt-h5:      clamp(1.1rem,   0.4vw + 1rem,    1.25rem);
  --lt-h4:      clamp(1.2rem,   0.8vw + 1rem,    1.5rem);
  --lt-h3:      clamp(1.3rem,   1.2vw + 1rem,    1.75rem);
  --lt-h2:      clamp(1.5rem,   2vw   + 1rem,    2rem);
  --lt-h1:      clamp(1.75rem,  3vw   + 1rem,    2.5rem);
  --lt-display: clamp(2rem,     5vw   + 0.6rem,  3rem);

  /* Headings tighten as they grow; body copy stays open. */
  --lt-tracking-tight: -0.02em;
  --lt-leading-body:   1.65;
  --lt-leading-tight:  1.25;

}

/* ==========================================================================
   Bootstrap bridge
   --------------------------------------------------------------------------
   Bootstrap 5.3 exposes most of its theme as custom properties, so pointing
   those at our tokens covers link colours, focus rings and the .*-primary
   utilities in one place.

   The exception is a handful of compiled literals — #86b7fe and
   rgba(13,110,253,.25) appear 33 times in bootstrap.min.css and cannot be
   reached through a variable. Those need the explicit rules further down.
   ========================================================================== */

:root {
  --bs-primary: var(--lt-primary);
  --bs-primary-rgb: 147, 0, 40;
  --bs-link-color: var(--lt-primary);
  --bs-link-color-rgb: 147, 0, 40;
  --bs-link-hover-color: var(--lt-primary-hover);
  --bs-link-hover-color-rgb: 116, 0, 31;
  --bs-focus-ring-color: var(--lt-primary-edge);
  --bs-body-color: var(--lt-ink);
  --bs-border-color: var(--lt-border);
  --bs-danger: var(--lt-danger);
}

.btn-primary {
  --bs-btn-bg: var(--lt-primary-solid);
  --bs-btn-border-color: var(--lt-primary-solid);
  --bs-btn-hover-bg: var(--lt-primary-hover);
  --bs-btn-hover-border-color: var(--lt-primary-hover);
  --bs-btn-active-bg: var(--lt-primary-active);
  --bs-btn-active-border-color: var(--lt-primary-active);
  --bs-btn-disabled-bg: var(--lt-primary);
  --bs-btn-disabled-border-color: var(--lt-primary);
}

.btn-outline-primary {
  --bs-btn-color: var(--lt-primary);
  --bs-btn-border-color: var(--lt-primary);
  --bs-btn-hover-bg: var(--lt-primary);
  --bs-btn-hover-border-color: var(--lt-primary);
  --bs-btn-active-bg: var(--lt-primary-hover);
  --bs-btn-active-border-color: var(--lt-primary-hover);
  --bs-btn-disabled-color: var(--lt-primary);
  --bs-btn-disabled-border-color: var(--lt-primary);
}

/* Focus. Bootstrap compiles its focus colours as literals, so these have to be
   restated rather than re-pointed. One consistent ring across every control
   also fixes a real accessibility gap: .btn had no visible focus ring at all
   distinguishable from :hover. */
.form-control:focus,
.form-select:focus,
.form-check-input:focus {
  border-color: color-mix(in srgb, var(--lt-primary) 45%, #fff);
  box-shadow: 0 0 0 0.25rem var(--lt-primary-edge);
}

.form-check-input:checked {
  background-color: var(--lt-primary);
  border-color: var(--lt-primary);
}

.btn:focus-visible,
.form-control:focus-visible,
.dropdown-item:focus-visible,
a:focus-visible {
  outline: 2px solid var(--lt-primary);
  outline-offset: 2px;
  box-shadow: none;
}

/* Buttons. Bootstrap's default is 400 weight at a 0.375rem radius, which reads
   as a link that happens to have a box around it. 600 at --lt-radius-sm gives
   the CTA the presence it needs to be the thing your eye lands on. */
.btn {
  --bs-btn-border-radius: var(--lt-radius-sm);
  --bs-btn-font-weight: 600;
  transition: background-color var(--lt-transition),
              border-color var(--lt-transition),
              color var(--lt-transition),
              box-shadow var(--lt-transition);
}

.btn-lg {
  --bs-btn-border-radius: var(--lt-radius-sm);
  --bs-btn-padding-x: 1.4rem;
  --bs-btn-padding-y: 0.65rem;
  --bs-btn-font-size: 1rem;
}

.btn-sm {
  --bs-btn-border-radius: var(--lt-radius-xs);
}

@media (prefers-reduced-motion: reduce) {
  .btn { transition: none; }
}

.text-primary { color: var(--lt-primary) !important; }
.bg-surface { background-color: var(--lt-surface); }


/* ==========================================================================
   Typography
   ========================================================================== */

body {
  font-family: var(--lt-font-sans);
  color: var(--lt-ink);
  line-height: var(--lt-leading-body);
}

/* Arabic reads better with a slightly larger line height. This predates the
   webfont and still holds: IBM Plex Sans Arabic has tall ascenders and deep
   descenders, so the extra room stops stacked diacritics from colliding. */
[dir="rtl"] body {
  line-height: 1.9;
}

/* Headings ----------------------------------------------------------------
   Bootstrap ships these at fixed rem sizes and weight 500. The fluid scale
   replaces the sizes; 600 gives Plex the density a heading needs, since at 500
   it reads closer to body text than it should.

   Negative tracking is applied through a custom property rather than a flat
   value because it must not apply to Arabic — Arabic is a connected script and
   tightening the letter spacing pulls joined glyphs into each other. The
   [dir="rtl"] rule below resets it. */
h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
  font-weight: 600;
  line-height: var(--lt-leading-tight);
  letter-spacing: var(--lt-tracking-tight);
  color: var(--lt-heading);
  /* Avoids a one-word orphan on the last line of a headline. */
  text-wrap: balance;
}

[dir="rtl"] h1, [dir="rtl"] h2, [dir="rtl"] h3,
[dir="rtl"] h4, [dir="rtl"] h5, [dir="rtl"] h6,
[dir="rtl"] .h1, [dir="rtl"] .h2, [dir="rtl"] .h3,
[dir="rtl"] .h4, [dir="rtl"] .h5, [dir="rtl"] .h6 {
  letter-spacing: normal;
  line-height: 1.45;
}

h1, .h1 { font-size: var(--lt-h1); }
h2, .h2 { font-size: var(--lt-h2); }
h3, .h3 { font-size: var(--lt-h3); }
h4, .h4 { font-size: var(--lt-h4); }
h5, .h5 { font-size: var(--lt-h5); }
h6, .h6 { font-size: var(--lt-h6); }

/* Bootstrap's display sizes are fixed rem values that overflow a 375px screen.
   Only display-5 and display-6 are used on this site. */
.display-4, .display-5, .display-6 {
  font-size: var(--lt-display);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: var(--lt-tracking-tight);
}

[dir="rtl"] .display-4,
[dir="rtl"] .display-5,
[dir="rtl"] .display-6 {
  letter-spacing: normal;
  line-height: 1.35;
}

/* Bootstrap sets .lead to weight 300. We only ship 400-700, so a 300 would be
   synthesised or silently snapped — state 400 rather than leave it to chance. */
.lead {
  font-size: var(--lt-text-lg);
  font-weight: 400;
  line-height: 1.6;
}

/* Figures line up in columns only if the digits are tabular. Plex defaults to
   proportional, which makes the stat rows look ragged. */
.stat-value,
.catalog-stat-value {
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--lt-tracking-tight);
}

/* Skip link ----------------------------------------------------------------
   Off-canvas until focused, then pinned over the header. Not display:none —
   a hidden element is not focusable, which would defeat the point. */
.skip-link {
  position: absolute;
  z-index: 1080;
  inset-inline-start: 1rem;
  top: -4rem;
  padding: 0.6rem 1.1rem;
  border-radius: 0 0 var(--lt-radius-sm) var(--lt-radius-sm);
  background-color: var(--lt-primary-solid);
  color: var(--lt-on-primary);
  font-size: var(--lt-text-sm);
  font-weight: 600;
  text-decoration: none;
  transition: top var(--lt-transition);
}

.skip-link:focus {
  top: 0;
  color: #fff;
}

/* The target gets tabindex="-1" so focus actually lands there; suppress the
   focus ring it would otherwise draw around the whole main element. */
main:focus { outline: none; }

@media (prefers-reduced-motion: reduce) {
  .skip-link { transition: none; }
}

/* Top contact strip ------------------------------------------------------- */
.topbar {
  background-color: var(--lt-slate-900);
  color: var(--lt-on-dark-soft);
  font-size: 0.825rem;
}

.topbar-link {
  color: var(--lt-on-dark-soft);
  text-decoration: none;
  vertical-align: baseline;
}

/* Crimson on hover rather than plain white: it is the first place brand colour
   appears on the page. --lt-on-dark-brand is the crimson-300 step, because
   crimson-600 on the slate topbar is 1.58:1 — see the dark-surface note in the
   token block. */
.topbar-link {
  transition: color var(--lt-transition);
}

.topbar-link:hover,
.topbar-link:focus-visible {
  color: var(--lt-on-dark-brand);
}

@media (prefers-reduced-motion: reduce) {
  .topbar-link { transition: none; }
}

/* Gap rather than .me-3 on each item: the address is hidden below lg, and a
   margin on a hidden sibling would leave the first visible link indented. */
.topbar-contact {
  column-gap: 1rem;
}

/* Phones: phone and email collapse to icons so the strip stays one line next
   to the language switcher. The label is clipped, not display:none, so the
   link keeps its accessible name. Padding brings each icon up to a usable tap
   target without making the strip taller than a single line of text. */
@media (max-width: 767.98px) {
  .topbar-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }

  .topbar-contact { column-gap: 0.25rem; }

  .topbar-link {
    display: inline-block;
    padding: 0.35rem 0.5rem;
    font-size: 0.95rem;
  }
}

/* The language switcher is the only Arabic text on an English page — the single
   word "العربية". Left on the normal stack it pulls the whole 42KB Plex Arabic
   400 face down on every English page load, for seven glyphs. Omitting the
   Arabic family here lets those glyphs fall to a system face instead. The
   Latin label is unaffected, and Arabic pages still get Plex Arabic properly
   because the rest of the page uses the normal stack. */
.lang-link {
  font-family: "IBM Plex Sans", "Segoe UI", Tahoma, "Noto Sans Arabic", sans-serif;
}

/* Navigation -------------------------------------------------------------- */
.site-nav {
  padding-block: 0.5rem;
  /* Flat against the page until it starts overlapping content. The .is-stuck
     class is toggled from site.js; the permanent Bootstrap .shadow-sm it
     replaces drew a hairline under the bar even at the top of the page, which
     made the header look detached from the hero. */
  border-bottom: 1px solid transparent;
  transition: box-shadow var(--lt-transition),
              border-color var(--lt-transition);
}

.site-nav.is-stuck {
  border-bottom-color: var(--lt-border);
  box-shadow: var(--lt-shadow-md);
}

@media (prefers-reduced-motion: reduce) {
  .site-nav { transition: none; }
}

/* Brand lockup ------------------------------------------------------------
   Mark plus wordmark, mirroring the printed logo. The wordmark is text, so it
   picks up IBM Plex Sans and --lt-heading rather than needing an image per
   colourway. */
.site-brand {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
  margin-inline-end: 0.85rem;
  padding-block: 0;
}

.site-brand-mark {
  height: 36px;
  width: auto;
}

.site-brand-name {
  font-size: 1.4rem;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--lt-heading);
  /* The logotype is lowercase; guard against a parent text-transform. */
  text-transform: lowercase;
}

/* Arabic is a connected script — negative tracking pulls joined glyphs
   together, and "lowercase" is meaningless. */
[dir="rtl"] .site-brand-name {
  letter-spacing: normal;
  text-transform: none;
  font-size: 1.3rem;
}

.site-nav .navbar-nav {
  column-gap: 0.1rem;
}

/* The extra .navbar-nav in this selector is load-bearing. Bootstrap styles these
   through .navbar-expand-xl .navbar-nav .nav-link — specificity 0,3,0 — so the
   obvious `.site-nav .nav-link` (0,2,0) loses, and the padding-inline it
   declared was dead code that silently did nothing. Matching Bootstrap's
   specificity and relying on load order (labtech.css comes after) is what makes
   the value below actually apply. */
.site-nav .navbar-nav .nav-link {
  position: relative;
  font-weight: 500;
  font-size: 0.9rem;
  color: var(--lt-heading);
  padding-inline: 0.5rem;
  padding-block: 0.5rem;
  border-radius: var(--lt-radius-xs);
  /* Ten menu items do not fit on one row at 1280px. Without this the browser
     breaks a label mid-phrase — "Brands & Partners" wrapped onto three lines
     and pushed the navbar to 88px tall. Shorter labels (see core.constants
     MAIN_MENU) keep the row within its width; this guarantees it. */
  white-space: nowrap;
  transition: color var(--lt-transition), background-color var(--lt-transition);
}

.site-nav .nav-link:hover,
.site-nav .nav-link:focus-visible {
  color: var(--lt-primary);
  background-color: var(--lt-primary-wash);
}

.site-nav .nav-link.active {
  color: var(--lt-primary);
}

/* Underline marker for the current section. Logical properties keep it
   correctly placed when the page flips to RTL for Arabic. */
/* The marker animates from the centre out, so moving between sections reads as
   the underline travelling rather than blinking. scaleX on a transform is
   compositor-only; animating width or inset would force layout on every frame. */
.site-nav .navbar-nav .nav-link::after {
  content: "";
  position: absolute;
  inset-inline: 0.5rem;
  bottom: 0.15rem;
  height: 2.5px;
  border-radius: 2px;
  background-color: var(--lt-primary);
  transform: scaleX(0);
  transform-origin: center;
  transition: transform var(--lt-transition);
}

.site-nav .navbar-nav .nav-link.active::after,
.site-nav .navbar-nav .nav-link:hover::after {
  transform: scaleX(1);
}

@media (prefers-reduced-motion: reduce) {
  .site-nav .navbar-nav .nav-link::after { transition: none; }
}

.site-nav .btn-quote {
  flex-shrink: 0;
  font-size: 0.875rem;
  white-space: nowrap;
}

/* Products dropdown ------------------------------------------------------- */
.site-nav .dropdown-menu {
  min-width: 15rem;
  margin-top: 0.5rem;
  padding: 0.4rem;
  border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-md);
  box-shadow: var(--lt-shadow-lg);
}

.site-nav .dropdown-item {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.45rem 0.6rem;
  border-radius: var(--lt-radius-xs);
  font-size: 0.875rem;
  white-space: normal;
}

.site-nav .dropdown-item i {
  flex-shrink: 0;
  width: 1.1rem;
  color: var(--lt-primary);
}

.site-nav .dropdown-item:hover,
.site-nav .dropdown-item:focus {
  background-color: var(--lt-primary-tint);
  color: var(--lt-primary);
}

/* Collapsed menu on tablet and mobile ------------------------------------- */
@media (max-width: 1199.98px) {
  .site-nav .navbar-collapse {
    padding-block: 0.5rem;
  }

  .site-nav .nav-item + .nav-item {
    border-top: 1px solid var(--lt-border-soft);
  }

  .site-nav .nav-link {
    padding-block: 0.65rem;
    font-size: 0.95rem;
  }

  /* The underline marker reads as a stray rule in a stacked list. Targets every
     nav link, not just .active: since Phase C the marker also appears on hover,
     so hiding only the active one would leave it firing on tap here. */
  .site-nav .navbar-nav .nav-link::after {
    display: none;
  }

  .site-nav .nav-link.active {
    background-color: var(--lt-primary-tint);
  }

  .site-nav .dropdown-menu {
    border: 0;
    box-shadow: none;
    padding-block: 0;
    padding-inline-start: 0.75rem;
  }

  .site-nav .btn-quote {
    width: 100%;
    margin-top: 0.75rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .site-nav .nav-link { transition: none; }
}

/* ==========================================================================
   Homepage hero
   --------------------------------------------------------------------------
   Light, not the dark gradient this replaces. Crimson is 9.25:1 on this ground
   and 1.58:1 on the old slate, so a light hero is the only version where the
   brand colour can carry the primary button instead of being pushed down to
   decoration. See THEME_PLAN.md §5.
   ========================================================================== */

.hero {
  position: relative;
  overflow: hidden;
  padding-block: clamp(3.5rem, 7vw, 6.5rem);
  background-color: var(--lt-surface);
  border-bottom: 1px solid var(--lt-border);
}

/* A single soft crimson bloom in the trailing corner — the whole reason the
   section is not just a flat grey band. Sits behind content and never
   intercepts a click. */
.hero::before {
  content: "";
  position: absolute;
  inset-block-start: -45%;
  inset-inline-end: -12%;
  width: min(46rem, 90vw);
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(
    circle,
    color-mix(in srgb, var(--lt-primary) 13%, transparent) 0%,
    transparent 68%
  );
  pointer-events: none;
}

.hero-inner {
  position: relative;
  display: grid;
  align-items: center;
  gap: clamp(2rem, 4vw, 3.5rem);
  max-width: 54rem;
}

/* Two columns only once there is room for both. Below this the grid stays
   single-column and the illustration falls under the copy — or disappears
   entirely, see .hero-visual. */
@media (min-width: 992px) {
  .hero-inner {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
    max-width: none;
  }
}

/* Eyebrow ------------------------------------------------------------------ */
.hero-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  margin-bottom: 1.1rem;
  padding: 0.3rem 0.8rem;
  border: 1px solid var(--lt-primary-edge);
  border-radius: var(--lt-radius-pill);
  background-color: var(--lt-surface-raised);
  color: var(--lt-primary);
  font-size: var(--lt-text-sm);
  font-weight: 600;
}

.hero-title {
  margin-bottom: 1rem;
  font-size: var(--lt-display);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: var(--lt-tracking-tight);
  color: var(--lt-heading);
  text-wrap: balance;
}

[dir="rtl"] .hero-title {
  letter-spacing: normal;
  line-height: 1.35;
}

.hero-lead {
  max-width: 44rem;
  margin-bottom: 2rem;
  font-size: var(--lt-text-lg);
  line-height: 1.6;
  color: var(--lt-ink-soft);
}

/* Actions ------------------------------------------------------------------
   One filled primary, one ghost, one text link — a deliberate hierarchy. The
   previous three .btn-lg of equal weight gave the eye nowhere to land. */
.hero-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1.25rem;
  margin-bottom: 2.5rem;
}

.hero-text-link {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  color: var(--lt-heading);
  font-weight: 600;
  font-size: var(--lt-text-sm);
  text-decoration: none;
  transition: color var(--lt-transition);
}

.hero-text-link i {
  transition: transform var(--lt-transition);
}

.hero-text-link:hover,
.hero-text-link:focus-visible {
  color: var(--lt-primary);
}

.hero-text-link:hover i { transform: translateX(3px); }
[dir="rtl"] .hero-text-link:hover i { transform: scaleX(-1) translateX(3px); }

/* Trust line --------------------------------------------------------------- */
.hero-trust {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 1.75rem;
  margin: 0;
  padding: 0;
  list-style: none;
  color: var(--lt-ink-soft);
  font-size: var(--lt-text-sm);
}

.hero-trust li {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.hero-trust i {
  color: var(--lt-primary);
  font-size: 1rem;
}

/* Hero illustration --------------------------------------------------------
   A laboratory bench assembling itself: cabinet, fume hood, microscope and
   benchtop analyser landing on a worktop, closed by a crimson rule. Markup and
   the reasoning behind the scene are in partials/hero_visual.html.

   Hidden below 768px. It is the wrong trade on a phone — a 4:3 illustration
   there pushes "Request a Quote" off the fold, and the CTA is the point of the
   section. Between 768 and 991px it sits under the copy at a capped width.

   Colour lives here rather than on the SVG elements so the drawing stays on
   the same tokens as everything else. Crimson is deliberately rationed to the
   handles, the microscope tube, the analyser lid and the closing rule: the
   filled CTA has to stay the largest mass of brand colour in the viewport
   (§2.1). */
.hero-visual {
  display: none;
}

@media (min-width: 768px) {
  .hero-visual {
    display: block;
    width: 100%;
    max-width: 30rem;
    margin-inline: auto;
  }
}

/* The auto margins have to go with the cap. An auto inline margin on a grid
   item consumes the free space itself, which cancels the stretch and leaves
   the SVG at its intrinsic 300px instead of filling the column. */
@media (min-width: 992px) {
  .hero-visual {
    max-width: none;
    margin-inline: 0;
  }
}

.hero-visual svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

/* Structure */
.hv-shadow { fill: var(--lt-border); }
.hv-bench  { fill: var(--lt-heading); }
.hv-leg    { fill: var(--lt-slate-300); }
.hv-rule   { fill: var(--lt-primary); }

/* Module shells */
.hv-body {
  fill: var(--lt-surface-raised);
  stroke: var(--lt-heading);
  stroke-width: 3;
}

.hv-panel {
  fill: var(--lt-surface);
  stroke: var(--lt-border);
  stroke-width: 2;
}

.hv-line {
  stroke: var(--lt-heading);
  stroke-width: 2;
  opacity: 0.4;
}

/* Azure is the data colour (§2.2), so it carries the glass and the readout. */
.hv-glass {
  fill: var(--lt-azure-50);
  stroke: var(--lt-azure-500);
  stroke-width: 2;
}

.hv-knob {
  fill: var(--lt-surface-raised);
  stroke: var(--lt-azure-500);
  stroke-width: 3;
}

.hv-screen { fill: var(--lt-slate-900); }

/* The readout sits on slate-900, where --lt-azure-500 itself is only 1.9:1.
   Lightened for the same reason §2.3 steps crimson up on dark grounds. */
.hv-bar,
.hv-led { fill: color-mix(in srgb, var(--lt-azure-500) 40%, #fff); }

/* Crimson accents */
.hv-valve,
.hv-hub { fill: var(--lt-primary); }

.hv-lid {
  fill: var(--lt-surface);
  stroke: var(--lt-primary);
  stroke-width: 3;
}

.hv-handle,
.hv-tube,
.hv-eye,
.hv-limb {
  stroke-linecap: round;
}

.hv-handle { stroke: var(--lt-primary); stroke-width: 4; }
.hv-tube   { stroke: var(--lt-primary); stroke-width: 9; }
.hv-eye    { stroke: var(--lt-primary); stroke-width: 6; }
.hv-limb   { stroke: var(--lt-heading); stroke-width: 7; }

/* The build ---------------------------------------------------------------
   Runs once on load as the last beat of the hero entrance, then rests as a
   finished bench. It deliberately does not loop: continuous motion next to
   copy someone is reading competes with the copy, and the payoff of this
   scene is the completed lab, not the assembling of it. The analyser lamp is
   the only thing that keeps moving.

   To make it loop instead, add `infinite` and a fade-out tail to the
   keyframes below — nothing else needs to change. */
@keyframes lt-hv-drop {
  from { opacity: 0; transform: translateY(-18px); }
  to   { opacity: 1; transform: none; }
}

@keyframes lt-hv-rule {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

@keyframes lt-hv-led {
  0%, 100% { opacity: 0.35; }
  50%      { opacity: 1; }
}

.hv-frame,
.hv-mod {
  animation: lt-hv-drop 0.55s cubic-bezier(0.2, 1.25, 0.4, 1) both;
}

.hv-frame  { animation-delay: 0.50s; }
.hv-mod-1  { animation-delay: 0.85s; }
.hv-mod-2  { animation-delay: 1.05s; }
.hv-mod-3  { animation-delay: 1.25s; }
.hv-mod-4  { animation-delay: 1.45s; }

.hv-rule {
  transform-box: fill-box;
  transform-origin: left center;
  animation: lt-hv-rule 0.7s cubic-bezier(0.22, 1, 0.36, 1) 1.8s both;
}

.hv-led {
  animation: lt-hv-led 3.2s ease-in-out 2.4s infinite;
}

/* Arabic reads the other way, so the bench has to fill the other way. The
   rule grows from the trailing edge and the modules land in reverse order. */
[dir="rtl"] .hv-rule  { transform-origin: right center; }
[dir="rtl"] .hv-mod-1 { animation-delay: 1.45s; }
[dir="rtl"] .hv-mod-2 { animation-delay: 1.25s; }
[dir="rtl"] .hv-mod-3 { animation-delay: 1.05s; }
[dir="rtl"] .hv-mod-4 { animation-delay: 0.85s; }

@media (prefers-reduced-motion: reduce) {
  .hero-text-link,
  .hero-text-link i { transition: none; }
  .hero-text-link:hover i { transform: none; }

  /* Not just slower — off. The global backstop only zeroes timing, which
     would leave the rule at scaleX(0) for an instant and then snap it out to
     full width. `animation: none` drops every element to its base style, and
     the base style is exactly the finished bench. */
  .hv-frame,
  .hv-mod,
  .hv-rule,
  .hv-led { animation: none; }
}


/* Breadcrumbs ------------------------------------------------------------- */
/* A pill-shaped trail that reads on both banner backgrounds (.page-banner and
   the .catalog-hero gradient). The divider is an inline SVG chevron instead of
   Bootstrap's "/" because the RTL build mirrors the layout but not the glyph,
   so Arabic needs its own arrow. Spacing is driven through Bootstrap's own
   --bs-breadcrumb-item-padding-x, which each build already applies to the
   correct side. */
.lt-breadcrumb {
  --bs-breadcrumb-item-padding-x: 0.3rem;
  --bs-breadcrumb-divider: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9' height='9' viewBox='0 0 16 16' fill='none' stroke='%235e7378' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5.5 2.5 11 8l-5.5 5.5'/%3E%3C/svg%3E");
  display: inline-flex;
  align-items: center;
  /* One line always: a deep trail scrolls sideways rather than stacking. */
  flex-wrap: nowrap;
  max-width: 100%;
  margin-bottom: 0;
  padding: 0.3rem 0.6rem;
  border: 1px solid var(--lt-primary-edge);
  border-radius: var(--lt-radius-pill);
  background-color: rgba(255, 255, 255, 0.72);
  box-shadow: var(--lt-shadow-sm);
  font-size: 0.8125rem;
  overflow-x: auto;
  scrollbar-width: none;
}

.lt-breadcrumb::-webkit-scrollbar {
  display: none;
}

[dir="rtl"] .lt-breadcrumb {
  --bs-breadcrumb-divider: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9' height='9' viewBox='0 0 16 16' fill='none' stroke='%235e7378' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10.5 2.5 5 8l5.5 5.5'/%3E%3C/svg%3E");
}

.lt-breadcrumb .breadcrumb-item {
  display: flex;
  align-items: center;
  /* Without this the last item refuses to shrink and the ellipsis never fires. */
  min-width: 0;
  white-space: nowrap;
}

.lt-breadcrumb .breadcrumb-item + .breadcrumb-item::before {
  /* The chevron is an image; keep it off the text baseline. */
  display: flex;
  align-items: center;
}

.lt-breadcrumb .breadcrumb-item a {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.1rem 0.4rem;
  border-radius: var(--lt-radius-lg);
  color: var(--lt-ink-soft);
  text-decoration: none;
  transition: color 0.15s, background-color 0.15s;
}

.lt-breadcrumb .breadcrumb-item a:hover,
.lt-breadcrumb .breadcrumb-item a:focus-visible {
  color: var(--lt-primary);
  background-color: var(--lt-primary-tint);
}

.lt-breadcrumb .breadcrumb-item a i {
  font-size: 0.9rem;
  line-height: 1;
}

.lt-breadcrumb .breadcrumb-item.active {
  color: var(--lt-heading);
  font-weight: 600;
}

/* The current page is often a long product or article title. */
.lt-breadcrumb-current {
  display: block;
  max-width: 22rem;
  padding-inline: 0.4rem;
  overflow: hidden;
  text-overflow: ellipsis;
}

@media (max-width: 575.98px) {
  .lt-breadcrumb-current { max-width: 12rem; }
}

@media (prefers-reduced-motion: reduce) {
  .lt-breadcrumb .breadcrumb-item a { transition: none; }
}

/* Section rhythm -----------------------------------------------------------
   One vertical scale for every band, replacing the blanket Bootstrap py-5 that
   made the whole page one undifferentiated scroll. */
.section-y { padding-block: var(--lt-section-y); }

.section-sub {
  max-width: 38rem;
  margin-inline: auto;
  margin-bottom: 2.25rem;
  color: var(--lt-ink-soft);
  text-align: center;
}

/* Statistic band -----------------------------------------------------------
   Figures are azure, not crimson: they are quantitative, and red numbers read
   as a warning. See THEME_PLAN.md §2.2. */
.stat-band {
  padding-block: clamp(2rem, 4vw, 3rem);
  border-bottom: 1px solid var(--lt-border);
  background-color: var(--lt-surface-raised);
}

.stat-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.5rem;
}

.stat {
  text-align: center;
  /* Hairline separators between figures rather than four floating columns. */
  padding-inline: 1rem;
  border-inline-start: 1px solid var(--lt-border-soft);
}

.stat:first-child { border-inline-start: 0; }

.stat-value {
  font-size: clamp(2rem, 3vw + 1rem, 2.75rem);
  font-weight: 700;
  color: var(--lt-info);
  line-height: 1;
  margin-bottom: 0.35rem;
}

.stat-label {
  color: var(--lt-ink-soft);
  font-size: var(--lt-text-xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* Arabic has no case, and tracking damages a connected script. */
[dir="rtl"] .stat-label {
  letter-spacing: normal;
  text-transform: none;
  font-size: var(--lt-text-sm);
}

@media (max-width: 767.98px) {
  .stat-grid { grid-template-columns: repeat(2, 1fr); }
  /* With two per row the third tile starts a new row and must lose its rail. */
  .stat:nth-child(odd) { border-inline-start: 0; }
}

/* Homepage "why us" list --------------------------------------------------- */
.reason-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 0.75rem;
}

.reason-list li {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-sm);
  background-color: var(--lt-surface-raised);
  font-size: var(--lt-text-sm);
  font-weight: 500;
}

.reason-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--lt-radius-xs);
  background-color: var(--lt-primary-tint);
  color: var(--lt-primary);
  font-size: 1.1rem;
}

/* Footer ------------------------------------------------------------------ */
.site-footer {
  background-color: var(--lt-slate-900);
  color: var(--lt-on-dark-soft);
  /* A single crimson hairline across the top. The cheapest possible brand
     signature, and the only crimson that works on this surface at full
     strength — it carries no text, so the 1.58:1 contrast rule does not
     apply to it. */
  border-top: 3px solid var(--lt-primary);
}

/* Footer brand lockup. Same structure as the header, recoloured for the dark
   ground — which is exactly why the wordmark is text and not an image. */
.footer-brand {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.85rem;
  text-decoration: none;
}

.footer-brand-mark {
  height: 34px;
  width: auto;
}

.footer-brand-name {
  font-size: 1.35rem;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.03em;
  color: #fff;
  text-transform: lowercase;
}

.footer-brand-descriptor {
  display: block;
  margin-top: 0.15rem;
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--lt-on-dark-soft);
}

[dir="rtl"] .footer-brand-name {
  letter-spacing: normal;
  text-transform: none;
}

/* Arabic is connected and has no case — tracking and uppercasing both damage
   it, and at 0.625rem the descriptor would be unreadable. */
[dir="rtl"] .footer-brand-descriptor {
  letter-spacing: normal;
  text-transform: none;
  font-size: 0.75rem;
}

.footer-heading {
  position: relative;
  color: #fff;
  font-size: 1rem;
  font-weight: 600;
  margin-bottom: 1.35rem;
  padding-bottom: 0.6rem;
}

.footer-heading::after {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  bottom: 0;
  width: 2rem;
  height: 2px;
  border-radius: 2px;
  background-color: var(--lt-primary);
}

.footer-link {
  color: var(--lt-on-dark-soft);
  text-decoration: none;
  transition: color var(--lt-transition);
}

.footer-link:hover,
.footer-link:focus-visible {
  color: var(--lt-on-dark-brand);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

@media (prefers-reduced-motion: reduce) {
  .footer-link { transition: none; }
}

.footer-divider {
  border-color: var(--lt-on-dark-border);
}

/* Floating WhatsApp button ------------------------------------------------ */
.whatsapp-float {
  position: fixed;
  bottom: 1.25rem;
  /* inset-inline-end flips automatically for Arabic. */
  inset-inline-end: 1.25rem;
  z-index: 1030;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 3.25rem;
  height: 3.25rem;
  border-radius: 50%;
  /* WhatsApp green stays. It is a platform identity colour and recolouring it
     crimson would cost recognition for no gain. */
  background-color: #25d366;
  color: #fff;
  font-size: 1.6rem;
  box-shadow: var(--lt-shadow-float);
  transition: transform var(--lt-transition), box-shadow var(--lt-transition);
}

.whatsapp-float:hover,
.whatsapp-float:focus-visible {
  color: #fff;
  transform: scale(1.08);
  box-shadow: var(--lt-shadow-lg);
}

/* Respect users who have asked for reduced motion. */
@media (prefers-reduced-motion: reduce) {
  .whatsapp-float { transition: none; }
  .whatsapp-float:hover { transform: none; }
}

/* Homepage solution cards -------------------------------------------------- */
.solution-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  height: 100%;
  min-height: 9rem;
  padding: 1.5rem 0.85rem;
  border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-md);
  background-color: var(--lt-surface-raised);
  color: var(--lt-heading);
  text-align: center;
  text-decoration: none;
  font-weight: 500;
  font-size: 0.9rem;
  transition: border-color var(--lt-transition),
              box-shadow var(--lt-transition),
              transform var(--lt-transition);
}

/* The icon sits in a tinted crimson tile rather than floating loose — the same
   treatment .solution-tile-icon uses, so the homepage cards and the catalog
   tiles read as one system instead of two unrelated components. */
.solution-card i {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  border-radius: var(--lt-radius-sm);
  background-color: var(--lt-primary-tint);
  font-size: 1.5rem;
  color: var(--lt-primary);
  transition: background-color var(--lt-transition);
}

.solution-card:hover,
.solution-card:focus-visible {
  border-color: var(--lt-primary);
  box-shadow: var(--lt-shadow-brand);
  transform: translateY(-3px);
  color: var(--lt-primary);
}

.solution-card:hover i {
  background-color: var(--lt-primary-tint-strong);
}

/* ==========================================================================
   Homepage partner logo marquee
   --------------------------------------------------------------------------
   The track holds the logo list twice and slides by exactly -50%, so the second
   copy arrives where the first started and the loop has no seam. That identity
   is the whole trick — if the two copies ever differ, the strip will visibly
   jump once per cycle.

   Pure CSS: no JS, no scroll dependency, and it stops dead under
   prefers-reduced-motion, where continuous motion is exactly what people
   enabling that setting are trying to avoid.
   ========================================================================== */

.logo-marquee {
  position: relative;
  overflow: hidden;
  padding-block: 0.5rem;
  /* Fade both edges so logos dissolve rather than being guillotined by the
     viewport. Masking beats overlaying gradients here: it works on any
     background instead of having to match one. */
  -webkit-mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
  mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
}

.logo-marquee-track {
  display: flex;
  align-items: center;
  gap: 3rem;
  width: max-content;
  /* Direction is pinned LTR. Logos have no reading order, and letting the track
     mirror under [dir="rtl"] would reverse the flex row and send the animation
     the wrong way against its own -50% translate. */
  direction: ltr;
  animation: lt-marquee 45s linear infinite;
}

.logo-marquee:hover .logo-marquee-track {
  animation-play-state: paused;
}

/* The half-gap is not a fudge factor. The track is one flex row of 2N logos
   with 2N-1 gaps, so its midpoint falls *inside* the gap between the two
   copies, not at the start of the second one. Copy 2's first logo actually sits
   at 50% + gap/2. Translating a plain -50% therefore lands half a gap short and
   the strip jumps by that much once per cycle — measured here as 2368px against
   a true offset of 2392px. Keep this in step with the track's `gap`. */
@keyframes lt-marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(-50% - 1.5rem)); }
}

@media (prefers-reduced-motion: reduce) {
  .logo-marquee-track {
    animation: none;
    /* Without the animation the track is twice the viewport wide and the second
       copy is unreachable dead space, so wrap it into a plain centred row. */
    flex-wrap: wrap;
    justify-content: center;
    width: 100%;
    gap: 1.5rem 2.5rem;
  }
}

.partner-logo {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 8.5rem;
  height: 4.5rem;
  padding: 0.5rem;
  border-radius: var(--lt-radius-xs);
  text-align: center;
}

.partner-logo-text {
  color: var(--lt-ink-soft);
  font-size: var(--lt-text-sm);
}

.partner-logo img {
  max-height: 100%;
  max-width: 100%;
  object-fit: contain;
  /* Neutralise the mixed logo backgrounds until official assets arrive. */
  filter: grayscale(100%);
  opacity: 0.75;
  transition: filter 0.15s, opacity 0.15s;
}

.partner-logo:hover img {
  filter: grayscale(0);
  opacity: 1;
}

/* ==========================================================================
   Catalog: products index and category pages
   ========================================================================== */

/* Banner ------------------------------------------------------------------ */
/* The banner every inner page opens with. Formerly two components doing one
   job — .page-banner was a flat surface block, this was a gradient — which is
   a large part of why the site felt inconsistent from page to page. .page-banner
   is gone; its four templates now use this. */
.catalog-hero {
  position: relative;
  padding-block: clamp(2.25rem, 4vw, 3.25rem);
  background:
    linear-gradient(180deg, var(--lt-surface-raised) 0%, var(--lt-surface) 100%);
  border-bottom: 1px solid var(--lt-border);
}

/* Crimson hairline along the bottom edge, echoing the footer's top rule. It
   carries no text, so the dark-surface contrast rule does not apply. */
.catalog-hero::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: -1px;
  height: 2px;
  background: linear-gradient(
    to right,
    var(--lt-primary) 0%,
    color-mix(in srgb, var(--lt-primary) 35%, transparent) 32%,
    transparent 62%
  );
}

/* Gradients take physical directions only, so `to right` would leave the solid
   end on the trailing edge in Arabic. Mirroring the whole strip is cheaper than
   restating the colour stops. */
[dir="rtl"] .catalog-hero::after {
  transform: scaleX(-1);
}

.catalog-hero-lead {
  max-width: 46rem;
  color: var(--lt-ink-soft);
}

.catalog-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 2.25rem;
}

.catalog-stat {
  display: flex;
  flex-direction: column;
  line-height: 1.2;
}

.catalog-stat-value {
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--lt-info);
}

.catalog-stat-label {
  font-size: 0.8rem;
  color: var(--lt-ink-soft);
}

/* Solution tiles on the products index ------------------------------------ */
.solution-tile {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: 1.5rem;
  border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-md);
  background-color: var(--lt-surface-raised);
  transition: border-color var(--lt-transition),
              box-shadow var(--lt-transition),
              transform var(--lt-transition);
}

.solution-tile:hover,
.solution-tile:focus-within {
  border-color: var(--lt-primary);
  box-shadow: var(--lt-shadow-brand);
  transform: translateY(-3px);
}

.solution-tile-icon {
  transition: background-color var(--lt-transition);
}

.solution-tile:hover .solution-tile-icon {
  background-color: var(--lt-primary-tint-strong);
}

.solution-tile-head {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  margin-bottom: 1rem;
}

.solution-tile-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 3rem;
  height: 3rem;
  border-radius: var(--lt-radius-sm);
  background-color: var(--lt-primary-tint);
  color: var(--lt-primary);
  font-size: 1.35rem;
}

.solution-tile-icon-lg {
  width: 3.5rem;
  height: 3.5rem;
  font-size: 1.6rem;
  background-color: var(--lt-surface-raised);
  border: 1px solid var(--lt-border);
}

.solution-tile-title {
  margin-bottom: 0.15rem;
  font-size: 1.05rem;
  font-weight: 600;
  line-height: 1.3;
}

.solution-tile-title a {
  color: var(--lt-heading);
  text-decoration: none;
}

.solution-tile:hover .solution-tile-title a { color: var(--lt-primary); }

.solution-tile-count {
  font-size: 0.775rem;
  color: var(--lt-ink-soft);
}

.solution-tile-desc {
  margin-bottom: 1rem;
  font-size: 0.875rem;
  color: var(--lt-ink-soft);
}

.solution-tile-link {
  margin-top: auto;
  padding-top: 1rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--lt-primary);
  text-decoration: none;
}

.solution-tile-link i {
  transition: transform 0.15s;
}

.solution-tile:hover .solution-tile-link i {
  transform: translateX(3px);
}

/* Arrows and chevrons must point the other way in Arabic. */
[dir="rtl"] .rtl-flip { transform: scaleX(-1); }
[dir="rtl"] .solution-tile:hover .solution-tile-link i { transform: scaleX(-1) translateX(3px); }

/* Subcategory pills -------------------------------------------------------- */
.subcat-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.subcat-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.3rem 0.7rem;
  border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-pill);
  background-color: var(--lt-surface);
  color: var(--lt-ink-soft);
  font-size: 0.775rem;
  font-weight: 500;
  text-decoration: none;
  transition: background-color var(--lt-transition),
              border-color var(--lt-transition),
              color var(--lt-transition);
}

.subcat-pill:hover {
  border-color: var(--lt-primary);
  background-color: var(--lt-primary-tint);
  color: var(--lt-primary);
}

.subcat-pill i { font-size: 0.65rem; opacity: 0.7; }

.subcat-pills-lg .subcat-pill {
  padding: 0.45rem 0.9rem;
  font-size: 0.875rem;
  background-color: var(--lt-surface-raised);
}

/* Section headings --------------------------------------------------------- */
.section-heading {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin-bottom: 1.25rem;
  padding-bottom: 0.7rem;
  border-bottom: 1px solid var(--lt-border-soft);
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--lt-heading);
}

/* Leading icon carries the brand colour; the text stays slate so the heading
   still reads as structure rather than as a link. */
.section-heading > i:first-child {
  color: var(--lt-primary);
  font-size: 1.1rem;
}

.section-heading-count {
  padding: 0.05rem 0.5rem;
  border-radius: var(--lt-radius-pill);
  background-color: var(--lt-surface);
  color: var(--lt-ink-soft);
  font-size: 0.775rem;
  font-weight: 600;
}

/* Category sidebar --------------------------------------------------------- */
.catalog-sidebar {
  position: sticky;
  /* Clears the sticky navbar. */
  top: 5.5rem;
  padding: 1.25rem;
  border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-md);
  background-color: var(--lt-surface-raised);
}

.catalog-sidebar-title {
  margin-bottom: 0.875rem;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--lt-ink-soft);
}

.catalog-sidebar-list,
.catalog-sidebar-children {
  margin: 0;
  padding: 0;
  list-style: none;
}

.catalog-sidebar-group {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.45rem 0.5rem;
  border-radius: var(--lt-radius-xs);
  color: var(--lt-heading);
  font-size: 0.875rem;
  text-decoration: none;
  transition: background-color 0.15s, color 0.15s;
}

.catalog-sidebar-group i { color: var(--lt-primary); opacity: 0.8; }

.catalog-sidebar-group:hover {
  background-color: var(--lt-primary-wash);
  color: var(--lt-primary);
}

/* The current group gets a crimson rail as well as the tint — with nine groups
   in the list, colour alone was a weak signal for "you are here". Logical
   border so it sits on the correct edge in Arabic. */
.catalog-sidebar-group.is-current {
  background-color: var(--lt-primary-tint);
  border-inline-start: 3px solid var(--lt-primary);
  padding-inline-start: calc(0.5rem - 3px);
  color: var(--lt-primary);
  font-weight: 600;
}

.catalog-sidebar-children {
  margin-block: 0.25rem 0.5rem;
  /* Logical padding and border so the guide rail flips for Arabic. */
  padding-inline-start: 1.4rem;
  border-inline-start: 2px solid var(--lt-border-soft);
  margin-inline-start: 0.9rem;
}

.catalog-sidebar-children a {
  display: block;
  padding: 0.3rem 0.4rem;
  border-radius: var(--lt-radius-xs);
  color: var(--lt-ink-soft);
  font-size: 0.8125rem;
  text-decoration: none;
  transition: color 0.15s, background-color 0.15s;
}

.catalog-sidebar-children a:hover {
  background-color: var(--lt-primary-wash);
  color: var(--lt-primary);
}

.catalog-sidebar-children a.is-current {
  color: var(--lt-primary);
  font-weight: 600;
}

.catalog-sidebar-cta {
  margin-top: 1.25rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--lt-border-soft);
  color: var(--lt-ink-soft);
}

/* The sidebar should not stick on narrow screens, where it sits above the
   content rather than beside it. */
@media (max-width: 991.98px) {
  .catalog-sidebar { position: static; }
}

/* Empty state -------------------------------------------------------------- */
/* ==========================================================================
   Empty state
   --------------------------------------------------------------------------
   Not an edge case. With 0 products, 0 published clients, 0 articles and 0
   downloads, this component *is* the catalog, the projects page and the
   resources centre for almost every visitor today — see PROJECT_PLAN.md §0.

   It used to be a dashed grey box, which reads as "broken" rather than "being
   prepared". Now it is a deliberate panel: solid border, raised surface, a
   crimson-tinted icon disc and a real primary CTA. The dashed border is
   deliberately gone — dashes signal a drop zone or a placeholder, and this is
   neither.
   ========================================================================== */

.empty-state {
  position: relative;
  overflow: hidden;
  padding: clamp(2.5rem, 5vw, 3.75rem) 1.5rem;
  border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-lg);
  background-color: var(--lt-surface-raised);
  box-shadow: var(--lt-shadow-sm);
  text-align: center;
}

/* The same crimson bloom the hero uses, so an empty category still feels like
   part of the site rather than a hole in it. */
.empty-state::before {
  content: "";
  position: absolute;
  inset-block-start: -55%;
  inset-inline-end: -15%;
  width: 24rem;
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(
    circle,
    color-mix(in srgb, var(--lt-primary) 9%, transparent) 0%,
    transparent 70%
  );
  pointer-events: none;
}

.empty-state > * { position: relative; }

.empty-state-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 4rem;
  height: 4rem;
  margin-bottom: 1.25rem;
  border-radius: 50%;
  background-color: var(--lt-primary-tint);
  color: var(--lt-primary);
  font-size: 1.65rem;
}

.empty-state h3,
.empty-state .h5 {
  margin-bottom: 0.6rem;
  font-size: var(--lt-h4);
}

.empty-state-text {
  max-width: 34rem;
  margin-inline: auto;
  margin-bottom: 1.75rem;
  color: var(--lt-ink-soft);
  font-size: var(--lt-text-base);
  line-height: 1.65;
}

.empty-state-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  justify-content: center;
}

/* Bottom call-to-action band ---------------------------------------------- */
/* The closing band on five pages. Slate base with a crimson bloom rather than a
   flat slate-to-crimson ramp: a straight two-stop gradient between those hues
   goes muddy through the middle, where they average into brown. */
.catalog-cta {
  position: relative;
  overflow: hidden;
  padding-block: clamp(2.75rem, 5vw, 4rem);
  background-color: var(--lt-slate-900);
  color: var(--lt-on-dark);
}

.catalog-cta::before {
  content: "";
  position: absolute;
  inset-block-start: -60%;
  inset-inline-end: -8%;
  width: min(40rem, 80vw);
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(
    circle,
    color-mix(in srgb, var(--lt-primary) 55%, transparent) 0%,
    transparent 68%
  );
  pointer-events: none;
}

.catalog-cta .container { position: relative; }

.catalog-cta h2 { color: #fff; }

.catalog-cta-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
}

.catalog-cta p {
  max-width: 40rem;
  color: var(--lt-on-dark);
  font-size: 0.9rem;
}

.catalog-cta-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

@media (prefers-reduced-motion: reduce) {
  .solution-tile,
  .solution-tile-link i,
  .subcat-pill { transition: none; }
  .solution-tile:hover { transform: none; }
  .solution-tile:hover .solution-tile-link i { transform: none; }
}

/* Product cards and imagery ------------------------------------------------ */
/* ==========================================================================
   Shared card recipe
   --------------------------------------------------------------------------
   Seven components were each declaring their own border, radius, surface and
   hover shadow — four different radii and three different shadows between them,
   which is most of why the site read as assembled rather than designed. One
   recipe now, split three ways:

     surface   the standalone components (the two .card-based ones inherit
               theirs from Bootstrap and are tuned through --bs-card-* below)
     motion    every card
     hover     lift for card-shaped things, border + shadow only for row-shaped
               ones, where a 3px jump on a full-width row reads as a glitch
   ========================================================================== */

.article-card,
.download-card,
.job-row,
.search-result,
.related-item {
  border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-md);
  background-color: var(--lt-surface-raised);
}

/* Bootstrap .card computes the inner radius of .card-img-top and .card-body
   from these, so setting the raw border-radius instead would leave a child's
   square corner poking through the parent's rounded one. */
.product-card,
.brand-card {
  --bs-card-border-color: var(--lt-border);
  --bs-card-border-radius: var(--lt-radius-md);
  --bs-card-inner-border-radius: calc(var(--lt-radius-md) - 1px);
  /* .product-card-image and .brand-card-logo are plain divs, not .card-img-top,
     so Bootstrap's inner-radius never reaches them and their square top corners
     showed through the card's rounded ones — visible once the radius grew to
     12px. Clipping is more robust here than rounding each child, which would
     also have to know which corner it is on in RTL. */
  overflow: hidden;
}

.product-card,
.brand-card,
.article-card,
.download-card,
.job-row,
.search-result,
.related-item {
  transition: border-color var(--lt-transition),
              box-shadow var(--lt-transition),
              transform var(--lt-transition);
}

.product-card:hover,
.brand-card:hover,
.article-card:hover,
.download-card:hover {
  border-color: var(--lt-primary);
  box-shadow: var(--lt-shadow-brand);
  transform: translateY(-3px);
}

.job-row:not(.is-closed):hover,
.search-result:hover,
.related-item:hover {
  border-color: var(--lt-primary);
  box-shadow: var(--lt-shadow-md);
}

@media (prefers-reduced-motion: reduce) {
  .product-card,
  .brand-card,
  .article-card,
  .download-card,
  .job-row,
  .search-result,
  .related-item { transition: none; }

  .product-card:hover,
  .brand-card:hover,
  .article-card:hover,
  .download-card:hover { transform: none; }
}

.product-card-image,
.product-hero-image,
.product-thumb,
.brand-card-logo,
.brand-header-logo {
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--lt-media-bg);
  overflow: hidden;
}

.product-card-image {
  height: 9rem;
  border-bottom: 1px solid var(--lt-border-soft);
  font-size: 2rem;
}

.product-hero-image {
  height: 22rem;
  border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-sm);
  font-size: 4rem;
}

.product-thumb {
  height: 4.5rem;
  border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-xs);
}

.product-card-image img,
.product-hero-image img,
.product-thumb img,
.brand-card-logo img,
.brand-header-logo img {
  max-height: 100%;
  max-width: 100%;
  object-fit: contain;
  padding: 0.5rem;
}

.w-35 { width: 35%; }

/* Specifications table -----------------------------------------------------
   Bootstrap's .table-striped alternates a grey fill across the full row, which
   on a two-column spec sheet reads as banding rather than structure. A tinted
   label column and hairline row rules make it scan as a definition list, which
   is what it is.

   Cannot be verified against real content — there are no products in the
   database yet (PROJECT_PLAN.md §0), so this is designed from the template's
   markup rather than from a rendered page. */
.spec-table {
  --bs-table-bg: transparent;
  margin-bottom: 0;
  border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-md);
  /* Needed for the radius to clip the first and last row borders. */
  overflow: hidden;
  border-collapse: separate;
  border-spacing: 0;
  font-size: var(--lt-text-sm);
}

.spec-table th,
.spec-table td {
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--lt-border-soft);
  vertical-align: top;
}

.spec-table tr:last-child th,
.spec-table tr:last-child td {
  border-bottom: 0;
}

.spec-table th {
  background-color: var(--lt-surface);
  color: var(--lt-heading);
  font-weight: 600;
  /* Logical border so the divider between label and value flips in Arabic. */
  border-inline-end: 1px solid var(--lt-border-soft);
}

.spec-table td { color: var(--lt-ink-prose); }

.spec-group-label {
  display: block;
  margin-bottom: 0.15rem;
  color: var(--lt-ink-soft);
  font-size: var(--lt-text-xs);
  font-weight: 500;
}

/* Brand cards -------------------------------------------------------------- */

.brand-card-logo {
  height: 5.5rem;
  border-bottom: 1px solid var(--lt-border-soft);
}

.brand-header-logo {
  height: 5rem;
  width: 10rem;
  padding: 0.5rem;
  border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-md);
  box-shadow: var(--lt-shadow-sm);
}

/* Was an inline style="font-size:.75rem" on brand_list.html. */
.brand-card-origin {
  color: var(--lt-ink-soft);
  font-size: var(--lt-text-xs);
}

@media (prefers-reduced-motion: reduce) {
  .solution-card,
  .partner-logo img { transition: none; }
  .solution-card:hover { transform: none; }
}

/* ==========================================================================
   Hand-written pages: About, Services, Industries, Projects
   ========================================================================== */

/* Opening paragraph of an industry page ----------------------------------- */
.lead-paragraph {
  font-size: 1.0625rem;
  line-height: 1.75;
  color: var(--lt-ink-prose);
}

/* About: team panel -------------------------------------------------------- */
.about-panel {
  padding: 1.5rem;
  border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-md);
  background-color: var(--lt-surface);
}

.about-panel-title {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
  font-size: 1rem;
  font-weight: 600;
  color: var(--lt-heading);
}

.about-panel-title i { color: var(--lt-primary); }

.team-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.team-list li {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding-block: 0.5rem;
}

.team-list li + li { border-top: 1px solid var(--lt-border); }

.team-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--lt-radius-xs);
  background-color: var(--lt-primary-tint);
  color: var(--lt-primary);
  font-weight: 700;
  font-size: 0.875rem;
  font-variant-numeric: tabular-nums;
}

.team-role { font-size: 0.9rem; }

/* About: vision and mission cards ------------------------------------------ */
.vm-card {
  display: flex;
  flex-direction: column;
  padding: 2rem;
  border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-md);
  background-color: var(--lt-surface-raised);
  box-shadow: var(--lt-shadow-sm);
}

/* About: values ------------------------------------------------------------ */
.value-card {
  display: flex;
  flex-direction: column;
  padding: 1.5rem;
  border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-md);
  background-color: var(--lt-surface-raised);
  text-align: center;
  align-items: center;
}

.value-card i {
  margin-bottom: 0.75rem;
  font-size: 1.75rem;
  color: var(--lt-primary);
}

/* About: closing statement ------------------------------------------------- */
.why-exist {
  padding: 2.5rem;
  border-radius: var(--lt-radius-md);
  /* Logical border so the accent rail sits on the correct side in Arabic. */
  border-inline-start: 4px solid var(--lt-primary);
  background-color: var(--lt-surface);
  color: var(--lt-ink-prose);
}

/* Services: sub-item lists ------------------------------------------------- */
.service-items {
  margin: 0;
  padding: 0;
  list-style: none;
}

.service-items li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding-block: 0.35rem;
  font-size: 0.9rem;
}

/* Services: turnkey project lifecycle -------------------------------------- */
.lifecycle {
  position: relative;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 1rem;
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: lifecycle;
}

/* The rail that turns five cards into one process.
   Each .lifecycle-number sits at top:-0.875rem with a height of 1.75rem, so its
   vertical centre lands exactly on the grid's top edge — which is why this only
   needs top:0 to thread through all five.
   Faded at both ends rather than measured to the outer node centres: those
   depend on the column width, which CSS cannot reference here. The fade is
   symmetric, so it needs no RTL mirror.
   The nodes are children and therefore paint over this. */
.lifecycle::before {
  content: "";
  position: absolute;
  top: 0;
  inset-inline: 0;
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(
    to right,
    transparent 0%,
    var(--lt-primary-tint-strong) 9%,
    var(--lt-primary-tint-strong) 91%,
    transparent 100%
  );
}

.lifecycle-step {
  position: relative;
  padding: 1.5rem 1.25rem 1.25rem;
  border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-md);
  background-color: var(--lt-surface-raised);
}

.lifecycle-number {
  position: absolute;
  top: -0.875rem;
  inset-inline-start: 1.25rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  background-color: var(--lt-primary-solid);
  color: var(--lt-on-primary);
  font-size: 0.8125rem;
  font-weight: 700;
}

/* Five across is too tight below desktop; two, then one. */
@media (max-width: 991.98px) {
  .lifecycle { grid-template-columns: repeat(2, 1fr); }

  /* Once the grid wraps, a single rail across the top would connect the first
     row only and float above the rest — worse than no rail at all. */
  .lifecycle::before { display: none; }
}

@media (max-width: 575.98px) {
  .lifecycle { grid-template-columns: 1fr; }
}

/* Industries: sector requirement list -------------------------------------- */
.need-item {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  height: 100%;
  padding: 0.875rem 1rem;
  border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-sm);
  background-color: var(--lt-surface-raised);
  font-size: 0.9rem;
}

.need-item i { margin-top: 0.15rem; flex-shrink: 0; }

/* Projects: customer logo wall --------------------------------------------- */
.client-sector + .client-sector { margin-top: 3rem; }

/* auto-fill rather than auto-fit so a sector holding two logos does not
   stretch them across the full row width. */
.client-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
  gap: 0.75rem;
}

.client-logo {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 6rem;
  padding: 0.875rem;
  border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-md);
  background-color: var(--lt-surface-raised);
  text-decoration: none;
  transition: border-color var(--lt-transition),
              box-shadow var(--lt-transition);
}

a.client-logo:hover {
  border-color: var(--lt-primary);
  box-shadow: var(--lt-shadow-brand);
}

.client-logo img {
  max-height: 100%;
  max-width: 100%;
  object-fit: contain;
  /* The logos come from a mixture of sources with clashing colours; muting
     them keeps the wall readable. Colour returns on hover. */
  filter: grayscale(100%);
  opacity: 0.72;
  transition: filter 0.15s, opacity 0.15s;
}

.client-logo:hover img {
  filter: grayscale(0);
  opacity: 1;
}

.client-logo-text {
  color: var(--lt-ink-soft);
  font-size: 0.8125rem;
  font-weight: 500;
  text-align: center;
}

@media (prefers-reduced-motion: reduce) {
  .client-logo,
  .client-logo img { transition: none; }
}

/* ==========================================================================
   Leads: contact and quote forms
   ========================================================================== */

/* Honeypot ---------------------------------------------------------------- */
/* Moved off-screen rather than `display: none`. Some bots skip fields that are
   display:none or type=hidden, but will happily fill a positioned one — and
   keeping it in the layout flow means screen readers can still skip it via the
   aria-hidden and tabindex="-1" set on the input itself. */
.lt-hp {
  position: absolute !important;
  inset-inline-start: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* Contact details panel --------------------------------------------------- */
.contact-panel {
  padding: 1.75rem;
  border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-md);
  background-color: var(--lt-surface-raised);
  box-shadow: var(--lt-shadow-md);
}

.contact-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.contact-list li {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding-block: 0.75rem;
}

.contact-list li + li { border-top: 1px solid var(--lt-border); }

.contact-list i {
  flex-shrink: 0;
  margin-top: 0.2rem;
  color: var(--lt-primary);
  font-size: 1.1rem;
}

.contact-label {
  display: block;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--lt-ink-soft);
}

.contact-value {
  display: block;
  color: var(--lt-heading);
  text-decoration: none;
  /* Phone numbers and emails must not force the panel wider than its column. */
  overflow-wrap: anywhere;
}

a.contact-value:hover { color: var(--lt-primary); }

/* Map --------------------------------------------------------------------- */
/* No top border any more: the map used to run edge to edge and needed a rule to
   separate it from the content above. It now sits inside .container as a
   bordered card, so a full-bleed line above it would cut the page in half. */

.location-map {
  display: block;
  width: 100%;
  height: 22rem;
  border: 0;
}

/* Wrapper so the iframe can be clipped to the same radius as everything else —
   an iframe ignores border-radius set on itself in several browsers. */
.map-frame {
  overflow: hidden;
  border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-md);
  box-shadow: var(--lt-shadow-sm);
}

/* Quote form: the product being quoted ------------------------------------ */
.quote-product {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-bottom: 1.75rem;
  padding: 1rem;
  border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-md);
  background-color: var(--lt-surface);
}

.quote-product-image {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 4.5rem;
  height: 4.5rem;
  border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-sm);
  background-color: var(--lt-media-bg);
  color: var(--lt-ink-soft);
  font-size: 1.5rem;
  overflow: hidden;
}

.quote-product-image img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  padding: 0.25rem;
}

.quote-product-label {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--lt-ink-soft);
}

.quote-product h2 a {
  color: var(--lt-heading);
  text-decoration: none;
}

.quote-product h2 a:hover { color: var(--lt-primary); }

/* Quote form: "what happens next" ----------------------------------------- */
.quote-steps {
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: quote-step;
}

.quote-steps li {
  position: relative;
  padding-block: 0.6rem;
  /* Logical padding so the numbers sit on the correct side in Arabic. */
  padding-inline-start: 2rem;
  counter-increment: quote-step;
}

.quote-steps li + li { border-top: 1px solid var(--lt-border-soft); }

.quote-steps li::before {
  content: counter(quote-step);
  position: absolute;
  inset-inline-start: 0;
  top: 0.65rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.4rem;
  height: 1.4rem;
  border-radius: 50%;
  background-color: var(--lt-primary-tint);
  color: var(--lt-primary);
  font-size: 0.75rem;
  font-weight: 700;
}

.quote-steps strong {
  display: block;
  font-size: 0.875rem;
  color: var(--lt-heading);
}

.quote-steps span {
  font-size: 0.8125rem;
  color: var(--lt-ink-soft);
}

/* Confirmation and throttle pages ----------------------------------------- */
.thanks-panel {
  max-width: 34rem;
  margin-inline: auto;
  padding: 3rem 2rem;
  border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-lg);
  background-color: var(--lt-surface-raised);
  box-shadow: var(--lt-shadow-md);
  text-align: center;
}

.thanks-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 4rem;
  height: 4rem;
  margin-bottom: 1.5rem;
  border-radius: 50%;
  background-color: color-mix(in srgb, var(--lt-success) 12%, transparent);
  color: var(--lt-success);
  font-size: 2rem;
}

.thanks-icon-warning {
  background-color: color-mix(in srgb, var(--lt-warning) 14%, transparent);
  color: var(--lt-warning);
}

.thanks-body {
  color: var(--lt-ink-soft);
}

.quote-reference {
  display: inline-block;
  padding: 0.15rem 0.6rem;
  border-radius: var(--lt-radius-xs);
  background-color: var(--lt-surface);
  color: var(--lt-primary);
  font-family: var(--lt-font-mono);
  /* A reference is a Latin-script code; it must not mirror in Arabic. */
  direction: ltr;
  unicode-bidi: isolate;
}

/* ==========================================================================
   Forms
   --------------------------------------------------------------------------
   Applies to the two crispy-rendered forms (contact and quote). Crispy's
   bootstrap5 pack emits .form-label / .form-control / .form-select / .form-text,
   so styling those covers both without touching the form classes in Python.

   Focus rings are handled globally in the Bootstrap bridge at the top of this
   file — Bootstrap compiles those colours as literals, so they cannot be
   reached through a variable.
   ========================================================================== */

.form-label {
  margin-bottom: 0.35rem;
  font-size: var(--lt-text-sm);
  font-weight: 600;
  color: var(--lt-heading);
}

.form-control,
.form-select {
  padding: 0.6rem 0.85rem;
  border-color: var(--lt-border);
  border-radius: var(--lt-radius-sm);
  font-size: var(--lt-text-base);
}

/* --lt-slate-300 was used here and measured 1.86:1 on white — unreadable. It is
   a fine border tone, which is what it exists for, but placeholder text is
   text. --lt-ink-soft gives 5.00:1 and still sits clearly lighter than an
   entered value at 14.62:1. */
.form-control::placeholder { color: var(--lt-ink-soft); opacity: 1; }

.form-control:hover:not(:focus),
.form-select:hover:not(:focus) {
  border-color: var(--lt-slate-300);
}

.form-text {
  font-size: var(--lt-text-xs);
  color: var(--lt-ink-soft);
}

textarea.form-control { min-height: 9rem; }

/* Validation. Bootstrap's is-invalid keeps its own red, which is correct: an
   error must not be the brand colour, or the two become the same signal. */
.form-control.is-invalid,
.form-select.is-invalid {
  border-color: var(--lt-danger);
}

.invalid-feedback {
  color: var(--lt-danger);
  font-size: var(--lt-text-xs);
  font-weight: 500;
}

/* The nav search sits inside an .input-group, where Bootstrap flattens the
   joined corners. Restore the scale's radius on the outer edges only. */
.input-group > .form-control:first-child,
.input-group > .input-group-text:first-child {
  border-start-start-radius: var(--lt-radius-sm);
  border-end-start-radius: var(--lt-radius-sm);
}

.input-group > .btn:last-child,
.input-group > .form-control:last-child {
  border-start-end-radius: var(--lt-radius-sm);
  border-end-end-radius: var(--lt-radius-sm);
}

.input-group-text {
  border-color: var(--lt-border);
  background-color: var(--lt-surface);
  color: var(--lt-ink-soft);
}

/* Required-field markers on crispy labels --------------------------------- */
/* Muted rather than red. A required field is not an error, and with crimson as
   the brand colour a red star next to every label competes with the actual
   error state for attention. See THEME_PLAN.md §2.4. */
.asteriskField {
  margin-inline-start: 0.15rem;
  color: var(--lt-ink-soft);
}

/* ==========================================================================
   Resources centre and careers
   ========================================================================== */

/* Selected state on the filter pills -------------------------------------- */
.subcat-pill.is-current {
  border-color: var(--lt-primary-solid);
  background-color: var(--lt-primary-solid);
  color: var(--lt-on-primary);
}

.subcat-pill.is-current:hover {
  background-color: var(--lt-primary-hover);
  border-color: var(--lt-primary-hover);
  color: #fff;
}

/* Article cards ----------------------------------------------------------- */
/* Surface, radius, motion and hover come from the shared card recipe. */
.article-card {
  display: flex;
  flex-direction: column;
  height: 100%;
  /* Clips the cover image to the card's rounded corners. */
  overflow: hidden;
}

.article-card-image {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 11rem;
  background-color: var(--lt-surface);
  border-bottom: 1px solid var(--lt-border-soft);
  color: var(--lt-primary);
  font-size: 2.25rem;
  text-decoration: none;
  overflow: hidden;
}

.article-card-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.article-card-body {
  display: flex;
  flex-direction: column;
  flex-grow: 1;
  padding: 1.25rem;
}

.article-card-tag {
  align-self: flex-start;
  margin-bottom: 0.6rem;
  padding: 0.15rem 0.55rem;
  border-radius: var(--lt-radius-pill);
  background-color: var(--lt-primary-tint);
  color: var(--lt-primary);
  font-size: 0.7rem;
  font-weight: 600;
  text-decoration: none;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.article-card-title {
  margin-bottom: 0.5rem;
  font-size: 1.02rem;
  font-weight: 600;
  line-height: 1.35;
}

.article-card-title a {
  color: var(--lt-heading);
  text-decoration: none;
}

.article-card:hover .article-card-title a { color: var(--lt-primary); }

.article-card-excerpt {
  margin-bottom: 1rem;
  color: var(--lt-ink-soft);
  font-size: 0.875rem;
}

.article-card-meta {
  margin-top: auto;
  padding-top: 0.5rem;
  color: var(--lt-ink-soft);
  font-size: 0.775rem;
}

/* Article detail ---------------------------------------------------------- */
.article-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
  color: var(--lt-ink-soft);
  font-size: 0.875rem;
}

.article-meta i { margin-inline-end: 0.3rem; }

.article-cover {
  width: 100%;
  max-height: 26rem;
  object-fit: cover;
  border-radius: var(--lt-radius-md);
  margin-bottom: 1.75rem;
}

.article-body p {
  margin-bottom: 1.1rem;
  line-height: 1.8;
  color: var(--lt-ink-prose);
}

.related-item {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  height: 100%;
  padding: 0.75rem 1rem;
  color: var(--lt-heading);
  font-size: 0.875rem;
  text-decoration: none;
}

.related-item i { color: var(--lt-primary); flex-shrink: 0; }

.related-item:hover {
  color: var(--lt-primary);
}

/* Download cards ---------------------------------------------------------- */
.download-card {
  display: flex;
  gap: 1rem;
  height: 100%;
  padding: 1.25rem;
}

.download-type {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  align-self: flex-start;
  width: 3rem;
  height: 3rem;
  border-radius: var(--lt-radius-sm);
  background-color: var(--lt-primary-tint);
  color: var(--lt-primary);
  font-size: 0.7rem;
  font-weight: 700;
  /* File extensions are Latin-script codes; they must not mirror in Arabic. */
  direction: ltr;
  unicode-bidi: isolate;
}

.download-body {
  display: flex;
  flex-direction: column;
  flex-grow: 1;
}

.download-title {
  margin-bottom: 0.25rem;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--lt-heading);
}

.download-meta {
  margin-bottom: 0.6rem;
  color: var(--lt-ink-soft);
  font-size: 0.775rem;
}

.download-desc {
  margin-bottom: 1rem;
  color: var(--lt-ink-soft);
  font-size: 0.85rem;
}

.download-body .btn { align-self: flex-start; }

/* Careers: vacancy rows --------------------------------------------------- */
.job-list {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.job-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.25rem 1.5rem;
}

.job-row.is-closed { opacity: 0.72; }
.job-row.is-closed:hover { border-color: var(--lt-slate-300); box-shadow: none; }

.job-row-main { flex: 1 1 18rem; }

.job-row-title {
  margin-bottom: 0.35rem;
  font-size: 1.05rem;
  font-weight: 600;
}

.job-row-title a {
  color: var(--lt-heading);
  text-decoration: none;
}

.job-row:not(.is-closed):hover .job-row-title a { color: var(--lt-primary); }

.job-row-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  color: var(--lt-ink-soft);
  font-size: 0.8125rem;
}

.job-row-meta i { margin-inline-end: 0.25rem; }

.job-badge {
  display: inline-block;
  margin-inline-start: 0.5rem;
  padding: 0.1rem 0.5rem;
  border-radius: var(--lt-radius-pill);
  font-size: 0.7rem;
  font-weight: 600;
  vertical-align: middle;
}

.job-badge-closed {
  background-color: var(--lt-border-soft);
  color: var(--lt-ink-soft);
}

/* Careers: role detail ---------------------------------------------------- */
.requirement-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.requirement-list li {
  display: flex;
  align-items: flex-start;
  gap: 0.65rem;
  padding-block: 0.5rem;
  font-size: 0.925rem;
}

/* Tinted disc rather than a loose glyph, matching .reason-icon on the homepage
   and .empty-state-icon — a check in a crimson circle reads as a confirmed
   requirement rather than as decoration. */
.requirement-list li > i:first-child {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 1.4rem;
  height: 1.4rem;
  margin-top: 0.1rem;
  border-radius: 50%;
  background-color: var(--lt-primary-tint);
  color: var(--lt-primary);
  font-size: 0.8rem;
}

.requirement-list i { margin-top: 0.25rem; flex-shrink: 0; }

.apply-deadline {
  padding: 0.6rem 0.8rem;
  margin-bottom: 1rem;
  border-radius: var(--lt-radius-sm);
  background-color: color-mix(in srgb, var(--lt-warning) 12%, transparent);
  color: var(--lt-warning);
  font-size: 0.8125rem;
}

.apply-deadline i { margin-inline-end: 0.35rem; }

.apply-closed {
  padding: 1.25rem;
  border-radius: var(--lt-radius-sm);
  background-color: var(--lt-surface);
  color: var(--lt-ink-soft);
  text-align: center;
}

.apply-closed i {
  display: block;
  margin-bottom: 0.5rem;
  font-size: 1.5rem;
  color: var(--lt-ink-soft);
}

/* Reduced-motion handling for these moved into the shared card recipe. */

/* ==========================================================================
   Search
   ========================================================================== */

/* Header search box -------------------------------------------------------- */
/* Width budget. Between 1200px and 1399px the menu is expanded and .container
   is 1140px, which must hold brand + ten items + search + CTA. Adding the Phase
   C wordmark took the brand from 42px to 104px; shrinking this box from 12rem
   to 9rem paid for it and then some.

   Measured after the change: brand 104 + menu 723 + search 144 + CTA 147 = 1118
   of 1140, so 22px spare and no wrap. That is the whole budget — anything else
   added to this row has to take its width from something already here. */
.nav-search {
  flex-shrink: 0;
  width: 9rem;
}

.nav-search .form-control { font-size: 0.8125rem; }

@media (max-width: 1199.98px) {
  /* Stacked menu: full width, with breathing room from the last nav item. */
  .nav-search {
    width: 100%;
    margin-top: 0.75rem;
  }
}

/* Results page ------------------------------------------------------------- */
.search-form { max-width: 40rem; }

.search-result {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  height: 100%;
  padding: 1rem 1.15rem;
  text-decoration: none;
}

.search-result-title {
  color: var(--lt-heading);
  font-weight: 600;
  font-size: 0.95rem;
}

.search-result:hover .search-result-title { color: var(--lt-primary); }

.search-result-desc {
  color: var(--lt-ink-soft);
  font-size: 0.85rem;
}

@media (prefers-reduced-motion: reduce) {
  .search-result { transition: none; }
}

/* ==========================================================================
   Django messages
   --------------------------------------------------------------------------
   Bootstrap's default alerts are pastel blocks with a heavy tinted border. The
   token versions use a solid rail on the inline-start edge instead, so they sit
   with the cards rather than shouting over them.
   ========================================================================== */

.alert {
  --bs-alert-border: 1px solid var(--lt-border);
  border-radius: var(--lt-radius-sm);
  border-inline-start-width: 3px;
  box-shadow: var(--lt-shadow-sm);
  font-size: var(--lt-text-sm);
}

.alert-success {
  --bs-alert-color: var(--lt-success);
  --bs-alert-bg: color-mix(in srgb, var(--lt-success) 8%, #fff);
  --bs-alert-border-color: var(--lt-success);
}

.alert-info {
  --bs-alert-color: var(--lt-info);
  --bs-alert-bg: var(--lt-info-tint);
  --bs-alert-border-color: var(--lt-info);
}

.alert-warning {
  --bs-alert-color: var(--lt-warning);
  --bs-alert-bg: color-mix(in srgb, var(--lt-warning) 10%, #fff);
  --bs-alert-border-color: var(--lt-warning);
}

.alert-danger,
.alert-error {
  --bs-alert-color: var(--lt-danger);
  --bs-alert-bg: color-mix(in srgb, var(--lt-danger) 8%, #fff);
  --bs-alert-border-color: var(--lt-danger);
}

/* ==========================================================================
   Error pages
   ========================================================================== */

.error-page {
  padding-block: var(--lt-section-y);
  text-align: center;
}

.error-code {
  margin-bottom: 0.5rem;
  font-size: clamp(4rem, 12vw, 7rem);
  font-weight: 700;
  line-height: 1;
  letter-spacing: var(--lt-tracking-tight);
  color: var(--lt-primary);
  font-variant-numeric: tabular-nums;
}

.error-lead {
  max-width: 34rem;
  margin-inline: auto;
  margin-bottom: 2rem;
  color: var(--lt-ink-soft);
}

.error-search {
  max-width: 34rem;
  margin-inline: auto;
  margin-bottom: 1.75rem;
}

/* Placeholder notice shown on pages awaiting later phases ----------------- */
/* Kept dashed on purpose, unlike .empty-state. This one marks content staff
   have not published yet rather than a designed state a visitor is meant to
   act on, and the dashes are the honest signal for that. */
.phase-placeholder {
  border: 1px dashed var(--lt-slate-300);
  border-radius: var(--lt-radius-md);
  background-color: var(--lt-surface);
  color: var(--lt-ink-soft);
  padding: 2.5rem;
  text-align: center;
}

/* ==========================================================================
   Motion
   --------------------------------------------------------------------------
   Two mechanisms, chosen for different failure modes.

   The hero entrance is pure CSS. A JS-driven entrance leaves the page blank
   when the script fails, and the hero is the first thing anyone sees — so it
   runs on load with `animation-fill-mode: both`, which means the worst case is
   "no animation", never "no headline".

   The scroll reveal has to be JS, because it depends on viewport position. The
   hidden state is therefore applied *by JS* (.js-reveal on <html>) rather than
   from here: if the script never runs, nothing was ever hidden. site.js also
   carries a 2s failsafe for the case where the script runs but the observer
   never fires.

   Both are cancelled under prefers-reduced-motion — see the backstop below,
   which zeroes animation-delay as well as duration. Without the delay reset, a
   staggered element would sit in its `from` state for its whole delay and then
   snap in, which is worse than no animation at all.
   ========================================================================== */

@keyframes lt-rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

.hero-eyebrow,
.hero-title,
.hero-lead,
.hero-actions,
.hero-trust {
  animation: lt-rise 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
}

.hero-eyebrow { animation-delay: 0.05s; }
.hero-title   { animation-delay: 0.14s; }
.hero-lead    { animation-delay: 0.24s; }
.hero-actions { animation-delay: 0.34s; }
.hero-trust   { animation-delay: 0.44s; }

/* Scroll reveal ----------------------------------------------------------- */
[data-reveal] {
  transition: opacity 0.6s cubic-bezier(0.22, 1, 0.36, 1),
              transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}

/* Only ever hidden when JS is present to reveal it again. */
.js-reveal [data-reveal] {
  opacity: 0;
  transform: translateY(20px);
}

.js-reveal [data-reveal].is-revealed {
  opacity: 1;
  transform: none;
}

/* Children of a revealed grid arrive in sequence rather than as one block.
   Capped at six: beyond that the last tile waits long enough to feel broken. */
.js-reveal [data-reveal-children] > * {
  transition: opacity 0.5s cubic-bezier(0.22, 1, 0.36, 1),
              transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
  opacity: 0;
  transform: translateY(16px);
}

.js-reveal [data-reveal-children].is-revealed > * {
  opacity: 1;
  transform: none;
}

.js-reveal [data-reveal-children].is-revealed > *:nth-child(1) { transition-delay: 0.00s; }
.js-reveal [data-reveal-children].is-revealed > *:nth-child(2) { transition-delay: 0.06s; }
.js-reveal [data-reveal-children].is-revealed > *:nth-child(3) { transition-delay: 0.12s; }
.js-reveal [data-reveal-children].is-revealed > *:nth-child(4) { transition-delay: 0.18s; }
.js-reveal [data-reveal-children].is-revealed > *:nth-child(5) { transition-delay: 0.24s; }
.js-reveal [data-reveal-children].is-revealed > *:nth-child(n + 6) { transition-delay: 0.30s; }

/* ==========================================================================
   Reduced motion — global backstop
   --------------------------------------------------------------------------
   The individual @media (prefers-reduced-motion) blocks above stay, because
   several of them do more than kill timing: the marquee reflows into a wrapped
   row, and the card hovers reset their transform so nothing shifts under the
   cursor. This block is the safety net for everything else — including
   Bootstrap's own transitions on dropdowns, collapses, modals and alerts,
   which nothing in this project was guarding.

   Placed last so it wins on cascade order. It only overrides timing, never
   animation-name, so `animation: none` in the specific rules above still holds
   and the marquee cannot jump to its end state and hide half the logos.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    /* Delay matters as much as duration here. A staggered element with
       fill-mode:both sits in its `from` state for the whole delay, so leaving
       a 0.44s delay in place would hold the hero trust line invisible and then
       snap it in — the opposite of what reduced motion is asking for. */
    animation-delay: 0s !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0s !important;
    scroll-behavior: auto !important;
  }
}

/* ==========================================================================
   Print
   --------------------------------------------------------------------------
   There was no print stylesheet at all. It matters for two documents in
   particular: the quote confirmation (which carries a reference number someone
   will file) and the contact page (address and phone). Everything else benefits
   from not wasting a cartridge on the navigation.

   Colours are forced to black on white — crimson at 9.25:1 on screen becomes a
   muddy grey on a mono laser, and background fills are dropped by most browsers
   in print anyway, which would leave white text on white paper.
   ========================================================================== */

@media print {
  /* Chrome and Firefox strip backgrounds unless asked; ask, then override the
     ones that would print as a solid black slab. */
  *,
  *::before,
  *::after {
    background: transparent !important;
    box-shadow: none !important;
    color: #000 !important;
    text-shadow: none !important;
  }

  body { font-size: 11pt; line-height: 1.45; }

  /* Chrome: page furniture and anything interactive. */
  .topbar,
  .site-nav,
  .site-footer,
  .whatsapp-float,
  .skip-link,
  .lt-breadcrumb,
  .catalog-cta,
  .catalog-sidebar,
  .nav-search,
  .logo-marquee,
  .hero-actions,
  .hero-visual,
  .empty-state-actions,
  .map-section,
  .dash-sidebar,
  .dash-topbar,
  .dash-toolbar,
  .dash-bulk-bar,
  form .btn,
  .btn-quote { display: none !important; }

  /* The sidebar is fixed-position, so its margin has to go too or every page
     prints with a 16rem blank gutter. */
  .dash-main { margin-inline-start: 0 !important; }

  /* Keep the mark: a printed quote should still be identifiably from elwan. */
  .footer-brand-mark,
  .site-brand-mark { display: block !important; }

  .hero,
  .catalog-hero,
  .stat-band,
  .empty-state,
  .thanks-panel,
  .contact-panel,
  .dash-panel {
    border: 1px solid #999 !important;
    padding: 0.75rem !important;
  }

  .hero::before,
  .catalog-hero::after,
  .empty-state::before { display: none !important; }

  /* A URL is useless on paper unless it is written out. Skipped for in-page
     anchors and for links whose text is already the address. */
  a[href^="http"]::after,
  a[href^="mailto"]::after {
    content: " (" attr(href) ")";
    font-size: 9pt;
    word-break: break-all;
  }

  a[href^="#"]::after { content: ""; }

  /* Never orphan a heading at the foot of a page, or split a reference. */
  h1, h2, h3, h4, h5, h6 { break-after: avoid-page; }
  .quote-reference,
  .thanks-panel,
  .lifecycle-step,
  .solution-tile { break-inside: avoid; }

  @page { margin: 1.5cm; }
}
