   /* =====================================================================
   SHARED PAGE STYLES
   Loaded on every page (see app/views/layouts/application.html.erb),
   before each page's own stylesheet_link_tag, so per-page CSS still
   wins the cascade wherever it needs to override something here.

   Contains only rules verified to be duplicated verbatim (ignoring
   whitespace/formatting) across 3+ of the 11 page stylesheets in
   app/assets/stylesheets/pages/. Anything that differs between pages
   (even slightly) was left in its own page file — see the per-file
   overrides noted inline below for the handful of cases where a page
   is 95% identical to the shared rule but tweaks one property.
   ===================================================================== */

/* ---------------------------------------------------------------------
   FAMILY A — "Listing" pages
   about.css, blogs_index.css, contact.css, events_index.css, home.css,
   products_index.css
   --------------------------------------------------------------------- */

:root {
  --bg: #ffffff;
  --bg-2: #f5f8fc;
  --bg-3: #eef3f9;
  --surface: #ffffff;
  --line: #e3eaf3;
  --line-strong: #d4dde9;
  --text: #0b1e3f;
  --text-dim: #5a6a82;
  /* WCAG 2.2 AA (1.4.3) needs 4.5:1 for body-size text. The old #8a96aa was 2.99:1 on white
     and 2.81:1 on --bg-2, failing everywhere it was used (blog card meta, filter headings,
     input placeholders). #616f86 is 5.09:1 on white, 4.78:1 on --bg-2, 4.56:1 on --bg-3.
     It sits close to --text-dim by necessity: on a white ground there is very little room
     below 4.5:1, so lean on font-size and weight for hierarchy, not on lighter grey. */
  --text-mute: #616f86;
  --accent: #0d5cf5;
  --accent-dark: #0942b8;
  --accent-2: #1ea7ff;
  --accent-soft: rgba(13, 92, 245, 0.08);
  --dark: #0b1e3f;
  --dark-2: #122c54;
  --radius: 14px;
  --container: 1280px;
  --t: cubic-bezier(.65,.05,.36,1);
}

*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; transition: color .25s var(--t); }
ul { list-style: none; }

/* body: common subset only — home.css overrides line-height/font-weight.
   `overflow-x: clip` (not `hidden`) — `hidden` makes body a scroll container on both axes per
   the CSS Overflow spec, which silently breaks `position: sticky` on every descendant (sidebar
   nav, sticky headers) across the site. `clip` blocks horizontal overflow the same way without
   that side effect. */
body {
  font-family: 'Inter', sans-serif;
  background: var(--bg);
  color: var(--text);
  line-height: 1.6;
  overflow-x: clip;
  -webkit-font-smoothing: antialiased;
}

.container { max-width: var(--container); margin: 0 auto; padding: 0 24px; }

h1, h2, h3, h4, h5, h6 { font-family: 'Archivo', sans-serif; font-weight: 800; line-height: 1.1; letter-spacing: -0.02em; color: var(--text); }
.section-title { font-size: clamp(2rem, 4vw, 3.4rem); font-weight: 900; }

/* section-eyebrow: common subset — home.css overrides font-family (Manrope) */
/* Visually hidden, still read by assistive tech. For labels that the visual design conveys
   through placement or a placeholder instead of visible text -- a placeholder is NOT an
   accessible name (WCAG 3.3.2 / 4.1.2): it disappears on focus and is not exposed reliably. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Skip link (layout, first element in <body>) -- WCAG 2.4.1.
   Hidden by being pulled above the viewport rather than with display:none/visibility:hidden,
   which would drop it out of the tab order entirely and defeat the point. It returns to
   top:0 on :focus only.
   z-index must beat .site-header's 100: the header is position:sticky at top:0, i.e. exactly
   where this lands, so a lower value would reveal the link underneath the header where the
   user cannot see what they just focused. */
.skip-link {
  position: absolute;
  top: -100px;
  left: 16px;
  z-index: 200;
  padding: 12px 20px;
  background: var(--accent);
  color: #ffffff;
  font-family: 'Montserrat', sans-serif;
  font-size: 0.85rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  border-radius: 0 0 8px 8px;
  /* Only the offset animates. `transition: all` here would also animate the focus outline
     into view, which reads as a lag on the very affordance that signals focus landed. */
  transition: top .2s var(--t);
}
.skip-link:focus {
  top: 0;
  /* Explicit outline rather than relying on the UA default: the link sits on --accent, and
     Chrome's default focus ring is a similar blue that all but disappears against it. */
  outline: 3px solid var(--text);
  outline-offset: 2px;
}

/* The skip target is an empty positioning marker, not a visible element. Without this it is
   a normal block box and contributes its line-height as blank space between the header and
   the first section on every page. */
#main-content {
  height: 0;
  /* Suppresses the focus ring the browser would otherwise draw around a zero-height box
     spanning the full page width -- a stray line under the header on every skip. The user's
     focus indication is the content they land on, and scroll position confirms the jump. */
  outline: none;
}

.section-eyebrow { display: inline-flex; align-items: center; gap: 12px; font-family: 'Archivo', sans-serif; font-size: 0.78rem; font-weight: 700; letter-spacing: 0.18em; color: var(--accent); text-transform: uppercase; margin-bottom: 18px; }
.section-eyebrow .line { width: 32px; height: 2px; background: var(--accent); }
.section-eyebrow--light { color: var(--accent-2); }
.section-eyebrow--light .line { background: var(--accent-2); }
.section-eyebrow--center { justify-content: center; }

.section-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 32px; margin-bottom: 56px; flex-wrap: wrap; }
.section-head--center { justify-content: center; text-align: center; }

/* Buttons: common subset — about.css and home.css add position:relative;overflow:hidden to .btn */
.btn { display: inline-flex; align-items: center; gap: 12px; padding: 14px 26px; font-family: 'Archivo', sans-serif; font-weight: 700; font-size: 0.9rem; letter-spacing: 0.05em; text-transform: uppercase; border-radius: 50px; border: none; cursor: pointer; transition: all .35s var(--t); }
.btn--primary { background: var(--accent); color: #ffffff; }
.btn--primary:hover { background: var(--accent-dark); transform: translateY(-2px); box-shadow: 0 14px 30px rgba(13, 92, 245, 0.35); }
.btn--ghost { background: transparent; color: var(--text); border: 1px solid var(--line-strong); }
.btn--ghost:hover { background: var(--text); color: #ffffff; border-color: var(--text); }
.btn--ghost-light { background: rgba(255,255,255,0.08); color: #ffffff; border: 1px solid rgba(255,255,255,0.25); backdrop-filter: blur(10px); }
.btn--ghost-light:hover { background: #ffffff; color: var(--accent); }
.btn--lg { padding: 18px 34px; font-size: 0.95rem; }

/* Topbar */
.topbar { background: var(--dark); font-size: 0.8rem; color: rgba(255,255,255,0.75); }
.topbar__inner { display: flex; justify-content: space-between; align-items: center; padding: 10px 24px; flex-wrap: wrap; gap: 8px; }
.topbar__left, .topbar__right { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.topbar__sep { color: rgba(255,255,255,0.2); }
.topbar i { color: var(--accent-2); margin-right: 6px; }
.topbar__social { display: flex; gap: 10px; }
.topbar__social a { width: 26px; height: 26px; display: inline-flex; align-items: center; justify-content: center; border-radius: 50%; background: rgba(255,255,255,0.08); font-size: 0.7rem; }
.topbar__social a i { color: inherit; margin: 0; }

/* Topbar — mobile responsiveness. Centralized here (rather than duplicated per
   page) so every page and subpage gets identical topbar behavior on small
   screens: shrink the text, and hide everything in .topbar__left except the
   first item (plus the separator) so it doesn't wrap or crowd the header.
   The address is a Google Maps <a>, not a <span>, so it needs its own rule —
   it's the longest item and the main cause of the topbar wrapping. */
@media (max-width: 768px) {
  .topbar__left, .topbar__right { font-size: 0.72rem; gap: 8px; }
  .topbar__left span:not(:first-child),
  .topbar__sep,
  .topbar__address { display: none; }

  /* Keep mail (left) and phone (right) side by side on one row instead of the
     right group wrapping underneath. The email is the only item allowed to shrink
     (ellipsis) so the phone number is never cut off. */
  .topbar__inner { flex-wrap: nowrap; gap: 12px; padding: 8px 16px; }
  .topbar__left, .topbar__right { flex-wrap: nowrap; }
  .topbar__left { min-width: 0; }
  .topbar__left a { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .topbar__right { flex-shrink: 0; }
  .topbar__right a { white-space: nowrap; }
  .topbar__social { gap: 6px; }
  .topbar__social a { width: 22px; height: 22px; }
}

/* On the narrowest phones there isn't room for mail + phone + social icons on
   one row, so the social icons give way (they're repeated in the footer). */
@media (max-width: 420px) {
  .topbar__social { display: none; }
}

/* Header / Nav */
/* Custom properties on :root are global, and several page stylesheets
   (product_category.css, product_detail.css, blog_detail.css, blog_author.css,
   event_detail.css) declare their own `:root { --accent: ...;
   --text: ...; --line: ...; --t: ...; }` for their own page content, reusing the same
   names as shared.css's tokens above but different values (e.g. --accent is #1ea7ff on
   those pages vs #0d5cf5 here). Because :root wins globally regardless of which
   stylesheet loads second, the header/nav -- including the mega menu's heading color
   and hover underline, both set via var(--accent) -- silently inherited whichever
   page's accent happened to be declared, so the same nav rendered a different color on
   different pages. Re-declaring the tokens the header subtree actually uses directly on
   `.site-header` pins them for every descendant (.nav, .mega, .brand, ...) via normal
   inheritance from the nearest ancestor that sets them, regardless of any page-level
   :root redefinition or load order -- so the header looks identical everywhere. */
.site-header {
  --line: #e3eaf3;
  --text: #0b1e3f;
  --text-dim: #5a6a82;
  --accent: #0d5cf5;
  --accent-soft: rgba(13, 92, 245, 0.08);
  --t: cubic-bezier(.65,.05,.36,1);
}
.site-header { position: sticky; top: 0; z-index: 100; background: rgba(255,255,255,0.92); backdrop-filter: blur(16px); border-bottom: 1px solid var(--line); transition: all .3s var(--t); }
.site-header.scrolled { background: rgba(255,255,255,0.98); box-shadow: 0 8px 30px rgba(11, 30, 63, 0.08); }
.header__inner { display: flex; align-items: center; justify-content: space-between; padding: 16px 24px; gap: 24px; }
/* `min-width: 0` lets .brand actually shrink inside the flex row below — without it a flex
   item's default `min-width: auto` floors its shrink at its content's natural width, so on
   narrow viewports .brand (logo + full company name) refuses to shrink and pushes
   .menu-toggle's hamburger tap target partly off-screen instead. */
.brand { display: flex; align-items: center; gap: 12px; min-width: 0; }
/* `overflow: hidden` clips the fallback alt-text/broken-image glyph browsers render in place
   of an `<img>` that fails to load, so a broken logo URL degrades to an empty box instead of
   multi-line alt text blowing out the header's height and width. */
/* `font-size: 0` hides the fallback alt-text glyph browsers render when the `src` fails to
   load (Chrome renders it as inline text content, which `overflow: hidden` alone doesn't
   reliably clip) — the `alt` attribute is still exposed to the accessibility tree regardless
   of font-size, so this doesn't affect screen readers. */
.brand__logo { display: block; width: 4.5rem; height: 4.5rem; object-fit: contain; overflow: hidden; flex-shrink: 0; font-size: 0; }
.site-header .brand__logo { margin: -10px 0; }
.brand__text { display: flex; flex-direction: column; line-height: 1; min-width: 0; }
/* Canonical header/nav chrome, matching home.css's look (the reference for every page).
   .brand__mark uses a literal color rather than var(--accent): "Family B" detail pages
   (event_detail.css etc.) redefine --accent to a different value (#1ea7ff, their own
   lighter highlight color) for their own content, which would make the logo mark drift
   from home's if this used the variable. */
.brand__mark {
  width: 44px; height: 44px;
  background: #0d5cf5;
  color: #ffffff;
  font-family: 'Montserrat', sans-serif;
  font-weight: 900;
  font-size: 1.05rem;
  display: flex; align-items: center; justify-content: center;
  border-radius: 8px;
  letter-spacing: -0.02em;
  position: relative;
}
.brand__mark::after {
  content: "";
  position: absolute; inset: 0;
  border-radius: 8px;
  border: 2px solid #0d5cf5;
  transform: translate(4px, 4px);
  opacity: 0.25;
  pointer-events: none;
}
.brand__sub { font-size: 0.65rem; font-weight: 600; letter-spacing: 0.32em; color: var(--text-dim); margin-top: 4px; font-family: 'Montserrat', sans-serif; }
/* Header wordmark: stacked two-line style referencing the uploaded logo's own lockup — bold
   accent-colored first line (every word but the last), smaller wide-tracked uppercase second
   line (the last word) underneath. `.brand__name` itself stays the flex column + base font so
   the footer's plain single-span usage (no -primary/-secondary children there) is unaffected. */
.brand__name { display: flex; flex-direction: column; min-width: 0; line-height: 1.15; font-family: 'Montserrat', sans-serif; font-weight: 900; font-size: 1.05rem; letter-spacing: 0.04em; color: #000000; }
.brand__name-primary {
  color: #000000; text-transform: uppercase;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.brand__name-secondary {
  font-size: 0.62em; font-weight: 700; letter-spacing: 0.28em; text-transform: uppercase;
  color: inherit; margin-top: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.brand--light .brand__logo { background: #ffffff; border-radius: 8px; padding: 0 2px; box-sizing: border-box; height: 3rem; }
.brand--light .brand__name { color: #ffffff; }
.brand--light .brand__name-primary { color: #ffffff; }
.brand--light .brand__sub { color: rgba(255,255,255,0.5); }

/* Header brand — mobile responsiveness. Centralized here (rather than duplicated per page)
   so every page gets identical behavior on small screens: shrink the logo and cap the
   wordmark's width so it truncates with an ellipsis instead of colliding with — or, with a
   broken/unreachable logo image, pushing off-screen — the .menu-toggle hamburger button.

   MUST stay below the base .header__inner / .brand / .brand__logo / .brand__text /
   .brand__name declarations above. A media query contributes nothing to specificity, so
   these selectors tie with their unscoped counterparts and the cascade falls through to
   source order. This block previously sat ~80 lines higher (just under the topbar's own
   768px block), which put it BEFORE every rule it overrides — so all six declarations lost
   and the block was dead: on a phone the logo stayed at the desktop 4.5rem with its -10px
   vertical bleed and the header kept its 16px/24px padding, which is exactly the crowding
   this block exists to prevent. Keep any new brand rule above this block, not below it. */
@media (max-width: 480px) {
  .header__inner { padding: 12px 16px; gap: 12px; }
  .brand { gap: 8px; }
  .brand__logo { width: 3rem; height: 3rem; }
  .site-header .brand__logo { margin: 0; }
  .brand__text { max-width: 46vw; }
  .brand__name { font-size: 0.92rem; }
}
.nav > ul { display: flex; gap: 4px; }
.nav li { position: relative; }
/* .has-mega opts back out of that relative positioning so .mega's nearest positioned
   ancestor is .site-header (position: sticky) instead of the trigger <li> -- that's what
   lets the panel span the full header width edge-to-edge rather than floating as a small
   card centered under the trigger link. */
.nav .has-mega { position: static; }
.nav a { display: inline-flex; align-items: center; gap: 6px; padding: 10px 14px; font-weight: 600; font-size: 0.92rem; color: var(--text-dim); border-radius: 50px; transition: all .25s var(--t); font-family: 'Montserrat', sans-serif; }
.nav a:hover, .nav a.active { color: var(--accent); background: var(--accent-soft); }
/* :focus-within keeps the panel open via Tab: it re-matches as focus moves from the trigger
   link onto the links inside .mega itself, so keyboard users can tab through the whole
   dropdown rather than it disappearing the instant focus leaves the trigger. */
.nav .has-mega:hover > .mega,
.nav .has-mega:focus-within > .mega { opacity: 1; visibility: visible; transform: translateY(0); }
/* Full-bleed flat panel attached to the header (FirstCry-style mega menu): edge-to-edge
   under the header rather than a floating rounded card, sized to `--container` (1280px)
   via the symmetric padding below so it lines up with the rest of the page.

   Flex columns, NOT CSS multi-column (and not grid). Both earlier attempts are recorded
   here because each failed on something specific:

   * grid `repeat(5, minmax(140px, 220px))` — a grid's shared row tracks stretch every
     column in a row to the tallest one, leaving big empty gaps under a 3-item category
     sitting beside a 10-item one. `max-content` tracks were worse: one long unwrapped
     line in row 2 widened that track for the whole grid and spilled across its neighbor.
   * multicol `column-count: 5/6` + `column-fill` — the column count is fixed up front,
     so the tracks always span the full content box. With ~5 columns' worth of categories
     and a 6-track cap the last track came out EMPTY and every bit of leftover width
     collected on one side, reading as left-aligned with a wide right gutter. `column-fill:
     balance` doesn't fix it either: per spec balancing is about equal track heights, and
     Chrome falls back to `auto`-style greedy filling whenever the multicol box has a
     definite height — which this panel does (max-height, right below). There is no way to
     tell multicol "use only as many tracks as the content needs, then center them".

   Flex, wrapping along the COLUMN axis (`flex-flow: column wrap`) — not the row axis.
   Row-wrapping was tried first and packs badly: every column is its own flex item on one
   row, the row is as tall as its tallest column (Online Check Weigher, 10 links), and a
   category that wraps to a second row starts *below* that tallest column, leaving a screen
   of dead space under the short ones. Wrapping along the column axis instead makes the
   main axis vertical: categories stack top-to-bottom, filling one column to the panel's
   max-height before starting the next one over — the tight, source-order packing multicol
   gave, where a 3-item category sits directly under a 4-item one in the same column.

   `align-content: center` is what centers it: with a vertical main axis the columns are
   the flex LINES, so align-content distributes them along the cross (horizontal) axis and
   splits leftover width equally on both sides. This is the piece multicol could never do
   — its track count is fixed up front, so the tracks always spanned the full content box
   regardless of how few were occupied.

   `.mega__col` needs an explicit `width` (below), not `flex-basis`: basis sizes the main
   axis, which is now the vertical one.

   The max-height cap now does double duty: it still bounds the panel on a 1366x768 laptop
   (real budget is calc(100vh - 160px) = 608px), and it is also the height each column
   fills before the next one starts, i.e. what decides how many columns there are. Raise it
   and the categories redistribute into fewer, taller columns. It stays a `max-height`
   rather than a `height` so a short menu (a `.has-mega` item with two children) renders a
   short panel instead of 620px of white.
   The mobile block (max-width: 1024px, further below) resets `display` to block and the
   height cap to none; it does not inherit any of this. */
.mega { position: absolute; top: 100%; left: 0; right: 0; width: 100%; transform: translateY(10px); max-height: min(620px, calc(100vh - 160px)); overflow-y: auto; scrollbar-width: thin; scrollbar-color: rgba(11,30,63,0.15) transparent; display: flex; flex-flow: column wrap; align-content: center; justify-content: flex-start; gap: 20px 24px; background: #ffffff; border: none; border-top: 1px solid var(--line); border-radius: 0; padding: 16px max(24px, calc((100vw - var(--container)) / 2)); box-shadow: 0 16px 32px rgba(11, 30, 63, 0.08); opacity: 0; visibility: hidden; transition: all .3s var(--t); }
.mega::-webkit-scrollbar { width: 6px; }
.mega::-webkit-scrollbar-track { background: transparent; }
.mega::-webkit-scrollbar-thumb { background: rgba(11,30,63,0.15); border-radius: 3px; }
.mega::-webkit-scrollbar-thumb:hover { background: rgba(11,30,63,0.25); }
/* A column stacks vertically: shared/mega_group can nest an arbitrary number of
   labeled sections (heading + links) inside one column, one under the other, rather
   than a column being limited to a single heading. */
/* The 20px gap between stacked categories comes from the panel's own `gap` (row axis =
   vertical here), not a margin: the flex line-breaking algorithm accounts for gaps when
   deciding where a column ends, but not for item margins -- with a margin the last
   category in each column overhung the max-height it was measured against. */
.mega__col { display: flex; flex-direction: column; text-align: left; flex: 0 0 auto; width: 180px; max-width: 100%; }
/* Only spaces sibling sections stacked inside the same column -- the gap between
   columns themselves already comes from the grid's own `gap`. */
.mega__col > .mega__col--group + .mega__col--group,
.mega__col > .mega__col--group + a,
.mega__col > a + .mega__col--group { margin-top: 18px; }
.mega__col-heading { font-size: 0.82rem; line-height: 1.1; color: var(--accent); letter-spacing: 0.02em; text-transform: uppercase; margin-bottom: 10px; padding-bottom: 8px; border-bottom: 1px solid var(--line); font-family: 'Montserrat', sans-serif; font-weight: 700; overflow-wrap: break-word; word-break: break-word; transition: border-color .2s var(--t), box-shadow .2s var(--t); }
/* Thicker underline via box-shadow, NOT border-bottom-width/padding: .mega wraps on the
   column axis, so its column breaks depend on each heading's exact height. Changing the
   border width on hover changed that height (border widths snap to device pixels, so 2px
   border + 7px padding != 1px + 8px at fractional DPR, and differ mid-transition), which
   re-flowed the whole panel from 6 columns to 5 under the cursor. A shadow paints only. */
.mega__col--group:hover > .mega__col-heading { border-bottom-color: var(--accent); box-shadow: 0 1px 0 var(--accent); }
/* Nested sections (a heading inside another column section, depth > 0) read as
   sub-headings rather than repeating the outer divider at every level. */
.mega__col--group--nested { margin-top: 14px; }
.mega__col--group--nested > .mega__col-heading { font-size: 0.78rem; border-bottom: none; padding-bottom: 0; margin-bottom: 6px; }
/* Desktop reveals the whole mega panel on :hover (line 148 above), so the per-category
   accordion toggle has nothing to do there — only shown back inside the mobile media
   query below, where shared.js wires up the tap-to-expand behavior. */
.mega__col-toggle { display: none; }
.mega__col a { display: block; padding: 2px 0; font-size: 0.86rem; color: var(--text-dim); background: none !important; border-radius: 0; font-family: 'Montserrat', sans-serif; overflow-wrap: break-word; word-break: break-word; }
.mega__col a:hover { color: var(--accent); }
/* Bullet marker per product link, with a hanging indent so a label that wraps to two lines
   stays visibly one entry (the wrapped line aligns under the text, not under the bullet).
   The indent is the same on every link, so marking the active one only recolours the bullet
   and never re-wraps a label. Desktop only -- the mobile accordion already separates links
   with each page's `.nav.open a` border-bottom. */
@media (min-width: 1025px) {
  .mega__col a { position: relative; padding: 4px 0 4px 14px; line-height: 1.4; }
  .mega__col a::before { content: ""; position: absolute; left: 0; top: calc(4px + 0.7em - 2.5px); width: 5px; height: 5px; border-radius: 50%; background: rgba(11, 30, 63, 0.22); transition: background .2s var(--t), transform .2s var(--t); }
  .mega__col a:hover::before,
  .mega__col a.active::before { background: var(--accent); transform: scale(1.3); }
}
/* Current-page link inside the open panel: accent colour only. No rail, indent, or weight
   change -- those shift the label and can re-wrap it inside the narrow columns. Pinned here
   (rather than relying on `.nav a.active`) at a specificity above `.mega__col a:hover` so
   hovering a sibling never leaves two links looking current. */
.mega__col a.active { color: var(--accent); }

/* Mobile mega-menu toggle. The mega panel is hover-only above (line 148), which is
   unreachable on touch, and every page's own mobile nav block sets `.mega { display:
   none; }` unconditionally (e.g. home.css:887) — so a `.has-mega` item like "Products"
   had no way to ever reveal its dropdown on a phone. Centralized here (rather than
   patched into all 12 page CSS files) using a selector specific enough
   (.nav.open .has-mega.open > .mega, at 0-5-0) to beat each page's `.mega { display:
   none }` regardless of load order. Toggled via shared.js's "MOBILE MEGA MENU" handler.
   Single-column + explicit width/box-sizing/word-break on every descendant avoids the
   horizontal-overflow/clipping bug a naive `display: block` here previously caused. */
@media (max-width: 1024px) {
  .nav.open .has-mega.open > a i.fa-chevron-down { transform: rotate(180deg); }
  .nav.open .has-mega > a i.fa-chevron-down { transition: transform .25s var(--t); }
  .nav.open .has-mega.open > .mega {
    /* `block`, not the desktop panel's centered flex row: on a phone this is an accordion,
       one full-width category under the next, so the columns must stack rather than sit
       side by side. This also neutralizes the desktop `flex: 0 1 180px` / `max-width` on
       `.mega__col` (overridden to 100% just below), since flex item properties stop
       applying once the parent is no longer a flex container. */
    display: block;
    /* Desktop's `max-height: min(620px, calc(100vh - 160px))` + `overflow-y: auto` isn't
       reset here otherwise, and it must be: `.nav.open` (the drawer, not `.mega`) is what
       actually scrolls on mobile -- shared.js sets its inline max-height live to the real
       space under the header -- so a second, nested scroll region inside it would trap the
       tail of a long category behind a scrollbar the user has no reason to look for. */
    max-height: none;
    overflow-y: visible;
    position: static;
    width: 100%;
    max-width: 100%;
    box-sizing: border-box;
    opacity: 1;
    visibility: visible;
    transform: none;
    background: transparent;
    border: none;
    border-radius: 0;
    box-shadow: none;
    padding: 4px 0 12px 16px;
  }
  .nav.open .has-mega.open > .mega .mega__col {
    width: 100%;
    max-width: 100%;
    box-sizing: border-box;
  }
  .nav.open .has-mega.open > .mega .mega__col + .mega__col { margin-top: 0; }
  .nav.open .has-mega.open > .mega .mega__col-heading {
    /* Same size as `.nav a` above (0.92rem) so the accordion heading reads as a
       continuation of the top-level list rather than a smaller sub-list. */
    font-size: 0.92rem;
    border-bottom: none;
    margin-bottom: 0;
    /* Same 14px vertical rhythm as `.nav.open a` above, so the accordion reads as a
       continuation of the top-level list rather than a cramped sub-list. */
    padding: 14px 0;
    white-space: normal;
    overflow-wrap: break-word;
    word-break: break-word;
  }
  .nav.open .has-mega.open > .mega .mega__col a {
    font-size: 0.92rem;
    padding: 14px 0;
    white-space: normal;
    overflow-wrap: break-word;
    word-break: break-word;
  }

  /* Nested per-category accordion — collapsed by default so a "Products" dropdown with
     several categories doesn't dump every link at once; toggled by shared.js. Only
     .mega__col--group (a column with its own .mega__col-heading label) collapses — an ungrouped
     single link column (no heading to tap) is unaffected. */
  .nav.open .has-mega.open > .mega .mega__col--group .mega__col-heading {
    display: flex;
    align-items: center;
    justify-content: space-between;
    cursor: pointer;
  }
  .nav.open .has-mega.open > .mega .mega__col--group .mega__col-toggle {
    display: inline-block;
    font-size: 0.6rem;
    transition: transform .25s var(--t);
  }
  .nav.open .has-mega.open > .mega .mega__col--group.open .mega__col-toggle { transform: rotate(180deg); }
  .nav.open .has-mega.open > .mega .mega__col--group:not(.open) a { display: none; }

  /* Each page's own `.nav.open { position: absolute; top: 100%; ... }` (e.g.
     home.css:877) sizes itself relative to the sticky header — with no bottom bound, a
     tall open panel (especially with a mega dropdown + several accordion categories
     expanded) just overflows past the viewport with nowhere of its own to scroll, so the
     touch-scroll gesture falls through to the page behind it instead. A fixed
     `calc(100vh - 90px)` isn't reliable either, since the topbar's contact info wraps to
     2 lines on narrow screens, changing the header's real height.
     `position: fixed` + `bottom: 0` was tried here to pin the panel exactly to the
     viewport, but `.site-header` has `backdrop-filter: blur(16px)` — and a
     `backdrop-filter`/`transform`/`filter` ancestor becomes the *containing block* for any
     `position: fixed` descendant (same as it would for `position: absolute`), so the panel
     was actually being fixed relative to the ~90px-tall header box, not the viewport,
     and effectively vanished off-screen. Staying on `position: absolute` (inherited from
     the page CSS) sidesteps that trap entirely — shared.js measures the real available
     height live (`window.innerHeight - header.getBoundingClientRect().bottom`) and sets
     it as an inline `max-height` each time the panel opens or the viewport resizes. */
  .nav.open {
    overflow-y: auto;
    overflow-x: hidden;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
  }
}
/* Scoped to `.site-header` (not plain `.header__cta`) so this beats the `.btn` reset that
   every "detail page family" stylesheet (product_detail.css, event_detail.css,
   blog_detail.css, blog_author.css, product_category.css) declares for
   its own in-page buttons — those load after pages/shared.css, so a same-specificity `.btn`
   there would otherwise win the cascade and reskin the shared header's CTA (no more uppercase,
   a stray 2px border, wrong padding/font) on every page that has its own button styling.
   Deliberately omits `display`: each page's own `@media (max-width: 1024px) { .header__cta {
   display: none; } }` rule (which toggles it in favor of the hamburger menu) has the same
   specificity as a plain `.btn`/`.header__cta` selector — if this rule set `display` too, its
   higher specificity would beat that media query at every width and the CTA would never hide
   on mobile, overflowing the header instead. */
.site-header .header__cta {
  gap: 12px;
  padding: 11px 22px; font-size: 0.82rem; font-family: 'Montserrat', sans-serif;
  font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
  border-radius: 50px; border: none; cursor: pointer; transition: all .35s var(--t);
}
.menu-toggle { display: none; background: none; border: none; width: 40px; height: 40px; flex-shrink: 0; flex-direction: column; gap: 5px; justify-content: center; align-items: center; cursor: pointer; }
.menu-toggle span { width: 24px; height: 2px; background: var(--text); transition: all .3s var(--t); }
.menu-toggle.open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.menu-toggle.open span:nth-child(2) { opacity: 0; }
.menu-toggle.open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* Mobile nav collapse — the canonical copy.
   Every one of the 12 page stylesheets already declares this same block, and the header
   only ever collapsed because of them. That made the behaviour depend on a file that is
   NOT the one drawing the header: pages/<page>.css is emitted separately via
   `content_for :head`, so on any page where it is missing, fails, or has simply not
   applied yet when a screenshot is taken, the phone gets the full desktop nav running off
   the right edge and no hamburger — verified by disabling just that file at a 390px device
   (nav: block, menu-toggle: none, 43 nav links visible, document 551px wide in a 390px
   viewport). That intermittency is what made an audit's readings inconsistent.
   Declaring it here, in the stylesheet that owns .site-header / .nav / .menu-toggle, makes
   the collapse a property of the header itself. The per-page copies are now redundant
   duplicates of this and can be deleted; they are harmless meanwhile, since they set the
   identical values and simply re-apply them later in the cascade.
   Placement matters: this sits below the base .nav/.menu-toggle rules above, because a
   media query adds no specificity and these selectors tie with their unscoped
   counterparts — see the brand block further up for the same constraint. */
@media (max-width: 1024px) {
  .nav, .header__cta { display: none; }
  .menu-toggle { display: flex; }
  .nav.open {
    display: block; position: absolute; top: 100%; left: 0; right: 0;
    background: #ffffff; border-top: 1px solid var(--line);
    padding: 20px; box-shadow: 0 10px 30px rgba(11, 30, 63, 0.08);
  }
  .nav.open ul { flex-direction: column; gap: 0; }
  .nav.open a { display: flex; width: 100%; justify-content: space-between; padding: 14px; border-bottom: 1px solid var(--line); border-radius: 0; }
  /* The desktop hover panel stays hidden on touch; shared.css's
     `.nav.open .has-mega.open > .mega` (0-5-0) is what reveals it as an accordion. */
  .mega { display: none; }
}

.breadcrumb a:hover { color: var(--accent-2); }

/* Stat / stats-strip block (about.css, events_index.css, home.css, products_index.css) */
/* Content is centre-justified within each column; the accent stays the original
   full-height vertical rule at the stat's leading edge. `padding-left` is deliberately
   0 so the text centres across the whole track rather than in the space left over
   beside the rule. */
.stat { text-align: center; border-left: 3px solid var(--accent); padding-left: 0; }
/* Number and suffix sit on a shared baseline: the number is animated by each page's count-up
   script (which rewrites its textContent), so the suffix has to live outside it. */
.stat__value { display: flex; align-items: baseline; justify-content: center; gap: 2px; }
.stat__num { font-family: 'Bebas Neue', sans-serif; font-size: 4.5rem; line-height: 1; margin-bottom: 8px; color: #ffffff; }
/* The CMS stat's own suffix ("%", "+", "k"), in the accent treatment that used to be a
   hardcoded `content: "+"` on .stat__num::after — that appended a "+" to every stat sitewide
   whether or not one was authored. See shared/_stats_strip.html.erb. */
.stat__suffix { font-family: 'Bebas Neue', sans-serif; font-size: 3rem; line-height: 1; color: var(--accent-2); }
.stat__label { font-family: 'Archivo', sans-serif; font-weight: 700; font-size: 0.95rem; line-height: 1.4; color: rgba(255,255,255,0.85); }
.stats-strip { background: var(--dark); color: #ffffff; padding: 70px 0; position: relative; overflow: hidden; }
.stats-strip::before { content: ""; position: absolute; inset: 0; background: radial-gradient(circle at 80% 50%, rgba(13, 92, 245, 0.25), transparent 60%), repeating-linear-gradient(45deg, transparent, transparent 30px, rgba(255,255,255,0.02) 30px, rgba(255,255,255,0.02) 60px); pointer-events: none; }
.stats-strip__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 40px; position: relative; }

/* Hero meta block (blogs_index.css, events_index.css, products_index.css) */
.hero__meta { display: flex; align-items: center; gap: 0; }
.hero__meta-item { padding: 0 40px 0 0; }
.hero__meta-item:first-child { padding-left: 0; }
.hero__meta-num { font-family: 'Bebas Neue', sans-serif; font-size: 3rem; color: #ffffff; line-height: 1; display: block; }
.hero__meta-label { font-family: 'Archivo', sans-serif; font-size: 0.78rem; font-weight: 600; color: rgba(255,255,255,0.5); letter-spacing: 0.08em; text-transform: uppercase; }
.hero__meta-div { width: 1px; height: 50px; background: rgba(255,255,255,0.15); margin-right: 40px; }
.page-hero__content .section-eyebrow { color: var(--accent-2); }
.page-hero__content .section-eyebrow .line { background: var(--accent-2); }

/* Filter bar (blogs_index.css, events_index.css, products_index.css) */
.filter-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 52px; padding: 6px; background: var(--bg); border: 1px solid var(--line); border-radius: 50px; width: fit-content; max-width: 100%; }
.filter-pill:hover { color: var(--accent); background: var(--accent-soft); }

.cta__actions { display: flex; gap: 16px; justify-content: center; flex-wrap: wrap; }
.cta__title em { color: var(--accent-2); }

.footer__col ul li { margin-bottom: 10px; }
.reveal.visible { opacity: 1; transform: translateY(0); }

/* Footer variant 1 (about.css, contact.css, home.css) */
.footer__bottom a { color: rgba(255,255,255,0.5); }
.footer__bottom a:hover { color: var(--accent-2); }
.footer__bottom span { margin: 0 6px; }
.footer__col-title::after { content: ""; position: absolute; left: 0; bottom: 0; width: 28px; height: 2px; background: var(--accent); }
.footer__col ul li a:hover { color: var(--accent-2); padding-left: 5px; }

/* Footer variant 2 (blogs_index.css, events_index.css, products_index.css) */
.footer__col ul a { font-size: 0.88rem; color: rgba(255,255,255,0.5); transition: color .25s var(--t); }
.footer__col ul a:hover { color: var(--accent-2); }

/* ---------------------------------------------------------------------
   FAMILY B — "Detail" pages with sticky sidebar layout
   blog_author.css, blog_detail.css, event_detail.css, product_detail.css
   --------------------------------------------------------------------- */

/* reset, img, a, ul, .header__inner, .brand*, .nav > ul, .nav li,
   .nav .has-mega:hover > .mega, .menu-toggle*, .site-header.scrolled,
   .topbar__inner, .topbar__left/.topbar__right, .topbar__sep,
   .topbar__social*: identical to the Family A copies above — not
   repeated here to avoid two same-specificity rules for the same
   selector fighting over source order. */
.header__cta:hover { background: #0942b8; transform: translateY(-2px); box-shadow: 0 14px 30px rgba(13,92,245,.35); }

/* .breadcrumb a:hover and .footer__col ul li a:hover genuinely differ
   from Family A's colors — kept page-local (see the "shared.css
   collision" override block appended to each of blog_author.css,
   blog_detail.css, event_detail.css, product_detail.css) instead of
   living here, where they'd silently win over Family A's values on
   every page since they'd share one selector name at equal specificity. */
.breadcrumb span { color: rgba(255,255,255,.85); font-weight: 500; }

/* Buttons: .btn--primary/:hover also genuinely differ from Family A's
   accent-blue — same reasoning, kept page-local in the 4 files above. */
.btn--outline { background: transparent; color: var(--blue); border-color: var(--blue); }
.btn--outline:hover { background: var(--blue-light); }
.btn--white { background: var(--white); color: var(--blue-dark); border-color: var(--white); }
.btn--ghost-white { background: transparent; color: var(--white); border-color: rgba(255,255,255,.4); }
.btn--ghost-white:hover { background: rgba(255,255,255,.12); border-color: var(--white); }
.btn--full { width: 100%; justify-content: center; }

/* Sticky sidebar detail-page layout */
.page-body { background: var(--gray-50); padding: 48px 0 64px; }
.page-body__inner { display: grid; grid-template-columns: var(--sidebar-w) 1fr; gap: 32px; align-items: start; }
/* Capped to the viewport (minus the sticky header offset), with its own scroll, so a sidebar
   taller than the screen never runs off past the bottom edge while pinned in place. */
.sidebar {
  position: sticky;
  top: var(--sticky-top);
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-height: calc(100vh - var(--sticky-top) - 24px);
  overflow-y: auto;
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* legacy Edge/IE */
}
.sidebar::-webkit-scrollbar { display: none; } /* Chrome/Safari */
.sidebar-card { background: var(--white); border: 1px solid var(--gray-200); border-radius: var(--radius-lg); padding: 20px; box-shadow: var(--shadow-sm); }
.sidebar-card--cta { background: linear-gradient(160deg, #0d1e40 0%, #1a3a6e 100%); border-color: rgba(13,92,245,.3); display: flex; flex-direction: column; gap: 12px; text-align: center; }
.sidebar-cta__icon { width: 40px; height: 40px; border-radius: 50%; background: rgba(13,92,245,.3); display: flex; align-items: center; justify-content: center; font-size: 1rem; color: var(--accent); margin: 0 auto; border: 1px solid rgba(30,167,255,.3); }
.sidebar-cta__heading { font-family: var(--font-heading); font-size: .95rem; font-weight: 700; color: var(--white); }
.sidebar-cta__text { font-size: .78rem; color: rgba(255,255,255,.6); line-height: 1.5; }

/* Sidebar in-page nav (blog_detail.css, event_detail.css, product_detail.css) */
.sidebar-nav { display: flex; flex-direction: column; gap: 2px; }
.sidebar-nav__item i { font-size: 0.4rem; color: var(--gray-300); transition: all var(--transition); flex-shrink: 0; }
.sidebar-nav__item:hover { color: var(--blue); background: var(--blue-light); border-left-color: var(--blue); }
.sidebar-nav__item:hover i { color: var(--blue); }
.sidebar-nav__item.active { color: var(--blue); background: var(--blue-light); border-left-color: var(--blue); font-weight: 600; }
.sidebar-nav__item.active i { color: var(--blue); font-size: 0.5rem; }

/* Section header / spec table / mini-stat (blog_detail.css, event_detail.css, product_detail.css) */
.section-header { display: flex; align-items: center; gap: 14px; margin-bottom: 8px; }
/* --blue-light (#e8f0fe) is a background tint, not a text colour: against .content-section's
   white ground it renders at 1.15:1, so the section numeral was effectively invisible. --blue
   (#0d5cf5) keeps the intended blue numeral at 5.43:1 -- clearing the 3:1 large-text threshold
   this 2rem display type is held to, and the stricter 4.5:1 body threshold as well.
   Not --accent: it resolves to #1ea7ff on all four detail pages, which is only 2.62:1.
   The literal fallback is here because --blue is defined per page (product_detail, blog_detail,
   event_detail, blog_author), not in this file's own :root. */
.section-num { font-family: var(--font-display); font-size: 2rem; line-height: 1; color: var(--blue, #0d5cf5); letter-spacing: 1px; min-width: 40px; }
/* .section-title also genuinely differs from Family A's clamp()-sized
   version — kept page-local (see the 4 detail files' override block). */
.section-body:last-child { margin-bottom: 0; }

.mini-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-top: 28px; }
.mini-stat { background: var(--gray-50); border: 1px solid var(--gray-200); border-radius: var(--radius-lg); padding: 20px; text-align: center; transition: all var(--transition); }
.mini-stat:hover { border-color: var(--blue); background: var(--blue-light); transform: translateY(-2px); box-shadow: var(--shadow); }
.mini-stat__icon { width: 40px; height: 40px; border-radius: 50%; background: linear-gradient(135deg, var(--blue) 0%, var(--accent) 100%); display: flex; align-items: center; justify-content: center; font-size: 0.95rem; color: var(--white); margin: 0 auto 10px; }
.mini-stat__value { font-family: var(--font-display); font-size: 1.8rem; letter-spacing: 1px; color: var(--blue-dark); line-height: 1; margin-bottom: 4px; }

.spec-table-wrapper { border-radius: var(--radius-lg); border: 1px solid var(--gray-200); overflow-x: auto; -webkit-overflow-scrolling: touch; }
.spec-table { width: 100%; min-width: 480px; border-collapse: collapse; font-size: 0.875rem; }
.spec-table thead th { background: var(--blue-dark); color: var(--white); padding: 12px 20px; text-align: left; font-family: var(--font-heading); font-weight: 700; font-size: 0.78rem; letter-spacing: 1px; text-transform: uppercase; }
.spec-table thead th:last-child { border-radius: 0 var(--radius-lg) 0 0; }
.spec-table tbody tr { border-bottom: 1px solid var(--gray-100); transition: background var(--transition); }
.spec-table tbody tr:last-child { border-bottom: none; }
.spec-table tbody tr:not(.spec-group-header):hover { background: var(--blue-light); }
.spec-table tbody td { padding: 11px 20px; color: var(--gray-700); vertical-align: middle; }
.spec-group-header td i { margin-right: 8px; color: var(--accent); }

.tag { display: inline-flex; align-items: center; padding: 4px 12px; background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.18); border-radius: 20px; font-size: 0.75rem; font-family: var(--font-heading); font-weight: 600; letter-spacing: 0.5px; color: rgba(255,255,255,.85); transition: all var(--transition); }
.tag:hover { background: rgba(13,92,245,.4); border-color: var(--accent); color: var(--white); }

/* CTA section (blog_author.css, blog_detail.css, event_detail.css, product_detail.css) */
.cta-section { position: relative; padding: 80px 0; overflow: hidden; }
.cta-section__bg { position: absolute; inset: 0; background: linear-gradient(135deg, var(--blue-dark) 0%, var(--blue-mid) 50%, #0d2a55 100%); }
.cta-section__inner { position: relative; z-index: 2; text-align: center; }
.cta-section__badge { display: inline-flex; align-items: center; gap: 6px; padding: 5px 14px; background: rgba(30,167,255,.15); border: 1px solid rgba(30,167,255,.3); border-radius: 20px; font-size: .72rem; font-family: var(--font-heading); font-weight: 700; letter-spacing: 2px; text-transform: uppercase; color: var(--accent); margin-bottom: 20px; }
.cta-section__title { font-family: var(--font-display); font-size: clamp(2rem,4vw,3rem); letter-spacing: 1px; color: var(--white); margin-bottom: 16px; line-height: 1.1; }
.cta-section__desc { font-size: .95rem; color: rgba(255,255,255,.85); max-width: 560px; margin: 0 auto 32px; line-height: 1.7; }
.cta-section__actions { display: flex; justify-content: center; gap: 16px; flex-wrap: wrap; margin-bottom: 48px; }
.cta-section__stats { display: flex; justify-content: center; gap: 40px; flex-wrap: wrap; }
.cta-stat { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.cta-stat__value { font-family: var(--font-display); font-size: 1.8rem; letter-spacing: 2px; color: var(--white); line-height: 1; }
.cta-stat__label { font-size: .72rem; font-family: var(--font-heading); font-weight: 600; letter-spacing: 1px; text-transform: uppercase; color: rgba(255,255,255,.5); }

/* Footer */
.footer__grid { display: flex; flex-wrap: nowrap; align-items: flex-start; gap: 40px; padding-bottom: 48px; }
.footer__brand { flex: 0 1 240px; min-width: 200px; }
.footer__col { flex: 1 1 160px; min-width: 140px; }
@media (max-width: 1024px) {
  .footer__grid { flex-wrap: wrap; gap: 40px; }
  .footer__brand { flex: 1 1 100%; }
  .footer__col { flex: 1 1 calc(50% - 20px); min-width: 200px; }
}
@media (max-width: 640px) {
  .footer__grid { flex-direction: column; }
  .footer__brand, .footer__col { flex: 1 1 auto; width: 100%; min-width: 0; }
}
.footer__brand p { font-size: .85rem; color: rgba(255,255,255,.5); line-height: 1.7; margin: 20px 0; }
.footer__social { display: flex; gap: 8px; }
.footer__social a { width: 34px; height: 34px; border-radius: 50%; background: rgba(255,255,255,.08); display: flex; align-items: center; justify-content: center; font-size: .75rem; color: rgba(255,255,255,.5); transition: all var(--transition); }
.footer__social a:hover { background: var(--blue); color: var(--white); transform: translateY(-2px); }
/* color matches the listing pages' (Family A) .footer__col-title for cross-page consistency —
   the detail-family static references specify a dim rgba(255,255,255,.4) here, but that reads
   as washed-out next to the listing pages' bold white, so this intentionally diverges from
   the per-page reference to keep the header/nav/footer chrome visually consistent site-wide. */
.footer__col-title { font-family: var(--font-heading); font-size: .72rem; font-weight: 700; letter-spacing: 2.5px; text-transform: uppercase; color: #ffffff; margin-bottom: 16px; }
.footer__col ul { display: flex; flex-direction: column; gap: 8px; }
.footer__col ul li a { font-size: .85rem; color: rgba(255,255,255,.55); transition: color var(--transition); }
/* .footer__col ul li a:hover also genuinely differs from Family A's
   accent-2 + padding-left hover effect — kept page-local. */
.footer__contact p { font-size: .83rem; color: rgba(255,255,255,.55); line-height: 1.6; margin-bottom: 12px; }
.footer__contact a { display: flex; align-items: center; gap: 10px; font-size: .83rem; color: rgba(255,255,255,.55); margin-bottom: 10px; transition: color var(--transition); }
.footer__contact a i { color: var(--accent); flex-shrink: 0; width: 14px; }
.footer__contact a:hover { color: var(--white); }
.footer__bottom { padding: 20px 0; border-top: 1px solid rgba(255,255,255,.08); }
.footer__bottom-inner { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; }
.footer__bottom-inner div { display: flex; gap: 20px; }
/* WCAG 2.1 AA (1.4.3) needs 4.5:1 for this size. Over the footer's --dark (#0b1e3f) the old
   .35 alpha composited to #606d82 -- 3.15:1, a fail axe-core flagged on every template, since
   this rule is the site-wide one (the per-page .footer__bottom-inner rules set only the
   inherited container colour and lose to this more specific selector).
   .55 composites to #919aa9 = 5.82:1. The minimum that clears 4.5 is .47 (4.61:1); the extra
   headroom is deliberate, so antialiasing and any future darkening of --dark don't quietly
   put this back under the line. */
.footer__bottom-inner p { font-size: .8rem; color: rgba(255,255,255,.55); }
.footer__bottom-inner a { font-size: .8rem; color: rgba(255,255,255,.55); transition: color var(--transition); }
.footer__bottom-inner a:hover { color: var(--white); }

/* font-size/border/cursor/visibility left page-local: Family A's
   back-to-top is an <a> hidden with opacity/transform only (no
   visibility toggle, so a leaked `visibility: hidden` here would never
   get reset back to visible on those pages); Family B's is a <button>
   that does use a visibility toggle (`.back-to-top.visible`) and needs
   the border/cursor reset — see the override block appended to each of
   the 4 detail-page files. */
.back-to-top { position: fixed; bottom: 28px; right: 28px; width: 44px; height: 44px; border-radius: 50%; background: var(--blue); color: var(--white); display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 16px rgba(13,92,245,.4); opacity: 0; transform: translateY(12px); transition: all var(--transition); z-index: 500; }
.back-to-top.visible { opacity: 1; visibility: visible; transform: translateY(0); }

/* =================== HERO IMAGE CAROUSEL ===================
   Shared by every `.product-hero__carousel` consumer (products/show_sections/_hero.html.erb,
   events/show.html.erb, ...) so the carousel looks and sizes identically wherever it's embedded,
   instead of each page keeping its own near-duplicate copy. Loaded on every page via
   layouts/application.html.erb's "pages/shared" stylesheet_link_tag — page-specific CSS files
   should not redeclare these selectors. */
.product-hero__carousel { flex-shrink: 0; width: 380px; align-self: center; }
.hero-carousel { position: relative; border-radius: 20px; overflow: hidden; box-shadow: 0 24px 64px rgba(0,0,0,.45); background: #ffffff; }
.hero-carousel__track { display: flex; transition: transform .5s cubic-bezier(.65,.05,.36,1); }
.hero-carousel__slide { min-width: 100%; }
.hero-carousel__slide .slide-visual__image { width: 100%; height: 100%; aspect-ratio: 4/3; object-fit: contain; object-position: center center; display: block; }
.product-hero__carousel--featured .hero-carousel__slide .slide-visual__image { box-sizing: border-box; padding: 16px; }
/* .featured__image: the products hero's single always-shown image (see
   products/show_sections/_hero.html.erb) — zoomed to fill the card edge-to-edge
   (object-fit: cover) instead of the letterboxed/padded .slide-visual__image look
   used by the multi-slide carousel and events/show.html.erb's featured fallback. */
.hero-carousel__slide .featured__image { width: 100%; height: 100%; aspect-ratio: 4/3; object-fit: contain; object-position: center center; display: block; box-sizing: border-box; padding: 16px; }
.slide-visual { aspect-ratio: 4/3; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 18px; position: relative; overflow: hidden; }
.slide-visual--1 { background: linear-gradient(135deg, #071441 0%, #0050CC 60%, #0088EE 100%); }
.slide-visual--2 { background: linear-gradient(135deg, #061233 0%, #003399 55%, #0066CC 100%); }
.slide-visual--3 { background: linear-gradient(135deg, #082055 0%, #0044BB 50%, #00CCEF 100%); }
.slide-visual--4 { background: linear-gradient(135deg, #051025 0%, #00389A 55%, #2EE090 100%); }
.slide-visual__icon { width: 80px; height: 80px; background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.2); border-radius: 18px; display: flex; align-items: center; justify-content: center; font-size: 2.2rem; color: #ffffff; backdrop-filter: blur(6px); }
.slide-visual__label { font-family: 'Archivo', sans-serif; font-weight: 700; font-size: 0.78rem; letter-spacing: 0.14em; text-transform: uppercase; color: rgba(255,255,255,.75); text-align: center; padding: 0 24px; }
.carousel-btn { position: absolute; top: 50%; transform: translateY(-50%); width: 36px; height: 36px; border: 1px solid rgba(0,0,0,.08); border-radius: 50%; background: #ffffff; box-shadow: 0 2px 10px rgba(0,0,0,.25); color: #0B1220; font-size: 0.75rem; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background .25s; z-index: 2; }
.carousel-btn:hover { background: #f1f3f6; }
.carousel-btn--prev { left: 12px; }
.carousel-btn--next { right: 12px; }
.hero-carousel__dots { position: absolute; bottom: 14px; left: 50%; transform: translateX(-50%); display: flex; gap: 7px; z-index: 2; }
.carousel-dot { width: 8px; height: 8px; border-radius: 50%; border: none; background: rgba(255,255,255,.35); cursor: pointer; padding: 0; transition: all .25s; }
.carousel-dot.active { background: #ffffff; width: 22px; border-radius: 4px; }

/* Responsive behavior for .product-hero__carousel stays page-local (product_detail.css /
   event_detail.css) — the two pages hide/resize it at different breakpoints on purpose. */
/* .reveal.visible: identical to the Family A copy above. */

/* Blog thumbnails showing the post's CMS "Body Image" (set as an inline background-image).
   `cover` so every image fills the full card width regardless of its uploaded aspect ratio
   (`contain` letterboxed narrower uploads); anchored to the top because these designed graphics
   carry their headline there, so any crop comes off the bottom. Two-class selectors so this
   outranks the per-page `--metal`/`--xray`/… modifiers, whose `background:` shorthand resets
   size/position. The grid-pattern overlay is hidden so it doesn't draw over the image. */
.post-card__thumb.post-card__thumb--image {
  background-color: var(--bg-2);
  background-size: cover;
  background-position: center top;
  background-repeat: no-repeat;
}
/* The featured post's thumb takes its height from the text column beside it, so its box ratio
   varies with the copy and `cover` would crop the graphic's edges (logo, side panels). `contain`
   keeps the whole image visible; --bg-2 fills any band. The badge moves to the bottom-left so it
   doesn't sit on the logo these graphics carry in their top-left corner. */
.featured-post__thumb.featured-post__thumb--image {
  background-color: var(--bg-2);
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
}
.featured-post__thumb.featured-post__thumb--image .featured-post__badge { top: auto; bottom: 20px; }
.featured-post__thumb.featured-post__thumb--image::before,
.post-card__thumb.post-card__thumb--image::before { display: none; }
/* Post card title links to the post; inherits the title's colour (and its per-page card-hover
   colour) instead of the global link styling. */
.post-card__title-link { color: inherit; text-decoration: none; }
.post-card__title-link:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
