/* PARAGON palette (see design-tokens-paragon.md + prototype-explore-
   paragon-10/assets/styles.css, the visual reference this file was ported
   from) — white body, signal-yellow accent, near-black graphite for
   weighted sections. Variable NAMES are kept identical to the previous
   yamamotor-next palette on purpose (every selector below and every
   component's className in src/ already references these names) — only
   the values changed. --sea/--sea-2/--chart are repointed from "ocean
   teal"/"gold label" roles to a single darker amber (#c98700) used for
   accent TEXT (nav hover, selected filter state, focus rings) — NOT the
   same value as --action. This project's hard rule is that the signal-
   yellow accent (--action) must never sit behind white/light text and
   must never BE the text color on a light surface (bright yellow on white
   is functionally illegible); --action is only ever used as a background
   (paired with dark --ink text) or as text on a dark (--navy) surface.
   --sea's darker amber has enough contrast to work as text against both
   white and --navy, which is why hover states/selected filter rows/focus
   rings were repointed to it instead of --action directly. */
:root {
  --page: #ffffff;
  --surface: #f7f7f4;
  --surface-2: #f3f1ea;
  --ink: #0d0d0c;
  --muted: #6b6a63;
  --line: #ddd9cd;
  --sea: #c98700;
  --sea-2: #b37a00;
  --navy: #121210;
  --chart: #f6b30b;
  --action: #f6b30b;
  --success: #16a34a;
  --shadow: 0 18px 50px rgba(0, 0, 0, 0.12);
  --radius: 8px;
}

* {
  box-sizing: border-box;
}

/* scroll-behavior: smooth was here, removed — it doesn't only affect
   deliberate in-page anchor jumps (this site has none: the only href="#"
   usages are pagination controls that always preventDefault(), never a
   real #section jump), it hijacks EVERY programmatic scroll on the
   page, including Next.js's own router doing "scroll to top" after a
   client-side navigation. With smooth-scroll on, that reset animates
   instead of jumping instantly, and if the destination page is still
   hydrating/reflowing while the animation is mid-flight, the animation
   gets interrupted and settles wherever it happened to be instead of at
   0 — confirmed live: Home -> Company via the nav link left scrollY at
   27-35px instead of 0, and a mobile-menu link left it at 144px,
   inconsistently, depending on scroll distance and how fast the target
   page became interactive. The back-to-top button already passes its
   own behavior: "smooth" directly to scrollTo() in JS, so it's
   unaffected by removing this. */
html {
  /* Reserves the vertical scrollbar's width on every page, whether or not
     that page's content is actually tall enough to need one. Without this,
     a short page (e.g. company.html) renders with no scrollbar, but
     products.html's long card grid needs one — navigating between them
     makes the scrollbar appear/disappear, shrinking or growing the visible
     body width by ~15-17px and shifting all centered content sideways for a
     moment. This keeps that width constant across every page instead. */
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  color: var(--ink);
  background:
    linear-gradient(rgba(246, 179, 11, 0.035) 1px, transparent 1px),
    linear-gradient(90deg, rgba(246, 179, 11, 0.035) 1px, transparent 1px),
    var(--page);
  background-size: 42px 42px;
  font-family: Archivo, "Noto Sans TC", Arial, sans-serif;
  letter-spacing: 0;
}

a {
  color: inherit;
  text-decoration: none;
}

img {
  display: block;
  max-width: 100%;
}

/* Thin contact bar above the main nav — new for the PARAGON port
   (prototype-explore-paragon-10's .top-strip), since the current site's
   every legacy page carries this same info in a full-width search bar
   (design-paragon.md §5); this is the toned-down 2036 version of it.
   Hidden below 640px alongside .nav's own height drop (see the mobile
   media query near the bottom of this file) — there's no room for it once
   the header has to fit a 375px viewport, and phone/email as plain text
   aren't tap targets worth the vertical cost there. */
.top-strip {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 24px;
  height: 34px;
  border-bottom: 1px solid var(--line);
  background: #fff;
  font-size: 12px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
}

.top-strip-inner {
  width: min(1180px, calc(100% - 32px));
  margin: 0 auto;
  display: flex;
  justify-content: flex-end;
  gap: 24px;
}

.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  background: rgba(255, 255, 255, 0.96);
}

/* Grid, not flex + space-between: with 3 flex children, space-between
   only guarantees equal gaps around .nav-links, not a true center — its
   position shifts whenever .header-actions's width changes (e.g. the
   "Parts Search" link being conditionally omitted on /products), which
   visibly shifted the nav right on that page. A 1fr/auto/1fr grid keeps
   .nav-links centered on nav-shell itself regardless of what either side
   contains. */
.nav-shell {
  width: min(1180px, calc(100% - 32px));
  margin: 0 auto;
  min-height: 78px;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 20px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
  grid-column: 1;
}

/* Real client-supplied logo (replacing the earlier CSS-drawn .brand-mark
   glyph used only while "PARAGON has no independent logo file" was true
   — see prototype-explore-paragon-10/EXPLORE-NOTES.md). Sized by height
   with auto width so the artwork's own aspect ratio is never distorted;
   header and footer share this base rule, footer's own inversion is
   layered on separately below since the artwork is black line art that
   would otherwise disappear against the dark footer.
   Replaced with a higher-quality 1200x700 source (was 504x231) — a wider
   aspect ratio (~1.71:1 vs ~2.18:1). The first recalculation (71px,
   width-matched to the old artwork) read too large next to the nav
   links on the live desktop site — the 51px mobile/footer value already
   confirmed as well-proportioned there, so desktop now matches it
   instead of trying to hold the old width. */
.brand img {
  height: 51px;
  width: auto;
}

.footer .brand img {
  height: 51px;
  filter: brightness(0) invert(1);
  opacity: 0.85;
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 26px;
  font-size: 14px;
  font-weight: 800;
  grid-column: 2;
}

/* Explicit grid-column on all three .nav-shell children (rather than
   relying on source-order auto-placement) because .nav-links gets
   display:none on mobile — grid auto-placement drops display:none items
   from the flow entirely, which was pulling .header-actions leftward
   into the vacated middle column (the mobile hamburger/cart buttons
   visibly landed near center instead of the right edge). */
.header-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  justify-self: end;
  gap: 10px;
  grid-column: 3;
}

/* Replaces the old plain "Parts Search" link at >720px — .sku-search-row
   itself sets no width (it's flex: 1 inside .results-controls on
   Products, full-width inside .mobile-search-bar), so it needs one here
   or it'd shrink to its own content's minimum inside .header-actions'
   flex row. Hidden below 720px (see that breakpoint) in favor of the
   icon toggle + .mobile-search-bar, which do the same job without the
   width this needs. */
.header-search-row {
  width: 240px;
}

/* Plain text nav — matches prototype exactly: hover/active only changes
   color to the amber accent, no pill background (an earlier yamamotor-
   next holdover this port carried by mistake). */
.nav-links a.active,
.nav-links a:hover {
  color: var(--sea);
}

.icon-btn {
  position: relative;
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--ink);
  border-radius: var(--radius);
  background: #fff;
  color: var(--ink);
  font-weight: 900;
  cursor: pointer;
}

.icon-btn svg {
  width: 20px;
  height: 20px;
}

.cart-count {
  position: absolute;
  top: -5px;
  right: -4px;
  min-width: 21px;
  height: 21px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--action);
  /* var(--action) is signal yellow — this project's hard rule is dark
     text on any yellow background, never white (see :root's comment). */
  color: var(--ink);
  font-size: 12px;
  font-weight: 800;
  line-height: 21px;
  text-align: center;
}

.mobile-menu-btn {
  display: none;
}

/* Expand/collapse animated the same way .filter-drawer slides in (same
   0.24s cubic-bezier(0.22, 1, 0.36, 1) easing), adapted from a
   horizontal slide to a vertical unfold since this is an inline
   dropdown that pushes page content down, not a fixed-position panel
   sliding over it. max-height (not height) animates because the actual
   content height isn't known up front — 500px is a deliberately
   generous cap so the transition always reaches its target before the
   real content (well under that) stops growing; overflow: hidden clips
   the gap. visibility flips to hidden only at the end of the closing
   transition (transition-delay, not present on the .open rule's own
   transition) so the collapsed panel's links can't still receive
   keyboard focus while invisible, without adding a visible delay to
   opening. */
/* border-top-width: 0 here, not the full border — same reasoning as
   .mobile-search-bar's padding below: a border is part of this box's
   own edge, not overflow content max-height: 0 + overflow: hidden can
   clip, so a standing 1px border-top here would (and did, confirmed
   live) leave the "closed" panel 1px tall instead of ~0 — combined with
   .mobile-search-bar's own 1px, that inflated .site-header's real
   height 2px past what .mobile-filter-bar's sticky top: 78px assumes
   on /products, covering a couple pixels of the Filters button after
   scrolling. Border only appears once .open sets its width back to
   1px. */
.mobile-menu {
  border-top: 0 solid var(--line);
  background: var(--surface);
  overflow: hidden;
  max-height: 0;
  opacity: 0;
  visibility: hidden;
  transition:
    max-height 0.24s cubic-bezier(0.22, 1, 0.36, 1),
    border-top-width 0.24s cubic-bezier(0.22, 1, 0.36, 1),
    opacity 0.24s cubic-bezier(0.22, 1, 0.36, 1),
    visibility 0s linear 0.24s;
}

.mobile-menu a {
  display: block;
  padding: 14px 24px;
  border-bottom: 1px solid var(--line);
  font-weight: 800;
}

/* Mobile Part No. search toggle — same "hidden icon-button until the
   ≤720px tier turns it on" pattern as .mobile-menu-btn right above,
   and .mobile-search-bar below mirrors .mobile-menu's own dropdown
   treatment (border-top + surface fill, pushes page content down
   rather than overlaying it) including its expand/collapse animation.
   Reuses .sku-search-row/.filter-search/.sku-search-btn as-is for the
   actual input+button row -- same control already built and fixed
   (height, border-radius, shadow) for Products/News, not a second copy
   of that styling. */
.mobile-search-btn {
  display: none;
}

/* padding: 0 here (not 14px 24px) is deliberate, not an oversight: a
   box's own padding is part of ITS box, not "overflow content" relative
   to itself, so overflow: hidden + max-height: 0 below never clips it
   the way .mobile-menu's per-link padding gets clipped (that padding
   belongs to the <a> children, which genuinely are this container's
   overflow content). Left at 14px 24px here, the closed panel would
   still render at ~29px (28px padding + 1px border) instead of
   collapsing to ~0 -- confirmed live: that leftover height was
   inflating .site-header's real rendered height past what
   .mobile-filter-bar's sticky top: 78px assumed, pushing the two out of
   alignment and letting page content creep up over the Filters button
   after scrolling. The real padding only applies in .open, below. */
.mobile-search-bar {
  border-top: 0 solid var(--line);
  background: var(--surface);
  padding: 0 24px;
  overflow: hidden;
  max-height: 0;
  opacity: 0;
  visibility: hidden;
  transition:
    max-height 0.24s cubic-bezier(0.22, 1, 0.36, 1),
    padding 0.24s cubic-bezier(0.22, 1, 0.36, 1),
    border-top-width 0.24s cubic-bezier(0.22, 1, 0.36, 1),
    opacity 0.24s cubic-bezier(0.22, 1, 0.36, 1),
    visibility 0s linear 0.24s;
}

.section {
  width: min(1180px, calc(100% - 32px));
  margin: 0 auto;
  padding: 72px 0;
}

.section.tight {
  padding: 40px 0;
}

/* Full-bleed soft backdrop band — matches prototype's .section.soft
   (used for its product-detail "Related parts" band). Wraps a .section
   the same way .page-hero wraps its own inner .section: this element
   supplies the full-width background, .section supplies the centered
   max-width content. */
.soft-band {
  background: var(--surface-2);
}

/* Narrower content width — matches prototype's own .tight (used for
   plain text blocks like News detail's article body, and now Company's
   standalone Operating beliefs list after Follow-us/Partner-brands were
   removed from beside it). Distinct from .section.tight above (a padding
   modifier for a different, currently-unused combination). */
.tight {
  width: min(980px, calc(100% - 40px));
  margin: 0 auto;
}

.eyebrow {
  color: var(--muted);
  font-size: 12px;
  font-weight: 900;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* Matches prototype's .mono — product detail's SKU line
   ("PG-15100-96J01 (15100-96J01)") renders in a monospace face to read
   as a part number, not prose. */
.eyebrow.mono {
  font-family: "IBM Plex Mono", Consolas, monospace;
}

/* Short accent underline + centered section intro — matches prototype's
   .rule/.section-title, used wherever a section opens with an eyebrow +
   heading pair (Partner brands, Related parts/news, etc.). */
.rule {
  width: 56px;
  height: 5px;
  background: var(--action);
  margin: 14px auto 0;
}

.rule.left {
  margin-left: 0;
}

.section-title {
  text-align: center;
  margin: 0 auto 34px;
  max-width: 680px;
}

.display-title {
  margin: 10px 0 18px;
  max-width: 820px;
  font-size: clamp(34px, 6.4vw, 70px);
  line-height: 0.94;
  letter-spacing: 0;
  /* Matches .btn's 900 weight — previously inherited body's default
     weight and read noticeably thinner than the Search button directly
     beneath it (confirmed on the live site: same Archivo family,
     visibly different weight). */
  font-weight: 900;
}

.lead {
  max-width: 680px;
  color: var(--ink);
  font-size: 18px;
  line-height: 1.75;
  overflow-wrap: break-word;
}

.muted {
  color: var(--muted);
}

/* Bordered "sticker" button — thick ink border + flat drop shadow instead
   of a soft blur, matching prototype-explore-paragon-10's button language.
   Default (no modifier) is a white/ink outline button; .primary is the
   only filled-yellow variant, and per this project's yellow-contrast rule
   it keeps dark --ink text rather than inheriting white from .btn. */
.btn {
  min-height: 46px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  border: 2px solid var(--ink);
  border-radius: var(--radius);
  padding: 11px 18px;
  background: #fff;
  color: var(--ink);
  font-weight: 900;
  cursor: pointer;
  box-shadow: 0 4px 0 var(--ink);
  transition: transform 0.16s ease, background 0.16s ease, box-shadow 0.16s ease;
}

.btn:hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 0 var(--ink);
}

.btn.primary {
  background: var(--action);
  color: var(--ink);
}

/* Was a distinct dark-gold fill (var(--sea)) — a leftover from
   yamamotor-next's ocean-blue token, never fully re-themed. It sat next
   to genuinely primary-yellow buttons (Search, View results, Accept)
   using a visibly different, muddier color for what reads as the same
   "primary action" role at every one of its 7 call sites (Filters
   trigger, SKU/news search, empty-state "back to products" CTAs).
   Matches .btn.primary now instead of keeping its own color: same base
   .btn black border + hard shadow, just filled with the real accent. */
.btn.sea {
  background: var(--action);
  color: var(--ink);
}

.btn.ghost {
  border-color: var(--line);
  background: transparent;
  color: var(--ink);
  box-shadow: none;
}

/* Compact variant — matches prototype's .btn.small, used for header CTAs
   (Parts Search / Back to Products), filter clear buttons, and card-level
   actions (Details) where the full 46px .btn reads oversized next to
   dense content. */
.btn.small {
  min-height: 38px;
  padding: 8px 12px;
  font-size: 13px;
  box-shadow: none;
}

/* Text treatment matches the homepage hero's "PARAGON Parts Finder" label
   (weight, tracking, case) at the button's own font size — .btn's 900
   weight is already close to .hero-tools-label's 600, so weight isn't
   overridden here, just tracking/case. Deliberately NOT colored with
   --chart/--action here: this button sits on the plain white page body
   (product detail page), not a dark surface, and bright yellow text on
   white fails contrast — --ink stays the text color, --sea's darker amber
   is used only for the border accent, which is decorative, not text.
   Selector matches .btn.ghost's specificity (two classes, not one) — a
   bare .btn-ghost-gold rule was previously losing to .btn.ghost's `color:
   var(--ink)` despite appearing later in the file, since CSS specificity
   is decided by selector weight, not source order. */
.btn.ghost.btn-ghost-gold {
  color: var(--ink);
  border-color: var(--sea);
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

/* Light hero — matches prototype-explore-paragon-10's actual .hero
   exactly (soft white/sea-glass gradient, dark ink text, an abstract
   clipped "photo plate" shape on the right via ::after below).
   Previously this was a dark navy hero — a leftover from yamamotor-next's
   own visual language that never actually matched the prototype, which
   originally kept dark surfaces to exactly two deliberate spots on the
   whole homepage (the hero's own .stats-card and the footer) rather
   than using dark as the hero's own base — now just the footer, since
   .stats-card was removed. assets/hero-ocean.jpg (yamamotor's own
   product photo, watermark
   baked into the JPEG) is still never used — this gradient is the
   prototype's own actual background, not a placeholder standing in for a
   missing photo. */
.hero {
  position: relative;
  overflow: hidden;
  min-height: 660px;
  display: flex;
  align-items: center;
  color: var(--ink);
  background:
    linear-gradient(90deg, rgba(255, 255, 255, 0.96) 0% 45%, rgba(255, 255, 255, 0.72) 58%, rgba(0, 0, 0, 0.12) 100%),
    radial-gradient(circle at 78% 35%, rgba(246, 179, 11, 0.22), transparent 22%),
    linear-gradient(135deg, #ecf6f9, #fff 45%, #dad2c3);
}

/* Real photo (outboard motor/propeller close-up — on-topic for a marine
   parts supplier, unlike the yacht-lifestyle shots also provided), not a
   fabricated gradient — client-supplied jpg/slide04.jpg, moved to
   public/jpg/ so Next.js can serve it. Kept as a bordered, clipped
   "plate" the same size/position as before (not a full-bleed background)
   per explicit instruction. The diagonal stripe texture and dark-to-
   transparent tint layer over the photo instead of over a gradient. */
.hero::after {
  content: "";
  position: absolute;
  right: -90px;
  top: 80px;
  width: min(58vw, 720px);
  aspect-ratio: 1.2;
  background:
    linear-gradient(130deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0)),
    repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.2) 0 2px, transparent 2px 18px),
    url("/jpg/slide04.jpg") center / cover;
  clip-path: polygon(9% 13%, 100% 0, 90% 74%, 18% 100%, 0 55%);
  border: 12px solid #fff;
  box-shadow: var(--shadow);
}

.hero-inner {
  position: relative;
  z-index: 1;
  width: min(1180px, calc(100% - 32px));
  margin: 0 auto;
  padding: clamp(48px, 8vh, 72px) 0 clamp(56px, 9vh, 96px);
}

/* Single column — was a two-column grid (headline + search on the
   left, a standalone .stats-card stats panel on the right, matching
   prototype-explore-paragon-10's .hero-grid) until .stats-card was
   removed. minmax(0, 1fr) minmax(300px, 430px) collapsed to just
   minmax(0, 1fr). Left as a grid (not switched to a plain block) so
   the min-width: 0 fix below still applies uniformly if a second
   column ever comes back. */
.hero-grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 56px;
  align-items: center;
}

/* A grid item's default min-width is "auto", not 0 — it refuses to
   shrink below its own content's natural minimum width. Kept even
   though .hero-grid is single-column now: still prevents the text
   column from refusing to shrink past its own natural content width
   at narrow viewports (see the original two-column overrun this fixed,
   confirmed live at 390px, documented in git history for this line). */
.hero-grid > * {
  min-width: 0;
}

/* Search card (left column) and stats card (right column) — matches
   prototype-explore-paragon-10's .search-panel, the actual homepage
   hero component. (.stats-card/.stat-line, the former right-column
   stats panel matching that same prototype, were removed along with
   the section itself — see .hero-grid's own comment above.) */
.search-panel {
  background: #fff;
  border: 1px solid var(--line);
  box-shadow: var(--shadow);
  border-radius: var(--radius);
  padding: 18px;
  margin-top: 28px;
  max-width: 620px;
}

.search-panel .search-row {
  display: flex;
  gap: 10px;
}

.search-panel .search-row input {
  min-width: 0;
  flex: 1;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 13px 14px;
  color: var(--ink);
}

/* Generic bordered card — matches prototype's base .card/.card.pad
   exactly. This project already had two parallel card systems (.panel
   and .category-card/.product-card) built up piecemeal before this one
   was added; this is the prototype's own literal utility, used where a
   page's markup nests a plain card directly (Contact's outer wrapper). */
.card {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.06);
  overflow: hidden;
}

.card.pad {
  padding: 22px;
}

.category-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 16px;
  margin-top: 34px;
}

/* "Hot product routes" cards — matches prototype's actual card markup:
   an outer bordered .card containing a full-bleed .photo (image +
   .corner-tag title) and a second, visually distinct .card.pad nested
   inside it for the description text. Previously this was a single flat
   card with a small 78x78 icon-style thumbnail and no corner-tag — closer
   to a generic tile than the prototype's actual photo-plate card
   language. */
.category-card {
  display: block;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.06);
}

/* Fixed aspect-ratio, not min-height + img height:100% — that combination
   left the container's real height indeterminate inside a CSS Grid auto-
   row track (each .category-photo's height depended on its own img's
   height:100%, which itself depended on the container — a circular
   reference grid auto-sizing resolves inconsistently row to row).
   Confirmed live: card 1 rendered as a 281x281 square while cards 2-8 in
   the same grid rendered 281x210.75 (4:3) on the same page load — a real
   rendering bug, not a deliberate design choice. 3/2 (landscape) matches
   prototype's own .photo proportions at its default column width
   (roughly 281px wide over its 170px min-height ≈ 1.65:1) — an
   aspect-ratio box, unlike min-height, also stays visually consistent
   across the responsive breakpoints where the column's width changes
   under a fixed height would otherwise have (mobile single-column ended
   up far flatter than desktop's 4-column proportions under the old rule). */
.category-photo {
  position: relative;
  aspect-ratio: 3 / 2;
  overflow: hidden;
}

.category-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.category-card .card.pad {
  border: 0;
  border-radius: 0;
  box-shadow: none;
  border-top: 1px solid var(--line);
}

.category-card .card.pad p {
  margin: 0;
  line-height: 1.55;
  /* A long unbroken run of characters (no spaces to wrap at — e.g. a URL
     pasted into an excerpt, or CJK text with no natural break opportunity
     the browser recognizes) would otherwise overflow the card's fixed
     width instead of wrapping. overflow-wrap only forces a break once a
     word genuinely can't fit on its own line — normal space-separated
     text still wraps at word boundaries exactly as before. */
  overflow-wrap: break-word;
}

/* 4-up commitment/stat grid — matches prototype's .metric-strip/.metric,
   used for the homepage "Operational promise" section (the four real
   service commitments design-paragon.md requires: quality/price/delivery/
   technical service) and reused for Company's founded/markets/
   manufacturers/items stat row. */
.metric-strip {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 16px;
}

.metric {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 20px;
  min-height: 104px;
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.05);
  /* .route-band-centered (the section this lives in, see above) sets
     text-align: center on the whole block so the headline centers —
     without this, that would also center each card's own "01"/"Stable
     Quality" text, which should stay left-aligned within the card. */
  text-align: left;
}

.metric strong {
  color: var(--sea);
  font-size: 28px;
  display: block;
  line-height: 1;
}

.metric b {
  display: block;
  margin: 6px 0 4px;
}

/* Abstract "photo plate" placeholder — matches prototype's .photo/
   .photo.dark-photo exactly (a decorative gradient block, label pinned to
   the bottom-left, often paired with .corner-tag). Used wherever the
   prototype has no real photography of its own to show (Company's photo
   panel, Contact's map fallback) — never a stand-in for a real uploaded
   photo, which keeps using its own actual <img>. */
.photo {
  min-height: 170px;
  position: relative;
  background:
    radial-gradient(circle at 22% 25%, rgba(246, 179, 11, 0.36), transparent 16%),
    linear-gradient(135deg, #fff, #d8e5e7 48%, #b7b0a5);
  display: flex;
  align-items: flex-end;
  justify-content: flex-start;
  padding: 16px;
  font-weight: 900;
  color: #111;
  border-radius: var(--radius);
  overflow: hidden;
}

/* jpg/img-bkg01.jpg (client-supplied outboard-motor photo, moved to
   public/jpg/) — highest commercial relevance of the provided set per
   explicit instruction, used for Company's "Taiwan QA + Global Export"
   panel. Dark tint kept over it at the same strength as the previous
   gradient-only version, so .corner-tag and white text stay legible. */
.photo.dark-photo {
  background:
    linear-gradient(rgba(0, 0, 0, 0.45), rgba(0, 0, 0, 0.45)),
    url("/jpg/img-bkg01.jpg") center / cover;
  color: #fff;
}

/* Homepage "Operational promise" section — matches prototype's
   .section.soft: soft-surface band, left intro copy / right a 2x2 grid of
   the four real service commitments (design-paragon.md's quality/price/
   delivery/technical-service requirement — replacing an earlier numbered
   "route" list carried over from yamamotor's own homepage narrative). */
.route-band {
  position: relative;
  overflow: hidden;
  background: var(--surface);
}

.route-band .section {
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  gap: 44px;
  align-items: center;
}

/* Stacked, centered variant (headline above, cards below) — replaces
   the left-copy/right-cards two-column layout above once the eyebrow +
   intro copy were removed and the section reads as one centered block,
   matching the "Our products" section's .section-title treatment
   instead of the old side-by-side split (which otherwise left a wide
   empty gap between a left-aligned headline and a right-aligned card
   grid with nothing between them).
   Selector is .route-band .section.route-band-centered (not just
   .route-band-centered alone) to match/exceed .route-band .section's
   specificity above (0,0,2,0) — a plain .route-band-centered (0,0,1,0)
   silently lost to it despite appearing later in the file (confirmed
   live: display: grid kept winning, section stayed two-column, even
   after the class was added and the deploy confirmed up to date). CSS
   cascade order doesn't override specificity, only breaks ties within
   equal specificity. */
.route-band .section.route-band-centered {
  display: block;
  text-align: center;
}

.route-band-centered .promise-metrics {
  max-width: 640px;
  margin: 0 auto;
}

.promise-metrics {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}

.footer {
  background: var(--navy);
  color: white;
}

/* 4-column footer (brand / links / news / contact) — matches prototype-
   explore-paragon-10's .footer-grid, replacing this port's earlier
   single-row logo+copyright footer. */
.footer-grid {
  width: min(1180px, calc(100% - 32px));
  margin: 0 auto;
  padding: 46px 0 22px;
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr 1.4fr;
  gap: 32px;
}

.footer-grid h3 {
  margin: 0 0 8px;
  font-size: 15px;
}

.footer-rule {
  width: 42px;
  height: 4px;
  background: var(--action);
  margin: 0 0 14px;
}

.footer-grid a,
.footer-grid p {
  color: rgba(255, 255, 255, 0.78);
  font-size: 13px;
}

.footer-grid a:hover {
  color: #fff;
}

.footer-grid nav {
  display: grid;
  gap: 8px;
}

.footer p {
  margin: 0;
  color: rgba(255, 255, 255, 0.72);
  font-size: 13px;
}

/* Card-style page header — matches prototype-explore-paragon-10's
   .page-hero/.label-box (a floating white card, left-bordered in signal
   yellow, over a soft gradient banner), replacing this port's earlier
   flat .page-title band. */
/* jpg/page-title09.jpg (client-supplied, moved to public/jpg/) is a
   1923x334 ultra-wide short banner — already shaped for exactly this
   strip, no cropping compromise needed the way a normal photo would.
   Layered under the same gradient this section already had (faded
   further on the left where .label-box sits on top of it, so that text
   stays readable) rather than replacing it outright. */
/* min-height set to News's natural content height (246px desktop /
   299px mobile — News/Products/Contact's copy is now the same .muted
   mt-2 style and wraps to the same 2 lines, so all three already match
   this exactly). Company's own paragraph is genuinely longer and wraps
   to an extra line, so it renders slightly taller than the shared floor
   here — min-height is a floor, not a cap, and there's no way to make
   Company match without shortening its actual copy. */
.page-hero {
  min-height: 246px;
  display: flex;
  align-items: center;
  background:
    linear-gradient(90deg, rgba(255, 255, 255, 0.82) 0% 48%, rgba(255, 255, 255, 0.35) 100%),
    url("/jpg/page-title09.jpg") center / cover;
  border-bottom: 1px solid var(--line);
}

.page-hero .section {
  padding: 44px 0;
}

.label-box {
  background: rgba(255, 255, 255, 0.85);
  border-radius: var(--radius);
  border-left: 6px solid var(--action);
  display: inline-block;
  padding: 22px 28px;
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.08);
}

/* Desktop only: stretch the label-box to the full .section width so its
   right edge lines up with the header's inquiry-cart button above it
   (both share the same min(1180px, calc(100% - 32px)) container width)
   instead of hugging its own text content. Reverted below at the same
   720px breakpoint .nav-links disappears at, so mobile keeps the
   content-sized box it already had. */
@media (min-width: 721px) {
  .page-hero .label-box {
    display: block;
    width: 100%;
  }
}

.label-box h1 {
  margin: 6px 0 0;
  font-size: clamp(30px, 5vw, 48px);
  line-height: 1.05;
}

.products-layout {
  display: grid;
  grid-template-columns: 302px 1fr;
  gap: 24px;
  align-items: start;
}

/* scrollbar-gutter: stable (architecture.md §0-C) reserves a dedicated
   track for the scrollbar alongside the padding box, rather than the
   scrollbar overlapping/hugging the content directly — content sits
   inset from the true edge with visible breathing room before the
   scrollbar, instead of the two touching. Also prevents the classic
   "content shifts sideways the moment a scrollbar appears" layout jump,
   since the gutter is reserved whether or not scrolling is actually
   needed right now. A prior version of this fix moved overflow to a
   separate inner wrapper instead (so the scrollbar would sit flush at
   the rounded corner) — reverted; that wasn't the effect wanted. */
/* Flat white bordered box, no drop shadow — matches prototype's own
   .filter-sidebar exactly (border + radius only). The --surface/--shadow
   combo this replaced doesn't appear on prototype's filter sidebar at
   all; it read as a raised gray panel rather than a plain card sitting
   on the page the way .card/.label-box do elsewhere on the site. */
.filter-sidebar {
  position: sticky;
  top: 94px;
  max-height: calc(100vh - 118px);
  overflow: auto;
  scrollbar-gutter: stable;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
}

.filter-section {
  border-bottom: 1px solid var(--line);
}

.filter-section:last-child {
  border-bottom: 0;
}

.filter-details {
  padding: 0 16px 16px;
}

.filter-sidebar-actions {
  position: sticky;
  bottom: 0;
  display: grid;
  border-top: 1px solid var(--line);
  padding: 12px;
  background: #fff;
}

.filter-sidebar-actions .btn {
  width: 100%;
}

/* Was `.filter-details summary` (native <details>'s own toggle element) —
   now a real <button> since <details>'s [open] state can't be animated by
   CSS (architecture.md §0-C). Same visual treatment, `width: 100%` and
   `text-align: left`/`border: 0`/`background` reset added since a <button>
   doesn't get <summary>'s block-level, borderless, inherited-background
   defaults for free the way that element does. */
.filter-summary {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  margin: 0 0 10px;
  padding: 16px 16px 10px;
  margin-left: -16px;
  margin-right: -16px;
  border: 0;
  background: #fff;
  color: var(--ink);
  font-size: 13px;
  font-weight: 900;
  text-align: left;
  text-transform: uppercase;
  cursor: pointer;
}

.filter-summary[aria-expanded="false"] {
  margin-bottom: 0;
}

.filter-summary-label {
  min-width: 0;
}

.filter-summary-chevron {
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  transition: transform 0.2s ease;
}

.filter-summary[aria-expanded="true"] .filter-summary-chevron {
  transform: rotate(180deg);
}

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

.filter-summary-selected {
  text-transform: none;
  font-weight: 600;
  color: var(--muted);
}

/* Collapsible section content — two nested layers (see filter-panel.tsx's
   own comment on why one element can't do both jobs): this outer layer
   owns the animated height via grid-template-rows (0fr collapsed, 1fr
   open — the standard variable-height CSS transition, no JS height
   measurement needed and no fixed max-height guess to keep in sync with
   content that can grow, e.g. a long option list). `sectionOpen` in
   filter-panel.tsx drives the class swap.

   prefers-reduced-motion below removes the transition (jumps instantly)
   rather than removing the collapse behavior itself — the open/closed
   *state* isn't motion, only the interpolation between them is. */
.filter-collapse {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.25s ease;
}

.filter-collapse-open {
  grid-template-rows: 1fr;
}

.filter-collapse-inner {
  overflow: hidden;
  min-height: 0;
}

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

.filter-search {
  width: 100%;
  min-height: 40px;
  margin-bottom: 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 8px 10px;
  background: #fff;
}

/* Was previously an absolutely-positioned button overlaid on top of the
   input (position: absolute; top/right: 0 with its own fixed min-height) —
   fragile because the input's real rendered height wasn't guaranteed to
   match the button's fixed height on every viewport, so the two drifted
   apart and read as two separate controls instead of one search field.
   Now the row itself is the bordered/rounded control; the input and button
   are plain flex children inside it, sharing its height by construction,
   which can't drift regardless of viewport or font metrics. */
/* height: 44px (border-box, so this is the total rendered height, not
   just content) matches .sort-select and .view-toggle right next to it
   in .results-controls — without it this row was 48px, 4px taller than
   both, since align-items: stretch let it grow to whatever height
   .sku-search-btn's own content/line-box naturally wanted instead of
   being capped to the same 44px "icon-button row" as its neighbors. */
.sku-search-row {
  display: flex;
  align-items: stretch;
  height: 44px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  overflow: hidden;
}

.sku-search-row .filter-search {
  flex: 1;
  min-width: 0;
  margin-bottom: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
}

/* .sku-search-row .sku-search-btn (not bare .sku-search-btn) — needs to
   out-specificity the ≤720px touch-target rule (".btn, .icon-btn {
   min-height: 46px }", further down this file) that also matches this
   button via its shared .btn class. That rule was winning at mobile
   widths despite appearing to lose here on source order alone, because
   both selectors are single-class (equal specificity) and the
   touch-target rule happens to sit later in the file — min-height: 0
   was being silently overridden back up to 46px, 2px taller than the
   row's own 44px height, with the overflow clipped by .sku-search-row's
   overflow: hidden. Same pattern already used for .sort-select above. */
.sku-search-row .sku-search-btn {
  flex-shrink: 0;
  width: 44px;
  min-height: 0;
  padding: 0;
  /* Square on the left (butts against the input), but not on the right
     (the row's own outer edge): .btn now carries a real 2px black
     border (was borderless when this button was last touched), and a
     hard-square right corner on that border was getting sliced off by
     .sku-search-row's overflow: hidden right where it meets the row's
     own 8px rounded corner -- the border survived on 3 sides, vanished
     on the 4th, reading as if the yellow fill had swallowed it. 7px,
     not 8px: nested inside the row's 1px border, so it needs to be
     exactly that 1px smaller to trace the same curve rather than
     poking past it. */
  border-top-right-radius: 7px;
  border-bottom-right-radius: 7px;
  /* .btn's own box-shadow: 0 4px 0 var(--ink) hard-shadow assumes room
     below the button to render into — this one is stretched flush to
     the row's own bottom edge with none, so the row's overflow: hidden
     clipped all but a ~1px sliver of it, showing as a stray black
     fragment at the button's bottom edge instead of either the full
     "lifted" effect or no shadow at all. Off here (not just on :hover
     below) so the resting state matches too. */
  box-shadow: none;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Overrides .btn:hover's lift transform (box-shadow is already off above,
   in the resting state too): this button lives flush inside
   .sku-search-row, which clips overflow to keep the row's own rounded
   corners clean, so a translateY lift would get visibly cut off by that
   clip instead of "lifting" — a background swap is the hover feedback
   that actually reads correctly in this context. */
.sku-search-btn:hover {
  background: var(--sea-2);
  transform: none;
}

.sku-search-btn svg {
  width: 18px;
  height: 18px;
}

.option-list {
  display: grid;
  gap: 6px;
}

.filter-option,
.check-option {
  min-height: 34px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  border-radius: 8px;
  padding: 7px 9px;
  color: var(--ink);
  font-size: 13px;
}

.filter-option {
  border: 0;
  background: transparent;
  text-align: left;
  cursor: pointer;
}

.filter-option.active,
.check-option:has(input:checked) {
  background: rgba(246, 179, 11, 0.1);
  color: var(--sea);
  font-weight: 900;
}

.filter-option:disabled,
.check-option.disabled {
  opacity: 0.42;
}

.check-option input {
  width: 16px;
  height: 16px;
  accent-color: var(--sea);
}

.count {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Collapsed rows stay mounted (see filter-panel.tsx's OptionList) so
   expanding "Show more" never remounts/reflows the rows already visible
   above it — display:none just removes them from layout and paint. */
.filter-option-hidden {
  display: none;
}

.filter-show-more {
  min-height: 34px;
  width: 100%;
  border: 0;
  background: transparent;
  border-radius: 8px;
  padding: 7px 9px;
  text-align: left;
  cursor: pointer;
  color: var(--sea);
  font-weight: 700;
  font-size: 13px;
}

.filter-show-more:hover {
  background: rgba(246, 179, 11, 0.08);
}

.mobile-filter-bar {
  display: none;
  gap: 10px;
  align-items: center;
  margin-bottom: 16px;
}

/* Selected-filter count on the mobile Filters trigger — was a plain
   "(2)" appended to the label, inconsistent with .cart-count's circular
   badge for the same "how many are currently selected" concept in the
   header. Colors are inverted from .cart-count (dark circle, light
   text) rather than copied outright: the trigger button itself is now
   filled with signal yellow (.btn.sea matches .btn.primary), so a
   yellow-on-yellow badge would be invisible. */
.filter-count-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--ink);
  color: #fff;
  font-size: 12px;
  font-weight: 800;
}

.active-chips {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 2px;
}

.chip {
  flex: 0 0 auto;
  min-height: 32px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border: 1px solid rgba(246, 179, 11, 0.16);
  border-radius: 999px;
  padding: 6px 10px;
  background: #fff;
  color: var(--sea);
  font-size: 12px;
  font-weight: 900;
}

.chip button {
  border: 0;
  background: transparent;
  color: inherit;
  cursor: pointer;
}

.results-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 16px;
}

.results-top select {
  min-height: 42px;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 8px 12px;
  background: var(--surface);
}

.results-controls {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* Part No. Search, moved here from the filter sidebar's first section
   (now sits left of the sort dropdown instead) — .sku-search-row has no
   width of its own otherwise, so it'd just shrink to its content's
   minimum inside this flex row. Grows to fill the row (flex: 1) now that
   .sort-select next to it is a fixed 44px icon rather than a wide text
   dropdown, instead of the fixed 220px this had when the two shared the
   row more evenly. */
.results-controls .sku-search-row {
  flex: 1;
  min-width: 0;
}

/* Icon-only sort control — still a real <select> (native picker, full
   keyboard/screen-reader support via the aria-label/title in the JSX),
   just styled so the closed box shows only the sort glyph instead of the
   current option's text ("Newest" etc.), freeing up width for the search
   box beside it. appearance: none removes the browser's own dropdown
   arrow (redundant with the glyph here); color: transparent hides the
   selected option's text in the closed box specifically — the option
   list itself gets its own explicit color below so the opened dropdown
   still reads normally. */
/* .results-controls select.sort-select (not just .sort-select) — needs to
   out-specificity the pre-existing ".results-top select" rule above
   (class+element beats a single class), or that rule's border/padding/
   background would win over this one's despite appearing later in the
   file. */
.results-controls select.sort-select {
  appearance: none;
  -webkit-appearance: none;
  flex-shrink: 0;
  width: 44px;
  min-width: 44px;
  height: 44px;
  min-height: 44px;
  padding: 0;
  color: transparent;
  text-align: center;
  cursor: pointer;
  border: 2px solid var(--ink);
  border-radius: var(--radius);
  background:
    #fff
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%230d0d0c' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 10l5-5 5 5M7 14l5 5 5-5'/%3E%3C/svg%3E")
    no-repeat center / 20px;
}

.sort-select option {
  color: var(--ink);
  text-align: left;
}

/* Grid/Compact List toggle — same 44px thick-border icon-button language
   as .sort-select right next to it, not a generic segmented control.
   Two buttons sharing one bordered pill (not two separate .icon-btns)
   so the pair reads as one control with two states, matching how a
   native OS view-switcher groups its options. height: 44px here (not
   just on the buttons below) so it's the wrapper's total rendered
   height, matching .sort-select/.sku-search-row exactly — previously
   only the inner buttons were 44px tall, and this wrapper's own 2px
   border added on top of that, inflating the visible control to 48px. */
.view-toggle {
  display: inline-flex;
  flex-shrink: 0;
  height: 44px;
  border: 2px solid var(--ink);
  border-radius: var(--radius);
  overflow: hidden;
}

.view-toggle button {
  width: 42px;
  height: 100%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: #fff;
  color: var(--muted);
  cursor: pointer;
}

.view-toggle button + button {
  border-left: 2px solid var(--ink);
}

.view-toggle button svg {
  width: 19px;
  height: 19px;
}

.view-toggle button.active {
  background: var(--ink);
  color: #fff;
}

/* auto-fill/minmax instead of a hardcoded per-breakpoint column count
   (was repeat(3, ...) here, repeat(2, ...) under the 1040px tier) — the
   fixed-2-column tier let cards balloon to 368-488px wide with sidebar
   hidden (721-1040px), a lot of dead space around .product-media's fixed
   1:1 square since real parts photos don't fill that frame, and produced
   a jarring snap right at the sidebar-reappear boundary (488px cards at
   1040px viewport, 231px cards one pixel later at 1041px — column count
   more than halving for a 1px width change, since both the sidebar
   visibility and this column count were pinned to the same 1040px
   breakpoint independently). 250px keeps 3 columns at the capped desktop
   container width (854px, same as before — 274px cards, unchanged from
   the old fixed rule) while letting column count respond continuously
   to whatever width is actually available as the sidebar comes and
   goes, instead of two independent breakpoints fighting each other. */
.product-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 16px;
}

.product-card {
  display: flex;
  flex-direction: column;
  min-width: 0;
  border: 1px solid var(--line);
  border-radius: 8px;
  overflow: hidden;
  background: var(--surface);
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.04);
}

.product-media {
  position: relative;
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, var(--surface-2), #fff);
}

/* Yellow corner badge — matches prototype's .corner-tag, used on photo/
   media areas (product thumbnails, News cards) to surface a short label
   (category, date) without competing with the image itself. */
.corner-tag {
  position: absolute;
  left: 10px;
  bottom: 10px;
  z-index: 1;
  display: inline-flex;
  background: var(--action);
  color: var(--ink);
  padding: 6px 9px;
  border-radius: 5px;
  font-size: 12px;
  font-weight: 900;
}

.product-media img {
  width: 82%;
  height: 82%;
  object-fit: contain;
  mix-blend-mode: multiply;
}

/* No-image placeholder inside a product card/detail image slot must fill the
   1:1 .product-media box exactly, not the fixed min-height: 260px that
   .photo-box uses elsewhere (company.html, news-detail.html) — those other
   usages are unrelated standalone placeholders, not a sibling of a real
   product photo, so .photo-box's shared base rule stays untouched. */
.product-media .photo-box {
  width: 100%;
  height: 100%;
  min-height: 0;
  border: 0;
  border-radius: 0;
  background: none;
}

.product-media .product-image-fallback {
  width: 100%;
  height: 100%;
}

.product-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  padding: 16px;
}

.product-body h3 {
  margin: 0;
  font-size: 20px;
}

.sku {
  color: var(--muted);
  font-family: "IBM Plex Mono", Consolas, monospace;
  font-size: 12px;
}

/* Product cards swap the title/part-number colors relative to every other
   .sku usage on the site (breadcrumbs, company.html's stat labels, inquiry
   numbers, etc. — .sku is a generic small-muted-text class shared well
   beyond actual SKUs, confirmed by grep before touching it) — scoped to
   .product-card specifically rather than changing the shared base rules,
   so this swap applies only to the product list's cards and the related-
   products mini-cards on the detail page (both use .product-card), not
   every other place .sku/h3 appear across the site. h3 has no color rule
   of its own elsewhere (inherits --ink via body), so this is the only
   place a title color is set at all — not overriding a competing rule. */
.product-card h3 {
  color: var(--muted);
}

.product-card .sku {
  color: var(--ink);
}

/* Title + Part No. share one small block with its own tight internal gap,
   independent of whatever spacing rule positions the block as a whole —
   margin-bottom below carries the ~5px breathing room before the next
   item (e.g. brand badges), never entangled with row-gap/margins meant
   for surrounding content. */
.product-title-block {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0 0 5px;
}

/* Main product-grid's title-block swaps which text (Part No. vs title)
   populates the h3 vs .sku elements (products-client.tsx). Font SIZE
   stays attached to each STRING, not the element — the title must stay
   the visually larger, prominent one and Part No. the smaller label,
   regardless of which element now holds which (h3 shrinks to Part No.'s
   size, .sku grows to the title's). Color, per a second explicit
   follow-up, flips back the OTHER way — the swap's initial "color
   follows the element" result (h3 muted, .sku ink, same as the base
   .product-card rule above) got reversed again: h3 (Part No.) is now
   ink, .sku (title) is now muted. Both scoped to
   ".product-grid .product-card" specifically (not the bare .product-card
   h3/.sku above, which still apply unmodified) so neither the size nor
   the color change also touches the detail page's related-products
   mini-cards — same .product-card/h3/.sku classes, never part of this
   swap, keeping their own original size and color. */
.product-grid .product-card h3 {
  font-size: 14px;
  color: var(--ink);
}

.product-grid .product-card .sku {
  font-size: 20px;
  color: var(--muted);
}

.tag-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 12px 0;
}

.stroke-preview {
  display: grid;
  gap: 6px;
  margin: 12px 0;
}

.stroke-row {
  display: grid;
  grid-template-columns: 30px minmax(0, 1fr);
  align-items: center;
  gap: 8px;
}

.stroke-label {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 25px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.08);
  color: var(--muted);
  font-size: 10px;
  font-weight: 900;
}

/* Min–max range text ("5-8HP"), replacing the old per-value badge row —
   see specRangeSummary() in products-client.tsx. */
.stroke-range {
  font-size: 13px;
  font-weight: 800;
  color: var(--ink);
  font-family: "IBM Plex Mono", Consolas, monospace;
}

.badge {
  display: inline-flex;
  align-items: center;
  min-height: 25px;
  border-radius: 999px;
  padding: 4px 8px;
  background: rgba(246, 179, 11, 0.1);
  color: var(--sea);
  font-size: 11px;
  font-weight: 900;
  line-height: 1.2;
}

.badge.hp {
  background: #f1eadc;
  color: #6f5930;
}

.badge.tag {
  background: rgba(22, 163, 74, 0.1);
  color: var(--success);
}

.card-actions {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
  margin-top: auto;
}

/* Compact List view — see CompactProductRow in products-client.tsx.
   .compact-body is a column at narrow widths (thumb + stacked content,
   same "small photo, everything else below" shape as .cart-item/
   .news-card elsewhere on the site) and switches to a single wide row
   once there's room via the @container query below.

   A container query, not a viewport @media query, because this column's
   actual available width isn't a fixed function of viewport width —
   .filter-sidebar (302px + 24px gap) occupies part of the viewport
   starting at 1041px, so the same 1041px browser window has a ~683px
   content column while a 1039px window (sidebar hidden) has almost the
   full viewport. A viewport-width breakpoint picked for one case
   overflowed the other (confirmed live: the row layout activating at a
   flat 900px viewport overflowed by 157px the moment the sidebar
   reappeared at 1041px). container-type below makes .products-content
   report its own real rendered width, which already reflects whether
   the sidebar is taking space. */
.products-content {
  container-type: inline-size;
}

.compact-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.compact-row {
  display: flex;
  gap: 14px;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
}

.compact-media {
  position: relative;
  flex-shrink: 0;
  width: 72px;
  height: 72px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 6px;
  background: linear-gradient(135deg, var(--surface-2), #fff);
}

.compact-media img {
  width: 82%;
  height: 82%;
  object-fit: contain;
  mix-blend-mode: multiply;
}

.compact-media .product-image-fallback {
  width: 100%;
  height: 100%;
  border-radius: 6px;
}

.compact-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.compact-title-block {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.compact-title-block h3 {
  font-size: 15px;
  overflow-wrap: break-word;
  color: var(--muted);
}

/* Same Part No./title color swap as .product-grid .product-card
   (Grid view) — Compact List already had Part No. first, title second
   in the markup, but the colors were never touched: .sku's own base
   rule (muted) and h3's plain inherited ink were still showing title
   muted-below/Part-No-muted-above instead of matching Grid's swapped
   ink-for-Part-No./muted-for-title. Scoped to .compact-row so it
   doesn't affect .sku/h3 elsewhere on the site. */
.compact-row .sku {
  color: var(--ink);
}

.compact-cell {
  font-size: 13px;
  color: var(--ink);
}

.compact-hp {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-family: "IBM Plex Mono", Consolas, monospace;
  font-weight: 700;
}

.compact-hp span {
  white-space: nowrap;
}

.compact-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* flex-basis, not flex:1's implicit 0 — "Add Inquiry" is meaningfully
   longer than "Details" at the same font-size/padding, so an equal
   0-based split was giving both buttons identical width regardless of
   content and wrapping "Add Inquiry" onto two lines at narrow card
   widths (confirmed at 320px). 120px is enough for either button's text
   on one line; flex-wrap above stacks them full-width instead once the
   row genuinely can't fit both side by side. */
.compact-actions .btn {
  flex: 1 1 120px;
  white-space: nowrap;
}

/* Budget for the container width this activates at (700px, see
   .products-content's comment above): padding 20 + row-gap 12 + media
   64 + 4 internal gaps (10px each = 40) + brand 85 + category 85 + hp
   150 + actions ~175 = 631px fixed, leaving .compact-title-block ~69px
   of its own before flex-shrink kicks in — tight but workable (a long
   title just wraps to two lines, same as it already can in the column
   layout below this threshold). */
@container (min-width: 700px) {
  .compact-body {
    flex-direction: row;
    align-items: center;
    gap: 10px;
  }

  .compact-title-block {
    flex: 1 1 180px;
  }

  .compact-brand,
  .compact-category {
    flex: 0 0 85px;
  }

  .compact-hp {
    flex: 0 0 150px;
    flex-direction: row;
    flex-wrap: wrap;
    row-gap: 2px;
    column-gap: 10px;
  }

  .compact-actions {
    flex: 0 0 auto;
    margin-left: auto;
  }

  .compact-actions .btn {
    flex: none;
  }
}

.drawer-overlay {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: none;
  background: rgba(15, 15, 13, 0.46);
}

.drawer-overlay.open {
  display: block;
}

.filter-drawer {
  position: fixed;
  inset: 0 auto 0 0;
  z-index: 90;
  width: min(340px, 88vw);
  display: flex;
  flex-direction: column;
  transform: translateX(-104%);
  transition: transform 0.24s cubic-bezier(0.22, 1, 0.36, 1);
  background: #fff;
  box-shadow: 24px 0 50px rgba(15, 15, 13, 0.24);
}

.filter-drawer.open {
  transform: translateX(0);
}

.drawer-head {
  min-height: 64px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid var(--line);
  padding: 12px 16px;
}

.drawer-body {
  overflow: auto;
}

.drawer-actions {
  position: sticky;
  bottom: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  border-top: 1px solid var(--line);
  padding: 12px;
  background: #fff;
}

.detail-layout {
  display: grid;
  grid-template-columns: minmax(280px, 0.92fr) 1.08fr;
  gap: 34px;
  align-items: start;
}

/* Flush, no card chrome — matches prototype's .photo block sitting
   directly on the page (a real product photo, unlike .photo itself,
   which stays reserved for decorative placeholders — see its own
   comment). The soft gradient backdrop below (same treatment as catalog
   .product-media thumbnails) plus a pinned .corner-tag SKU badge
   replicate .photo's visual language without reusing the class itself. */
.detail-media {
  position: relative;
}

.gallery-main {
  position: relative;
  overflow: hidden;
  cursor: zoom-in;
  border-radius: var(--radius);
  aspect-ratio: 1;
  background: linear-gradient(135deg, var(--surface-2), #fff);
}

.gallery-main img {
  display: block;
  width: 82%;
  height: 82%;
  margin: 9%;
  object-fit: contain;
  mix-blend-mode: multiply;
}

/* Scoped override, not a change to the base .corner-tag rule used
   sitewide (product/News/category cards all have short, controlled
   labels) — this is the one place a corner-tag can receive an arbitrary-
   length compound value (product.name), so only here needs a width cap
   and truncation to stay inside the photo instead of overflowing it. */
.detail-media .corner-tag {
  max-width: calc(100% - 20px);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: "IBM Plex Mono", Consolas, monospace;
}

.detail-media-empty {
  position: relative;
}

.detail-media .product-image-fallback {
  border-radius: var(--radius);
  aspect-ratio: 1;
}

/* Cursor-tracked magnifier — mouse-only input (see product-gallery.tsx's
   comment on why this is gated on actual input capability, not a
   screen-width breakpoint). While .zooming, the <img> is hidden and the
   container's own background-image (set inline via JS from
   mousemove) takes over at 220% size, positioned by cursor coordinates —
   the classic "background-position tracks the cursor" zoom technique,
   avoiding a second full-resolution <img> element or a canvas. .zooming
   only ever gets added by the mouseenter handler in product-gallery.tsx,
   which touch input never fires, so no separate touch-side override is
   needed here — this block only matters on devices where it can trigger. */
@media (hover: hover) and (pointer: fine) {
  .gallery-main.zooming img {
    opacity: 0;
  }

  .gallery-main.zooming {
    background-repeat: no-repeat;
    background-size: 220%;
  }
}

/* Fullscreen tap-to-view for touch devices (also reachable by clicking
   the main image on desktop, as a bonus, not a requirement). Pinch-zoom
   and pan are the browser's own native gesture handling on the <img> —
   deliberately not reimplemented in JS: this site's viewport meta never
   sets user-scalable=no or a maximum-scale, so native pinch-zoom already
   works on any fullscreen image without extra code. */
.gallery-lightbox {
  position: fixed;
  inset: 0;
  z-index: 110;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(15, 15, 13, 0.92);
  padding: 24px;
}

.gallery-lightbox img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  touch-action: pinch-zoom;
}

.gallery-lightbox-close {
  position: absolute;
  top: 18px;
  right: 18px;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  cursor: pointer;
}

.gallery-lightbox-close:hover {
  background: rgba(255, 255, 255, 0.22);
}

.gallery-thumbs {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
  gap: 8px;
  margin-top: 12px;
}

.gallery-thumbs button {
  border: 1px solid var(--line);
  border-radius: 6px;
  background: #fff;
  padding: 4px;
  aspect-ratio: 1;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.gallery-thumbs button.active,
.gallery-thumbs button:hover {
  border-color: var(--sea);
  box-shadow: 0 0 0 2px rgba(201, 135, 0, 0.16);
}

.gallery-thumbs img {
  width: 100%;
  height: 100%;
  aspect-ratio: 1;
  object-fit: contain;
}

/* Flush — no card border/background/padding, matching prototype's detail
   panel sitting directly on the page. Replaces the earlier bordered card
   that wrapped .spec-grid; that grid-of-mini-cards layout plus the tabs
   UI below it are both gone in favor of prototype's single flowing
   .spec-table (see .spec-table below), which folds what used to be a
   separate "Additional Information" tab (HP/displacement/model-year/
   engine-model) directly into the same table as ordinary rows. */
.detail-panel {
  padding: 0;
}

/* Detail page's eyebrow/title-slot swap (page.tsx) moved which STRING is
   large/prominent vs small/label-sized along with the position+color
   swap — per follow-up correction, size should stay swapped back: the
   title ("CABLE, STEERING") must render at the large heading size
   regardless of now sitting in the .eyebrow element, and Part No. stays
   small regardless of now sitting in .detail-title. Scoped to
   .detail-panel specifically since bare .eyebrow is used site-wide. */
.detail-panel .eyebrow {
  font-size: clamp(30px, 4vw, 46px);
  line-height: 1.05;
}

.detail-title {
  font-size: 16px;
  line-height: 1.4;
  margin: 10px 0;
}

/* Matches prototype's .spec-table exactly: a plain bordered table, label
   column shaded, value column plain — replacing the earlier .spec-grid
   mini-card grid. HP/displacement/model-year/engine-model values render
   as .badge.hp pills inside a cell (same technique prototype's own BOLT
   stress-test page uses for its 48 HP combinations), not a separate tab. */
.spec-table {
  width: 100%;
  border-collapse: collapse;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  margin: 20px 0;
}

.spec-table th,
.spec-table td {
  text-align: left;
  border-bottom: 1px solid var(--line);
  padding: 14px;
  vertical-align: top;
}

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

.spec-table th {
  width: 180px;
  background: var(--surface);
}

.detail-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-top: 22px;
}

.related-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}

/* bottom: 90px, not 18px — .toast (below) already claims this same
   bottom-right corner at bottom: 18px for its own fixed positioning, so
   stacking this any lower would overlap an in-flight "Added to Inquiry"
   toast instead of sitting cleanly above it. z-index: 60 is deliberately
   below .drawer-overlay/.filter-drawer/.cookie-banner/.toast/
   .gallery-lightbox (80-110) so none of those get hidden behind it, but
   above .site-header/.mobile-filter-bar (30-40) so it isn't itself
   hidden by ordinary page chrome. */
.back-to-top-btn {
  position: fixed;
  right: 18px;
  bottom: 90px;
  z-index: 60;
  width: 46px;
  height: 46px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--ink);
  border-radius: var(--radius);
  background: #fff;
  color: var(--ink);
  box-shadow: 0 4px 0 var(--ink);
  cursor: pointer;
}

.back-to-top-btn:hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 0 var(--ink);
}

.back-to-top-btn svg {
  width: 20px;
  height: 20px;
}

.toast {
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: 100;
  max-width: min(360px, calc(100vw - 36px));
  transform: translateY(16px);
  opacity: 0;
  pointer-events: none;
  border-radius: 8px;
  padding: 14px 16px;
  background: var(--ink);
  color: white;
  box-shadow: var(--shadow);
  transition: opacity 0.18s ease, transform 0.18s ease;
}

.toast.show {
  transform: translateY(0);
  opacity: 1;
}

.empty-state {
  border: 1px dashed var(--line);
  border-radius: 8px;
  padding: 34px;
  background: rgba(255, 255, 255, 0.58);
  text-align: center;
}

/* Success feedback (inquiry submitted, message sent) — uses the design
   tokens' own --success color instead of raw Tailwind emerald utilities,
   so it stays tied to the same token system as .badge.tag rather than a
   hardcoded color that drifts if --success ever changes. */
.status-message {
  padding: 16px;
  border-radius: var(--radius);
  font-weight: 800;
}

.status-message.success {
  background: rgba(22, 163, 74, 0.1);
  color: var(--success);
  border: 1px solid rgba(22, 163, 74, 0.3);
}

.content-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(280px, 340px);
  gap: 24px;
  align-items: start;
}

/* Even 2-column split — matches prototype's .grid.cols-2 as used on
   Contact (dark info card | form), as distinct from .content-grid's
   uneven article/aside proportions used elsewhere on this page. */
.contact-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 24px;
  align-items: start;
}

.content-grid > *,
.content-grid aside {
  min-width: 0;
  max-width: 100%;
}

/* Dark contact-info card — matches prototype's contact.html treatment
   (black card holding phone/fax/email + map, next to the plain form). */
.card.dark {
  background: #050505;
  color: #fff;
  border-color: #050505;
}

.card.dark .info-row {
  border-top-color: rgba(255, 255, 255, 0.15);
}

.card.dark .info-row span {
  color: rgba(255, 255, 255, 0.6);
}

.card.dark .info-row strong {
  color: #fff;
}

.card.dark .eyebrow {
  color: rgba(255, 255, 255, 0.6);
}

.card.dark .map-box {
  margin-top: 18px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.15);
}

.info-list {
  display: grid;
  gap: 12px;
}

.info-row {
  display: grid;
  grid-template-columns: minmax(86px, 112px) minmax(0, 1fr);
  gap: 14px;
  border-top: 1px solid var(--line);
  padding-top: 12px;
}

.info-row:first-child {
  border-top: 0;
  padding-top: 0;
}

.info-row span {
  color: var(--muted);
  font-size: 12px;
  font-weight: 900;
  text-transform: uppercase;
}

.info-row strong {
  min-width: 0;
  overflow-wrap: anywhere;
  word-break: break-word;
}

.form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
}

.field {
  display: grid;
  gap: 7px;
}

.field.full {
  grid-column: 1 / -1;
}

.field label {
  color: var(--ink);
  font-size: 13px;
  font-weight: 900;
}

.field input,
.field textarea,
.field select {
  width: 100%;
  min-height: 44px;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 10px 12px;
  background: #fff;
  color: var(--ink);
}

.field textarea {
  min-height: 132px;
  resize: vertical;
}

.map-box,
.photo-box {
  max-width: 100%;
  min-height: 260px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 8px;
  background:
    linear-gradient(rgba(246, 179, 11, 0.08) 1px, transparent 1px),
    linear-gradient(90deg, rgba(246, 179, 11, 0.08) 1px, transparent 1px),
    var(--surface-2);
  background-size: 28px 28px;
  color: var(--sea);
  font-weight: 900;
  text-align: center;
}

/* No-photo placeholder for product imagery specifically (list cards,
   detail page, related products, cart line items) — the site logo fills
   the frame on a plain white background, replacing the grid-pattern +
   name-initials .photo-box used for non-product placeholders elsewhere
   (map embeds, News). Per-context sizing rules below mirror whatever
   that slot's real <img> would have used, same pattern .photo-box itself
   already follows (see .product-media/.detail-media overrides above). */
.product-image-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: #fff;
}

.product-image-fallback img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  padding: 12%;
}

/* Recently-viewed strip's mini cards — same visual vocabulary as
   .product-card (white, thin border, .sku's mono/muted styling) instead
   of the generic rounded-lg/border/hover:bg-gray-50 Tailwind combo this
   replaced, which didn't read as part of the same site as the catalog
   cards next to it. */
.recent-view-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 128px;
  flex-shrink: 0;
  text-align: left;
  padding: 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: #fff;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.recent-view-card:hover:not(:disabled) {
  border-color: var(--sea);
  box-shadow: 0 0 0 2px rgba(201, 135, 0, 0.16);
}

.recent-view-card:disabled {
  opacity: 0.5;
}

.recent-view-media {
  position: relative;
  aspect-ratio: 1;
  border-radius: 4px;
  overflow: hidden;
  background: linear-gradient(135deg, var(--surface-2), #fff);
}

.recent-view-media img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  mix-blend-mode: multiply;
}

.recent-view-media .product-image-fallback {
  width: 100%;
  height: 100%;
  background: none;
}

.recent-view-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13px;
  font-weight: 800;
}

.recent-view-card .sku {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 11px;
}

.map-box {
  position: relative;
  display: block;
  aspect-ratio: 4 / 3;
  min-height: 0;
  padding: 0;
  background: #fff;
  color: inherit;
}

.map-box iframe {
  display: block;
  width: 100%;
  max-width: 100%;
  height: 100%;
  min-height: 0;
}

.cart-list {
  display: grid;
  gap: 12px;
}

.cart-item {
  display: grid;
  grid-template-columns: 86px minmax(0, 1fr) 120px;
  gap: 14px;
  align-items: center;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 12px;
  background: #fff;
}

.cart-item img {
  width: 86px;
  height: 86px;
  object-fit: contain;
  border-radius: 8px;
  background: var(--surface-2);
}

.cart-item .product-image-fallback {
  width: 86px;
  height: 86px;
  border-radius: 8px;
}

.qty-control {
  display: flex;
  align-items: center;
  gap: 8px;
}

.qty-control button {
  width: 34px;
  height: 34px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  font-weight: 900;
  cursor: pointer;
}

.news-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 20px;
}

.segmented {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.segmented button,
.pagination a {
  min-height: 38px;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 8px 12px;
  background: var(--surface);
  color: var(--ink);
  font-weight: 900;
  cursor: pointer;
}

.segmented button.active,
.pagination a.active {
  border-color: var(--sea);
  background: rgba(246, 179, 11, 0.1);
  color: var(--sea);
}

.news-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}

.news-card {
  display: grid;
  grid-template-columns: 140px minmax(0, 1fr);
  gap: 16px;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 14px;
  background: var(--surface);
}

/* No cover image uploaded: skip the thumbnail column entirely (no <img>
   is rendered) rather than showing a placeholder box — a placeholder
   needed a fixed height to look intentional next to a real 140x120
   thumbnail, and no single fixed height/ratio matched both this card's
   proportions and the article page's own now-unused cover-image slot, so
   it always read as an odd blank tile rather than "just no image here."
   Text content fills the full card width instead. */
.news-card-no-image {
  grid-template-columns: 1fr;
}

.news-thumb {
  position: relative;
  width: 140px;
  height: 120px;
}

.news-thumb .corner-tag {
  left: 6px;
  bottom: 6px;
  font-size: 10px;
  padding: 4px 7px;
}

.news-card img {
  width: 140px;
  height: 120px;
  /* cover, not contain: News cover images are admin-uploaded photos of
     varying aspect ratios (unlike product photos, which are consistently
     near-square) — contain preserved the full image but let a wide/
     landscape upload shrink to a thin sliver inside this near-square
     140x120 box to keep its ratio, which read as "the image is cut off"
     even though nothing was actually cropped. cover fills the box and
     crops the overflow instead, so every upload reads as a normal-sized
     thumbnail regardless of its original ratio. */
  object-fit: cover;
  border-radius: 8px;
  background: var(--surface-2);
}

.pagination {
  display: flex;
  gap: 8px;
  margin-top: 22px;
}

@media (max-width: 1180px) {
  .content-grid {
    grid-template-columns: minmax(0, 1fr) minmax(260px, 320px);
    gap: 20px;
  }
}

@media (max-width: 1040px) {
  .category-grid,
  .metric-strip {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .products-layout,
  .content-grid,
  .contact-grid,
  .route-band .section,
  .hero-grid {
    grid-template-columns: 1fr;
  }

  /* .hero::after is absolutely positioned with a viewport-relative width
     (min(58vw, 720px)) entirely independent of .hero-grid's column
     layout — it was designed to peek out from the empty space the 2-
     column grid's right portion creates, with .stats-card deliberately
     overlapping its corner. Once the grid above goes single-column, the
     text block becomes full-width with nothing left carving out space
     for the photo, so it started running directly over the headline/
     paragraph/search box instead (confirmed live: paragraph text was
     genuinely unreadable where it crossed behind the image, not just a
     minor visual nit). There's no single-column equivalent of "photo
     peeking out from the right" composition to preserve — hiding it
     here is the actual fix, not a workaround. */
  .hero::after {
    display: none;
  }

  .footer-grid {
    grid-template-columns: 1fr 1fr;
  }

  .filter-sidebar {
    display: none;
  }

  /* Sticky under .site-header rather than scrolling away with the page
     — a catalog with a long product list otherwise means re-opening
     filters requires scrolling all the way back up first. Own
     background/padding/z-index since it's no longer just an inline
     block in the page flow, it's a strip pinned above whatever scrolls
     beneath it within .section's own width — not full-bleed to the
     viewport edge, since .section has no fixed side padding to offset
     with a negative margin (it's width-clamped + auto-centered, not
     padded). top: 78px, not a smaller guess — must exactly match
     .nav-shell's min-height (.site-header's only content, so that's its
     real rendered height); a mismatched top left a few px of the page's
     own background exposed between the two sticky bars whenever
     .site-header's higher z-index didn't happen to paper over it. */
  .mobile-filter-bar {
    display: flex;
    position: sticky;
    top: 78px;
    z-index: 30;
    padding: 10px 0;
    /* Matches .site-header's own sticky treatment (rgba white, not
       var(--page) solid) rather than a fully opaque strip — the two bars
       stack sticky on top of each other while scrolling, so a mismatched
       opacity between them read as two different UI languages. No
       border-bottom (previously here) either: against a translucent bar
       with scrolled content showing through, a hard 1px line read as a
       stray seam between the hazy translucent zone above it and the
       opaque page content below, rather than an intentional divider. */
    background: rgba(255, 255, 255, 0.96);
  }

  /* .results-top's own FilterChips copy is redundant once
     .mobile-filter-bar's copy is switched on above — without this, both
     render at this breakpoint and the active-filter chips show up twice
     on the page (once under the Filters(N) button, once under the
     product count). */
  .results-top-chips {
    display: none;
  }

  /* No List-specific overrides at this breakpoint — the 1180px tier's
     132px-image / 2-column-info horizontal row (above) is a sensible fit
     all the way down to where the 720px tier's stacked mobile split
     (below) takes over. A stray flex-column override used to sit here
     that stretched the image to ~full card width, i.e. a List row lost
     its own distinct shape and read as an ordinary Grid card — the gap
     the user flagged: "List looks like Grid somewhere between tablet and
     phone width." It went unnoticed because List mode was hard-locked
     to Grid under 720px (before §0-C-105) so nobody exercised the (720,
     1040] range in List mode to begin with. */
}

@media (max-width: 720px) {
  /* Given its own breakpoint, split off from the shared 1040px one above
     (.products-layout/.content-grid/.route-band .section) — that breakpoint
     exists because .filter-sidebar genuinely needs the width a real tablet
     doesn't reliably have, but .detail-layout's two columns (image + spec
     panel) fit comfortably at 768px: left column is grid-clamped to
     minmax(280px, 0.92fr), so it no longer needs a manual .detail-media
     max-width once it's inside the grid — that cap only mattered for the
     single-column case below, where nothing else constrains the image's
     width. Confirmed via screenshot: 768px/820px were needlessly forced
     into a single stacked column with an oversized image, even though the
     two-column layout has room to spare at those widths.  */
  .detail-layout {
    grid-template-columns: 1fr;
  }

  .detail-media {
    max-width: 480px;
    margin: 0 auto;
  }

  .top-strip {
    display: none;
  }

  .nav-links {
    display: none;
  }

  /* Same tier .nav-links drops at, not a separate 640px rule (as this
     used to be, back when this was a plain "Parts Search" link rather
     than the .header-search-row search field it is now) — that mismatch
     left a 641-720px window where both rendered together, a visibly
     redundant "two search entry points side by side" (confirmed live at
     680px). .mobile-search-btn's icon + .mobile-search-bar below cover
     the same "search and navigate in one step" job at this width and
     narrower, so there's nothing lost by dropping the standing field
     here. */
  .header-search-row {
    display: none;
  }

  .mobile-menu-btn {
    display: inline-flex;
  }

  .mobile-menu.open {
    max-height: 500px;
    border-top-width: 1px;
    opacity: 1;
    visibility: visible;
    transition:
      max-height 0.24s cubic-bezier(0.22, 1, 0.36, 1),
      border-top-width 0.24s cubic-bezier(0.22, 1, 0.36, 1),
      opacity 0.24s cubic-bezier(0.22, 1, 0.36, 1);
  }

  .mobile-search-btn {
    display: inline-flex;
  }

  .mobile-search-bar.open {
    max-height: 160px;
    padding: 14px 24px;
    border-top-width: 1px;
    opacity: 1;
    visibility: visible;
    transition:
      max-height 0.24s cubic-bezier(0.22, 1, 0.36, 1),
      padding 0.24s cubic-bezier(0.22, 1, 0.36, 1),
      border-top-width 0.24s cubic-bezier(0.22, 1, 0.36, 1),
      opacity 0.24s cubic-bezier(0.22, 1, 0.36, 1);
  }
  /* This block's .brand img override was removed — the base rule
     (styles.css:162) is now 51px too, so this mobile-only copy became a
     no-op duplicate. .brand's base min-width: 0 (styles.css:121) still
     handles the 320px overflow this block originally existed to fix
     (nav-shell's brand + header-actions exceeding viewport width,
     pushing the hamburger off-screen). */

  .hero {
    height: auto;
    max-height: none;
    min-height: auto;
  }

  .section,
  .hero-inner {
    width: min(calc(100% - 28px), 1180px);
    padding: 48px 0;
  }

  .category-grid,
  .product-grid,
  .related-grid,
  .form-grid,
  .news-grid {
    grid-template-columns: 1fr;
  }

  .news-toolbar,
  .cart-item,
  .news-card {
    grid-template-columns: 1fr;
  }

  /* .sort-select deliberately excluded from both rules below — it stays
     a fixed 44px icon at every width; only the search box grows to fill
     whatever room that leaves. */
  .results-controls {
    width: 100%;
  }

  .results-controls .sku-search-row {
    width: auto;
    flex: 1;
    min-width: 0;
  }

  .news-toolbar {
    align-items: stretch;
    flex-direction: column;
  }

  .news-card img {
    width: 100%;
  }

  .info-row {
    grid-template-columns: 1fr;
    gap: 4px;
  }

  .results-top {
    align-items: flex-start;
    flex-direction: column;
  }

  .display-title {
    font-size: 38px;
  }

  .lead {
    font-size: 16px;
  }

  .card-actions {
    grid-template-columns: 1fr;
  }

  .btn,
  .icon-btn {
    min-height: 46px;
  }
}

.cookie-banner {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 95;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 24px;
  background: var(--navy);
  color: rgba(255, 255, 255, 0.92);
  box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.24);
}

.cookie-banner p {
  margin: 0;
  font-size: 13px;
  line-height: 1.6;
  max-width: 640px;
}

.cookie-banner a {
  color: white;
  text-decoration: underline;
}

.cookie-banner-actions {
  display: flex;
  gap: 8px;
  flex-shrink: 0;
}

.cookie-banner .btn.ghost {
  border-color: rgba(255, 255, 255, 0.4);
  color: white;
}

@media (max-width: 640px) {
  /* Matches prototype's own 640px .spec-table collapse exactly: rows
     stack into label-then-value blocks instead of staying in a fixed
     180px-label / flexible-value row, which gets cramped once HP badge
     lists wrap to several lines at phone width. */
  .spec-table,
  .spec-table tbody,
  .spec-table tr,
  .spec-table th,
  .spec-table td {
    display: block;
    width: 100%;
  }

  .spec-table th {
    border-bottom: 0;
    padding-bottom: 4px;
  }

  .cookie-banner {
    flex-direction: column;
    align-items: stretch;
  }

  .cookie-banner-actions {
    justify-content: flex-end;
  }

  .footer-grid {
    grid-template-columns: 1fr;
  }

  .metric-strip,
  .promise-metrics {
    grid-template-columns: 1fr;
  }

  .page-hero {
    min-height: 299px;
  }
}
