/* The Gun Search — brand palette + light/dark themes. Plain CSS, no framework.
 *
 * MOBILE-FIRST: base rules are the phone layout; min-width queries layer on the
 * desktop enhancement. Most traffic arrives from social on a phone. Never give
 * a flex/grid child `white-space: nowrap` while a sibling is allowed to shrink —
 * the nowrap child wins the space fight and collapses the other to nothing.
 */

:root {
  /* Brand palette (source of truth) */
  --brand-orange: #FF5F05;
  --brand-navy:   #102636;
  --brand-gray:   #C5C5C5;
  --black:        #000000;
  --white:        #FFFFFF;

  /* Semantic tokens — light theme (default) */
  --bg:          #F6F7F8;
  --surface:     var(--white);
  --surface-alt: #FBFBFC;
  --border:      #E2E6E9;
  --heading:     var(--brand-navy);
  --text:        #26333D;
  --text-muted:  #667580;
  --accent:      var(--brand-orange);
  /* Navy on orange is 4.7:1 (AA); white on orange is only 3.0:1. */
  --accent-ink:  var(--brand-navy);
  --shadow:      0 1px 2px rgba(16, 38, 54, 0.06);

  --radius: 10px;
  --maxw: 900px;
}

/* Dark theme: navy ground, brand-gray type (deliberately not pure white on navy). */
:root[data-theme="dark"] {
  --bg:          var(--brand-navy);
  --surface:     #16313F;
  --surface-alt: #143241;
  --border:      #24455A;
  --heading:     #E6E9EB;
  --text:        var(--brand-gray);
  --text-muted:  #8FA0AB;
  --accent:      var(--brand-orange);
  --accent-ink:  var(--brand-navy);
  --shadow:      0 1px 2px rgba(0, 0, 0, 0.3);
}

/* No stored choice + no JS: follow the OS. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:          var(--brand-navy);
    --surface:     #16313F;
    --surface-alt: #143241;
    --border:      #24455A;
    --heading:     #E6E9EB;
    --text:        var(--brand-gray);
    --text-muted:  #8FA0AB;
    --shadow:      0 1px 2px rgba(0, 0, 0, 0.3);
  }
}

/* ---- theme-swapped assets (logo + any inline icon mark) ---- */
.on-dark { display: none; }
.on-light { display: block; }
:root[data-theme="dark"] .on-light { display: none; }
:root[data-theme="dark"] .on-dark { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .on-light { display: none; }
  :root:not([data-theme="light"]) .on-dark { display: block; }
}

/* ---- base ---- */
* { box-sizing: border-box; }
/* An explicit background on html, not just body.
   body's background does propagate to the canvas when html is transparent, so
   this is redundant by spec — but iOS Safari has a long history of painting the
   canvas its own colour during overscroll and toolbar transitions, which shows
   as a band of white below the page on a dark theme. Reported once, not
   reproducible in desktop Chrome at any width (measured gap was 40px on mobile
   and 48px on desktop — i.e. just the footer margin, and not mobile-specific).
   Cheap, no downside, and closes the whole class. */
html { color-scheme: light dark; background: var(--bg); }
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  color: var(--text);
  background: var(--bg);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow-wrap: break-word;
}
h1, h2, h3 { color: var(--heading); letter-spacing: -0.02em; }
a { color: var(--accent); }
.muted { color: var(--text-muted); }
img { max-width: 100%; }

/* ---- header ---- */
/* Utility bar: slim contrasting strip above the main nav. Dark blue in light
   mode, light gray in dark mode — both palette tokens, inverted text so the two
   short links read clearly against each background. One compact line at every
   width (it is ~30px, not a third stacked nav). */
.utility-bar { background: var(--brand-navy); }
.utility-bar-inner {
  max-width: var(--maxw); margin: 0 auto; display: flex; align-items: center;
  gap: 1.4rem; padding: 0.3rem 1rem; font-size: 0.8rem; overflow-x: auto;
  -webkit-overflow-scrolling: touch; scrollbar-width: none;
}
.utility-bar-inner::-webkit-scrollbar { display: none; }
.utility-bar a { color: #fff; text-decoration: none; font-weight: 600;
  white-space: nowrap; letter-spacing: 0.01em; }
.utility-bar a:hover { color: var(--brand-orange); }
:root[data-theme="dark"] .utility-bar { background: var(--brand-gray); }
:root[data-theme="dark"] .utility-bar a { color: var(--brand-navy); }
:root[data-theme="dark"] .utility-bar a:hover { color: var(--brand-orange); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .utility-bar { background: var(--brand-gray); }
  :root:not([data-theme="light"]) .utility-bar a { color: var(--brand-navy); }
  :root:not([data-theme="light"]) .utility-bar a:hover { color: var(--brand-orange); }
}
@media (min-width: 640px) { .utility-bar-inner { padding: 0.3rem 1.5rem; } }

.site-header { background: var(--surface); border-bottom: 1px solid var(--border); }
/* Mobile: two rows via wrap — [logo | actions] then [search full-width].
   Desktop (>=960px) unwraps this into one row; see the media query. */
.header-inner {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 0.6rem 1rem;
  display: flex;
  align-items: center;
  gap: 0.5rem 0.75rem;
  flex-wrap: wrap;
}
.brand { display: inline-flex; align-items: center; text-decoration: none; min-width: 0; order: 1; }
/* 42px: the logo is the brand and it was reading as a footnote at 26px.
   Asset is 342x96 (3.56:1), so this renders ~150px wide and still leaves room
   for the actions on a 375px screen. Header padding is trimmed to compensate
   so the bar doesn't grow proportionally. */
.brand-logo { height: 42px; width: auto; }
/* margin-left:auto does the work `justify-content: space-between` used to —
   space-between would push the wrapped search row around too. */
.header-actions { display: flex; align-items: center; gap: 0.4rem; flex: none; order: 2; margin-left: auto; }
.theme-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface-alt);
  color: var(--text-muted);
  cursor: pointer;
  flex: none;
}
.theme-toggle:hover { color: var(--accent); border-color: var(--accent); }
.theme-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- social icons (inline SVG, currentColor -> follows theme) ---- */
.social { list-style: none; display: flex; align-items: center; gap: 0.35rem; margin: 0; padding: 0; }
.social a {
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--brand-navy);
  padding: 0.35rem;
  border-radius: 6px;
}
:root[data-theme="dark"] .social a { color: var(--brand-gray); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .social a { color: var(--brand-gray); }
}
.social a:hover { color: var(--accent); }
.social a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

main { max-width: var(--maxw); margin: 0 auto; padding: 2rem 1rem; }

/* ---- deals index ---- */
.hero-deals h1 { font-size: 1.65rem; margin: 0 0 0.35rem; }
.tagline { color: var(--text-muted); margin: 0 0 1.25rem; }

/* Chips scroll sideways on a phone rather than stacking into rows. */
.filters {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
  overflow-x: auto;
  flex-wrap: nowrap;
  padding-bottom: 0.3rem;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.filters::-webkit-scrollbar { display: none; }
.chip {
  flex: none;
  white-space: nowrap;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.4rem 0.9rem;
  font-size: 0.9rem;
  color: var(--text);
  text-decoration: none;
}
.chip:hover { border-color: var(--accent); color: var(--accent); }
.chip.is-active { background: var(--brand-navy); border-color: var(--brand-navy); color: var(--white); }
:root[data-theme="dark"] .chip.is-active {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink); font-weight: 600;
}

.deal-list {
  list-style: none; padding: 0; margin: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface);
  box-shadow: var(--shadow);
}
.deal + .deal { border-top: 1px solid var(--border); }

/* Mobile: image spans the left, body then price stacked to its right, so the
   price always gets its own line and can never collide with the title. */
.deal-link {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas:
    "img body"
    "img price";
  column-gap: 0.85rem;
  row-gap: 0.4rem;
  align-items: start;
  padding: 0.9rem 1rem;
  text-decoration: none;
  color: inherit;
}
.deal-link:hover { background: var(--surface-alt); }
.deal-img {
  grid-area: img;
  width: 64px; height: 64px;
  border-radius: 6px;
  object-fit: contain;
  background: var(--white);
  flex: none;
}
.deal-img-empty { background: var(--surface-alt); border: 1px solid var(--border); }
.deal-body { grid-area: body; min-width: 0; }
.deal-name {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  font-weight: 600;
  color: var(--heading);
  line-height: 1.35;
}
.deal-meta {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-width: 0;
  margin-top: 0.15rem;
  font-size: 0.85rem;
  color: var(--text-muted);
}
.deal-merchant { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.deal-price {
  grid-area: price;
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.price-now { font-size: 1.05rem; color: var(--heading); }
.price-was { color: var(--text-muted); font-size: 0.85rem; }
.pct { color: var(--accent); font-weight: 800; font-size: 0.9rem; }
.tag {
  flex: none;
  white-space: nowrap;
  display: inline-block;
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0 0.4rem;
  font-size: 0.72rem;
  line-height: 1.5;
}
.tag-atl { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); font-weight: 700; }

.empty {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 2rem 1.25rem;
  text-align: center;
}
.stats {
  list-style: none; padding: 0; margin: 2rem 0 0;
  display: flex; flex-wrap: wrap; gap: 1.25rem 2rem;
}
.stats li { font-size: 0.85rem; color: var(--text-muted); }
.stats strong { display: block; font-size: 1.35rem; color: var(--heading); }

/* ---- product page ---- */
.product-head { display: flex; flex-direction: column; gap: 1.25rem; }
.product-img img {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--white);
  /* Reserve a fixed square box BEFORE the image loads so there is zero layout
     shift, and object-fit: contain letterboxes any non-square merchant image
     inside it rather than distorting — we don't know the true aspect ratio, so
     a fixed box is the only CLS-proof option. The width/height attributes on
     the tag give older engines the same 1:1 hint. */
  width: 240px;
  aspect-ratio: 1 / 1;
  height: auto;
  object-fit: contain;
}
.product-meta { min-width: 0; }
.eyebrow {
  text-transform: uppercase; letter-spacing: 0.09em;
  font-size: 0.72rem; font-weight: 700;
  color: var(--accent); margin: 0 0 0.4rem;
}
.product-meta h1 { font-size: 1.35rem; margin: 0 0 0.85rem; line-height: 1.3; }
.best-price { margin: 0 0 1rem; font-size: 0.95rem; }
.best-price strong { font-size: 1.3rem; color: var(--accent); }
.specs { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 0.3rem 1rem; margin: 0; font-size: 0.88rem; }
.specs dt { color: var(--text-muted); }
.specs dd { margin: 0; color: var(--text); min-width: 0; overflow-wrap: anywhere; }

/* ---- shortlink (copy for social posts) ---- */
.shortlink { margin-top: 1.5rem; max-width: 26rem; }
.shortlink label { display: block; font-size: 0.78rem; color: var(--text); margin-bottom: 0.35rem; }
.shortlink-row { display: flex; gap: 0.5rem; }
.shortlink-row input {
  flex: 1; min-width: 0;
  font: inherit; font-size: 0.85rem;
  padding: 0.45rem 0.6rem;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
}
.shortlink-row input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.btn-copy { border: none; cursor: pointer; font-family: inherit; flex: none; }
.btn-copy.is-copied { background: var(--brand-navy); color: var(--white); }
:root[data-theme="dark"] .btn-copy.is-copied { background: var(--brand-gray); color: var(--brand-navy); }

.prices { margin-top: 2.5rem; }
.prices h2, .description h2 { font-size: 1.1rem; margin: 0 0 0.85rem; }

/* Mobile: the price table collapses to one card per retailer. */
/* ---- offer rows (product page price comparison) ----
   A list of links, not a table: the whole row is one tap target, which a <tr>
   cannot be. Card-shaped at every width — this never rendered as a table. */
.price-list {
  list-style: none; margin: 0; padding: 0;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); box-shadow: var(--shadow); overflow: hidden;
}
.price-list li + li { border-top: 1px solid var(--border); }
/* Column labels are meaningless when the row is a stacked card. */
.price-head { display: none; }
.offer-row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  align-items: center; gap: 0.15rem 0.75rem;
  padding: 0.85rem 1rem; min-height: 44px;   /* thumb-sized, per the mobile rule */
  text-decoration: none; color: var(--text);
}
.offer-row:hover { background: var(--surface-alt); }
.offer-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.offer-row.is-best { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.offer-row.is-best:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
/* Known out-of-stock: dim the whole row so an in-stock offer reads as the
   live choice. It stays a full tap target (the merchant may still be worth a
   look) — dimmed, not disabled. The price keeps its weight; only the row
   recedes. */
.offer-row.is-oos { opacity: 0.55; }
.offer-row.is-oos:hover { opacity: 0.8; }
/* min-width:0 so a long retailer name ellipsizes instead of shoving the CTA
   off the row — the CTA is nowrap and would win the space fight. */
.offer-main { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.offer-merchant {
  font-weight: 600; color: var(--heading);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.offer-price strong { font-size: 1.05rem; color: var(--heading); }
.offer-price s { margin-left: 0.4rem; font-size: 0.85rem; color: var(--text-muted); }
/* "with code XXX" beside a coupon-adjusted price, on the product page and the
   offer rows. Deliberately not muted-to-invisible: the price above it is only
   true with this code, so it reads as part of the price. inline-block +
   overflow-wrap so a long code wraps whole instead of ellipsizing to a
   half-code, and flex:none-safe (it never competes for space with the price). */
.coupon-note {
  display: inline-block; font-size: 0.78rem; color: var(--text);
  overflow-wrap: anywhere;
}
.coupon-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.8rem; letter-spacing: 0.03em; color: var(--heading);
  background: var(--surface-2, rgba(127, 127, 127, 0.14));
  border-radius: 4px; padding: 0.05rem 0.3rem;
}
.best-price .coupon-note { margin-left: 0.15rem; }
.best-price .coupon-was { font-size: 0.85rem; }
.offer-price .coupon-note { display: block; margin-top: 0.1rem; }
.offer-stock { font-size: 0.8rem; color: var(--text-muted); }
.offer-cta { flex: none; align-self: center; }
/* Shipping disclosure spans the full row width, under the merchant/price/stock
   (grid-column:1/-1 works in both the 2-col mobile grid and the 4-col desktop
   one). Muted and small — it's context, not a price. */
.offer-ship {
  grid-column: 1 / -1; margin-top: 0.25rem;
  font-size: 0.78rem; line-height: 1.35; color: var(--text-muted);
}
.offer-ship-fee { font-weight: 600; color: var(--text); }
.offer-ship-ver { opacity: 0.75; }
.offer-ship-detail { display: block; opacity: 0.85; }
/* Per-offer freshness, full-width and very muted — reassurance, not noise. */
.offer-updated { grid-column: 1 / -1; margin-top: 0.15rem; font-size: 0.72rem; color: var(--text-muted); opacity: 0.85; }

/* --- price history chart ------------------------------------------------- */
.price-history { margin-top: 2rem; }
.ph-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.ph-head h2 { margin: 0; }
.ph-ranges { display: flex; gap: 0.3rem; }
.ph-range {
  border: 1px solid var(--border); background: var(--surface); color: var(--text-muted);
  border-radius: 999px; padding: 0.2rem 0.6rem; font-size: 0.78rem; cursor: pointer;
}
.ph-range.is-on { background: var(--brand-navy); border-color: var(--brand-navy); color: var(--white); }
:root[data-theme="dark"] .ph-range.is-on { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.ph-chart { position: relative; margin: 0.75rem 0 0; width: 100%; touch-action: pan-y; }
.ph-svg { display: block; overflow: visible; }
.ph-grid { stroke: var(--border); stroke-width: 1; }
.ph-axis { fill: var(--text-muted); font-size: 10px; }
/* best-available-price: the bold primary line */
.ph-best { stroke: var(--accent); stroke-width: 3; fill: none; stroke-linecap: round; stroke-linejoin: round; }
.ph-best-dot { }
/* min/max spread band, sits behind the lines */
.ph-band { fill: color-mix(in srgb, var(--accent) 12%, transparent); stroke: none; }
/* secondary per-merchant lines, thinner; dashed where out of stock */
.ph-line { fill: none; stroke-linecap: round; }
.ph-merchant { stroke-width: 1.5; opacity: 0.9; }
.ph-line.is-oos { stroke-dasharray: 3 3; opacity: 0.4; }
.ph-dot.is-oos { opacity: 0.35; }
.ph-guide { stroke: var(--text-muted); stroke-width: 1; stroke-dasharray: 2 3; pointer-events: none; }
.ph-legend { list-style: none; margin: 0.5rem 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0.35rem 0.9rem; }
.ph-legend-item { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.82rem; color: var(--text-muted); }
.ph-legend-best { font-weight: 600; color: var(--heading); }
.ph-legend-toggle { background: none; border: 0; padding: 0.15rem 0; font: inherit; color: var(--text-muted); cursor: pointer; }
.ph-legend-toggle.is-off { opacity: 0.45; }
.ph-legend-toggle.is-off .ph-swatch { box-shadow: inset 0 0 0 2px currentColor; background: transparent !important; }
.ph-swatch { width: 11px; height: 11px; border-radius: 3px; display: inline-block; flex: none; }
/* "Dashed = out of stock" caption, so the graph reads without a hover. */
.ph-caption { display: flex; align-items: center; gap: 0.4rem; margin: 0.35rem 0 0; font-size: 0.76rem; color: var(--text-muted); }
.ph-dash-swatch { width: 18px; height: 0; border-top: 2px dashed currentColor; display: inline-block; flex: none; opacity: 0.7; }
.ph-stats { display: flex; flex-wrap: wrap; gap: 0.4rem 1.25rem; margin: 0.75rem 0 0; font-size: 0.9rem; }
.ph-stats b { font-weight: 600; color: var(--heading); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; margin-right: 0.3rem; }
/* Tooltip: absolutely positioned inside the (relative) chart. */
.ph-tip {
  position: absolute; top: 6px; z-index: 3; pointer-events: none;
  background: var(--surface); border: 1px solid var(--border); border-radius: 8px;
  padding: 0.4rem 0.55rem; font-size: 0.8rem; box-shadow: 0 4px 14px rgba(0,0,0,0.18);
  min-width: 120px; max-width: 260px;
}
.ph-tip-date { font-weight: 600; color: var(--heading); margin-bottom: 0.25rem; }
.ph-tip-row { display: flex; align-items: center; gap: 0.35rem; }
.ph-tip-name { color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ph-tip-p { margin-left: auto; font-weight: 600; white-space: nowrap; }
.ph-tip-oos { color: var(--text-muted); font-weight: 400; font-size: 0.72rem; }

.best-price-link { text-decoration: none; color: inherit; }
.best-price-link:hover { color: var(--accent); }
.best-cta { margin: 0.6rem 0 0; }
.btn-go { display: inline-flex; align-items: center; min-height: 44px; padding: 0 1.1rem; }

.btn {
  display: inline-block;
  background: var(--accent); color: var(--accent-ink);
  text-decoration: none;
  padding: 0.45rem 0.9rem;
  border-radius: 6px;
  font-size: 0.85rem; font-weight: 700;
  white-space: nowrap;
}
.btn:hover { filter: brightness(1.06); }
.disclosure { font-size: 0.8rem; margin-top: 0.9rem; }
.description { margin-top: 2rem; }

/* ---- footer ---- */
.site-footer {
  max-width: var(--maxw);
  margin: 2.5rem auto;
  padding: 1.5rem 1rem;
  color: var(--text-muted);
  font-size: 0.85rem;
  border-top: 1px solid var(--border);
}
.footer-inner {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 1rem;
}
.footer-follow { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.follow-text { font-weight: 600; color: var(--text); }
.footer-links { display: flex; gap: 1.25rem; }
.footer-links a { color: var(--text-muted); text-decoration: none; }
.footer-links a:hover { color: var(--accent); }
.footer-legal { margin: 1.25rem 0 0; line-height: 1.6; }

/* ---- prose (privacy / terms) ---- */
.prose { max-width: 42rem; }
.prose h1 { font-size: 1.6rem; margin: 0 0 0.5rem; }
.prose h2 { font-size: 1.05rem; margin: 1.75rem 0 0.5rem; }
.prose p { margin: 0 0 1rem; }

/* =====================================================================
   Desktop enhancement
   ===================================================================== */
@media (min-width: 640px) {
  .header-inner { padding: 0.75rem 1.5rem; }
  .brand-logo { height: 48px; }
  .header-actions { gap: 0.85rem; }
  .social { gap: 0.6rem; }
  main { padding: 3rem 1.5rem; }

  .hero-deals h1 { font-size: 2.15rem; margin-bottom: 0.4rem; }
  .tagline { margin-bottom: 1.75rem; }
  .filters { margin-bottom: 2rem; }

  /* One row: image | body | price aligned right. */
  .deal-link {
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "img body price";
    align-items: center;
    column-gap: 1.1rem;
    padding: 1rem 1.15rem;
  }
  .deal-img { width: 72px; height: 72px; }
  .deal-name { -webkit-line-clamp: 1; line-clamp: 1; }
  .deal-price { justify-content: flex-end; text-align: right; }

  .stats { gap: 2.5rem; margin-top: 2.5rem; }
  .stats strong { font-size: 1.5rem; }

  .product-head { flex-direction: row; gap: 2rem; align-items: flex-start; }
  .product-img { flex: none; }
  .product-meta { flex: 1; min-width: 260px; }
  .product-meta h1 { font-size: 1.65rem; }
  .best-price strong { font-size: 1.4rem; }
  .prices { margin-top: 3rem; }

  /* Columns from here up: same visual grammar as the table this replaced —
     stacked cards on phones, aligned columns on desktop. Grid instead of <td>,
     so the row can stay a single link. */
  .price-head {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 10rem 7rem auto;
    align-items: center; gap: 0.75rem;
    padding: 0.6rem 1rem;
    font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em;
    color: var(--white); background: var(--brand-navy);
  }
  :root[data-theme="dark"] .price-head { color: var(--brand-gray); background: var(--surface-alt); }
  .offer-row {
    grid-template-columns: minmax(0, 1fr) 10rem 7rem auto;
    gap: 0.75rem;
  }
  .offer-main { display: contents; }  /* let the three spans become grid cells */
  .offer-stock { font-size: inherit; }
  .offer-cta { justify-self: end; }

  .footer-inner { flex-direction: row; align-items: center; justify-content: space-between; }
  .site-footer { padding: 1.75rem 1.5rem; margin: 3rem auto; }
  .prose h1 { font-size: 1.9rem; }
  .prose h2 { font-size: 1.1rem; margin-top: 2rem; }
}

/* ==========================================================================
   Browse: search, category nav, product cards, pagination.
   Mobile-first — base rules are the 375px layout, min-width queries add desktop.
   ========================================================================== */

.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* --- header search ------------------------------------------------------ */
/* Its own full-width row on phones: sharing the logo's row would leave the
   input a stub, and a search box too small to read is a search box nobody uses. */
/* order:3 + 100% basis = its own row under the logo on phones. */
.header-search { order: 3; flex: 1 0 100%; min-width: 0; padding: 0 0 0.15rem; }
.searchbox { position: relative; display: flex; align-items: center; }
.searchbox input {
  /* min-width:0 lets the input shrink inside the flex row instead of forcing
     the row wider than the viewport. */
  flex: 1; min-width: 0; width: 100%;
  font: inherit; font-size: 1rem;  /* <16px makes iOS zoom on focus */
  padding: 0.6rem 2.6rem 0.6rem 0.85rem;
  min-height: 44px;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface-alt); color: var(--text);
  -webkit-appearance: none; appearance: none;
}
.searchbox input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.searchbox input::-webkit-search-cancel-button { -webkit-appearance: none; }
.searchbox-go {
  position: absolute; right: 0.3rem;
  display: grid; place-items: center;
  width: 38px; height: 38px; flex: none;
  border: none; border-radius: 50%; cursor: pointer;
  background: transparent; color: var(--text-muted);
}
.searchbox-go:hover { color: var(--accent); }

/* typeahead dropdown */
.ta-list {
  position: absolute; top: calc(100% + 0.35rem); left: 0; right: 0; z-index: 40;
  margin: 0; padding: 0.25rem; list-style: none;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow);
  max-height: 66vh; overflow-y: auto;
}
.ta-list[hidden] { display: none; }
.ta-list a {
  display: flex; align-items: center; gap: 0.6rem;
  padding: 0.45rem 0.5rem; border-radius: 8px;
  text-decoration: none; color: var(--text);
}
.ta-list li[aria-selected="true"] a, .ta-list a:hover { background: var(--surface-alt); }
.ta-img {
  width: 40px; height: 40px; flex: none;
  object-fit: contain; background: #fff; border-radius: 6px;
}
.ta-img-empty { background: var(--surface-alt); border: 1px solid var(--border); }
/* min-width:0 is what lets the name ellipsize instead of stretching the row. */
.ta-body { display: flex; flex-direction: column; min-width: 0; gap: 0.1rem; }
.ta-name {
  font-size: 0.85rem; line-height: 1.25;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ta-meta { display: flex; align-items: center; gap: 0.4rem; font-size: 0.78rem; color: var(--text-muted); }
.ta-badge {
  flex: none; white-space: nowrap;
  background: var(--accent); color: var(--accent-ink);
  border-radius: 3px; padding: 0 0.3rem; font-size: 0.68rem; font-weight: 700;
}
.ta-offers { flex: none; white-space: nowrap; }

/* --- category nav ------------------------------------------------------- */
/* Scrollable strip, not a hamburger: 11 destinations swipe fine and stay
   visible instead of hiding behind a tap. */
.catnav { border-top: 1px solid var(--border); background: var(--surface); }
.catnav-list {
  display: flex; gap: 0.15rem; margin: 0; padding: 0 0.5rem; list-style: none;
  overflow-x: auto; flex-wrap: nowrap;
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.catnav-list::-webkit-scrollbar { display: none; }
.catnav-link {
  display: block; flex: none; white-space: nowrap;
  padding: 0.7rem 0.6rem;
  font-size: 0.88rem; color: var(--text); text-decoration: none;
  border-bottom: 2px solid transparent;
}
.catnav-link:hover { color: var(--accent); }
.catnav-link.is-active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }

/* --- chips (filters / refinements) -------------------------------------- */
/* Wraps to rows, never scrolls sideways: a horizontal-scroll chip strip hides
   options off-screen with no affordance, and the standing rule is no horizontal
   overflow, ever. The overflow is managed by collapsing to 5 + "N more". */
.chips {
  display: flex; gap: 0.5rem; margin-bottom: 1rem;
  flex-wrap: wrap; align-items: center;
}
/* Collapsed by default; the checkbox hack reveals them in place. */
.chip-extra { display: none; }
.chips-more-cb:checked ~ .chip-extra { display: inline-flex; }
.chips-more-cb:checked ~ .chip-more { display: none; }
/* The "+N more" pill: styled like a chip but it's a toggle, so give it a
   pointer and a faint dashed edge to read as an action, not a destination. */
.chip-more { cursor: pointer; border-style: dashed; color: var(--text-muted); }
.chip-more:hover { border-color: var(--accent); color: var(--accent); }
.chip-n { color: var(--text-muted); font-size: 0.78em; margin-left: 0.15rem; }

/* Filter row: category chips grow, the in-stock toggle sits alongside and wraps
   under them on narrow phones rather than squeezing the chips. */
/* One consolidated controls row (in-stock toggle, sort, view) under the page
   description, above the pill rows. Stock + sort read left; the view toggle sits
   at the right edge. The three keep their behavior + query-string persistence —
   this is placement only. */
.controls-row { display: flex; align-items: center; flex-wrap: wrap;
  gap: 0.5rem 0.85rem; margin: 0.6rem 0 0.9rem; }
.controls-row .sortbar { margin-left: 0; }        /* group with stock, not pushed right */
.controls-row .viewtoggle { margin-left: auto; }  /* view toggle to the right edge */
/* Narrow widths: shorten "In stock only" -> "In stock" to make room. Drop the
   word, never the control (CLAUDE.md: never nowrap a control into a sliver). */
@media (max-width: 430px) {
  .stock-only-word { display: none; }
  /* Tighten so the (now three-button) view toggle keeps its place on the row
     instead of wrapping to a second line. Smaller gap + slightly narrower toggle
     buttons; 33px stays a comfortable tap target at 34px tall. */
  .controls-row { gap: 0.45rem 0.5rem; }
  .controls-row .viewtoggle a { min-width: 33px; }
}
/* 375px can't fit all three even with the shortened stock label — the sort
   select is sized to its widest option ("Price: low to high"). Per the agreed
   fallback (collapse sort before anything disappears): drop the "Sort" word and
   compact the select so it shows the current value (truncating in the closed
   state; the dropdown still lists every option in full). The control stays fully
   usable — this only trims its resting footprint. */
@media (max-width: 400px) {
  .controls-row .sortbar-label { display: none; }
  .controls-row .sortbar-select { max-width: 104px; }
}

.filter-row { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem 0.9rem; margin-bottom: 1rem; }
/* Sort control: compact label + native select, sits in the filter row and wraps
   with it on a phone. margin-left:auto pushes it to the row's end on wider
   screens without breaking the 375px wrap. */
.sortbar { display: inline-flex; align-items: center; gap: 0.35rem; margin-left: auto; }
.sortbar-label { font-size: 0.8rem; color: var(--text-muted); }
.sortbar-select {
  font-size: 0.82rem; padding: 0.28rem 0.5rem; border-radius: 6px;
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
  max-width: 12rem;
}
.filter-row .chips { flex: 1 1 240px; margin-bottom: 0; }
/* iOS-style switch, but a real link so it works with no JS. */
.stock-toggle {
  display: inline-flex; align-items: center; gap: 0.5rem; flex: none;
  min-height: 36px; padding: 0.2rem 0.1rem; white-space: nowrap;
  font-size: 0.85rem; color: var(--text); text-decoration: none; cursor: pointer;
}
.stock-toggle:hover { color: var(--accent); }
.stock-toggle-track {
  flex: none; position: relative; width: 34px; height: 20px; border-radius: 999px;
  background: var(--border); transition: background 0.15s ease;
}
.stock-toggle-knob {
  position: absolute; top: 2px; left: 2px; width: 16px; height: 16px;
  border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,0.35);
  transition: transform 0.15s ease;
}
.stock-toggle.is-on .stock-toggle-track { background: var(--accent); }
.stock-toggle.is-on .stock-toggle-knob { transform: translateX(14px); }
.chip.is-active .chip-n { color: inherit; opacity: 0.75; }
.chips-sm .chip { padding: 0.25rem 0.7rem; font-size: 0.8rem; }
.subfilter { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 1.25rem; }
.subfilter-label {
  flex: none; white-space: nowrap;
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-muted);
}
.subfilter .chips { margin-bottom: 0; min-width: 0; }

/* --- breadcrumbs + page head -------------------------------------------- */
.crumbs { display: flex; gap: 0.4rem; align-items: center; font-size: 0.8rem;
          color: var(--text-muted); margin-bottom: 0.75rem; flex-wrap: wrap; }
.crumbs a { color: var(--text-muted); }
.crumbs a:hover { color: var(--accent); }
.cat-head { margin-bottom: 1rem; }
.cat-head h1 { font-size: 1.5rem; margin: 0 0 0.25rem; }

/* --- product grid ------------------------------------------------------- */
/* minmax(0,1fr), not 1fr: a bare 1fr floors at min-content, so one long
   unbroken title widens the column and pushes the page into horizontal scroll. */
.pgrid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.6rem; list-style: none; padding: 0; margin: 0 0 1.25rem;
}
.pcard {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); overflow: hidden;
}
.pcard-link { display: flex; flex-direction: column; height: 100%; text-decoration: none; color: var(--text); }
.pcard:hover { border-color: var(--accent); }
.pcard-media { position: relative; display: block; background: #fff; }
.pcard-img {
  display: block; width: 100%; aspect-ratio: 1 / 1;
  object-fit: contain; padding: 0.4rem;
}
.pcard-img-empty { background: var(--surface-alt); }
.pcard-badge {
  position: absolute; top: 0.35rem; left: 0.35rem;
  background: var(--accent); color: var(--accent-ink);
  border-radius: 3px; padding: 0.1rem 0.35rem;
  font-size: 0.68rem; font-weight: 800; white-space: nowrap;
}
/* "Just got cheaper" drop badge: green + down-arrow, top-RIGHT so it never
   collides with the orange retail-discount badge (top-left). Sits over the
   always-white media, so the green reads in both themes. */
.pcard-drop {
  position: absolute; top: 0.35rem; right: 0.35rem;
  background: #1a8f4c; color: #fff;
  border-radius: 3px; padding: 0.1rem 0.35rem;
  font-size: 0.68rem; font-weight: 800; white-space: nowrap;
}
.pcard-body { display: flex; flex-direction: column; gap: 0.2rem; padding: 0.5rem 0.6rem 0.6rem; flex: 1; min-width: 0; }
.pcard-brand {
  font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Two-line clamp: feed titles run to 115 characters and would otherwise set
   the card's height from the longest one in the row. */
.pcard-name {
  font-size: 0.82rem; line-height: 1.3; color: var(--heading);
  /* MOBILE (base, mobile-first): 5 lines. Gun titles carry the distinguishing
     specs at the TAIL — barrel length, capacity, finish — and a phone card is
     narrow (~2 cols at 375px, ~18 chars/line), so 3 lines cut exactly what tells
     a Prodigy or a Performance-Center SKU apart from its siblings. 5 lines holds
     the p90 (107 chars). Desktop drops back to 3 in the min-width block below,
     where cards are wider and the grid denser. Ellipsis past the clamp is the
     backstop; title= carries the full string, and list view is the full-title
     escape hatch. min-height reserves the 5-line box so a short title leaves
     clean whitespace instead of ragged, uneven card internals across a row. */
  display: -webkit-box; -webkit-line-clamp: 5; line-clamp: 5;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: calc(5 * 1.3em);
}
.pcard-foot {
  display: flex; align-items: baseline; gap: 0.4rem; margin-top: auto;
  padding-top: 0.35rem; min-width: 0; flex-wrap: wrap;
}
.pcard-price { font-weight: 800; color: var(--heading); font-size: 0.95rem; flex: none; }
.pcard-price-none { font-weight: 600; color: var(--text-muted); font-size: 0.82rem; }
/* Strikethrough "was": present only when the shown price is a real drop. flex:
   none so it can't squeeze the price; it wraps under on a very narrow card. */
.pcard-was { flex: none; font-size: 0.78rem; color: var(--text-muted); text-decoration: line-through; }
/* Price per round: the ammo comparison metric, its own line under the price. */
.pcard-ppr { font-size: 0.75rem; font-weight: 700; color: var(--accent); margin-top: 0.1rem; }
/* "with code XXX" on a card. Its own line under the price, never inline with
   it: this label is what makes the number above reproducible at checkout, so it
   must not be allowed to ellipsize away or shove the price. Wraps rather than
   truncates for the same reason — a half-printed code is a wrong code. */
.pcard-coupon {
  display: block; margin-top: 0.15rem; font-size: 0.72rem; line-height: 1.3;
  color: var(--text-muted); overflow-wrap: anywhere;
}
.pcard-coupon b { color: var(--heading); letter-spacing: 0.02em; }
/* Editorial flag note ("why") on the picks/hot-deals cards. Own line, small,
   accent-tinted so it reads as an editorial voice, not product spec. */
.pcard-flagnote, .prow-flagnote {
  display: block; margin-top: 0.15rem; font-size: 0.72rem; line-height: 1.35;
  color: var(--accent); font-style: italic; overflow-wrap: anywhere;
}
/* Product-page "cheaper this week" note — green, matches the card drop badge. */
.drop-note { color: #1a8f4c; font-weight: 700; font-size: 0.9rem; margin: 0.15rem 0 0; }
/* Retailer line under the price. Ellipsizes — never wins space from anything. */
.pcard-meta {
  min-width: 0; margin-top: 0.15rem; font-size: 0.72rem; color: var(--text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pcard-merchant { overflow: hidden; text-overflow: ellipsis; }
.pcard-more { color: var(--text-muted); opacity: 0.8; }
.pcard-oos { color: #b3261e; font-weight: 600; }
:root[data-theme="dark"] .pcard-oos { color: #ff8a80; }

/* --- card sections ------------------------------------------------------ */
.csection { margin: 2rem 0; }
.csection-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; }
.csection-head h2 { font-size: 1.15rem; margin: 0 0 0.15rem; }
.csection-more { font-size: 0.82rem; flex: none; white-space: nowrap; }
.csection-blurb { margin: 0 0 0.75rem; font-size: 0.85rem; color: var(--text-muted); }

/* --- categories index --------------------------------------------------- */
.catgrid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.6rem; list-style: none; padding: 0; margin: 0;
}
.cattile {
  display: flex; flex-direction: column; gap: 0.2rem;
  padding: 0.9rem; text-decoration: none;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
}
.cattile:hover { border-color: var(--accent); }
.cattile-name { font-weight: 700; color: var(--heading); }
.cattile-n { font-size: 0.78rem; color: var(--text-muted); }

/* --- pagination --------------------------------------------------------- */
.pager { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin: 1.5rem 0; }
.pager-btn {
  flex: none; white-space: nowrap;
  display: inline-flex; align-items: center; min-height: 44px; padding: 0 0.9rem;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface); color: var(--text); text-decoration: none; font-size: 0.85rem;
}
.pager-btn:hover { border-color: var(--accent); color: var(--accent); }
.pager-btn.is-off { opacity: 0.35; }

/* Load-more (progressive enhancement over .pager — see components/nav.html).
   Mobile-first: a full-width 48px target on a phone, which is where this
   control actually gets used, then capped and centred once there's room. */
.loadmore-wrap { display: flex; justify-content: center; margin: 1.5rem 0; }
.btn-wide {
  width: 100%; min-height: 48px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; cursor: pointer; font-family: inherit;
}
.btn-wide[disabled] { opacity: 0.6; cursor: default; }
.loadmore-end { text-align: center; margin: 1.5rem 0; }
@media (min-width: 640px) {
  .btn-wide { width: auto; min-width: 16rem; }
}
.pager-at { font-size: 0.8rem; color: var(--text-muted); text-align: center; min-width: 0; }

/* --- search ------------------------------------------------------------- */
.did-you-mean {
  background: var(--surface-alt); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 0.6rem 0.8rem;
  font-size: 0.88rem; margin: 0 0 1rem;
}

/* --- desktop ------------------------------------------------------------ */
@media (min-width: 640px) {
  .header-search { padding: 0 0 0.2rem; }
  .searchbox { max-width: 620px; margin: 0 auto; }
  .catnav-list { padding: 0 1rem; justify-content: center; }
  .pgrid, .catgrid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.75rem; }
  /* Desktop cards are wider and the grid is denser — 3 lines holds the median
     here, and a taller reserved box would waste vertical space per the original
     measurement. Back to 3, with the reserved min-height matched to it. */
  .pcard-name {
    font-size: 0.86rem;
    -webkit-line-clamp: 3; line-clamp: 3;
    min-height: calc(3 * 1.3em);
  }
  .cat-head h1 { font-size: 1.9rem; }
}
@media (min-width: 960px) {
  .pgrid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .catgrid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* ==========================================================================
   Desktop header: ONE row — logo | search | actions.
   960px, not 640: the row needs ~150-215px of logo + a search box wide enough
   to be worth having (max 560) + ~120px of actions. Below 960 that math forces
   the search into a stub, so tablets keep the phone's two-row header.
   ========================================================================== */
@media (min-width: 960px) {
  .header-inner {
    flex-wrap: nowrap;
    gap: 1.5rem;
    padding-top: 0.7rem;
    padding-bottom: 0.7rem;
  }
  .brand-logo { height: 56px; }
  /* order:2 slots search between the brand and the actions; `margin: 0 auto`
     inside a flex:1 track is what centres it rather than left-aligning it
     against the logo. */
  .header-search {
    order: 2;
    flex: 1 1 auto;
    max-width: 560px;
    margin: 0 auto;
    padding: 0;
  }
  .header-actions { order: 3; margin-left: 0; }
  /* The search row is gone, so the nav sits directly under a taller bar. */
  .catnav-list { padding: 0 1.5rem; }
}

/* ---- LIST VIEW ---------------------------------------------------------- */
/* One product per row, full width. Trades image size for a complete title. */
.plist { list-style: none; padding: 0; margin: 0 0 1.25rem; display: flex;
  flex-direction: column; gap: 0.4rem; }
.prow { background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); overflow: hidden; }
.prow-link { display: flex; gap: 0.7rem; align-items: flex-start;
  padding: 0.5rem 0.6rem; text-decoration: none; color: var(--text); }
/* Fixed 80px box with an explicit aspect ratio: the row's height is decided
   before the image loads, so a lazy image can never shift the layout. */
.prow-media { flex: none; width: 80px; }
.prow-img { display: block; width: 80px; height: 80px; aspect-ratio: 1 / 1;
  object-fit: contain; background: #fff; border-radius: 4px; }
.prow-img-empty { background: var(--surface-alt); }
/* min-width:0 is what lets the title wrap instead of forcing the row wide —
   without it the flex child refuses to shrink and the row overflows. */
.prow-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 0.15rem; }
.prow-brand { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--accent); font-weight: 700; }
/* NO clamp, NO ellipsis — the whole point of this view. */
.prow-name { font-size: 0.88rem; line-height: 1.35; color: var(--heading);
  overflow-wrap: anywhere; }
.prow-meta { display: flex; flex-wrap: wrap; gap: 0.15rem 0.6rem; align-items: baseline;
  font-size: 0.72rem; color: var(--text-muted); }
.prow-ppr { font-weight: 700; color: var(--accent); }
.prow-drop { color: #1a8f4c; font-weight: 700; }
.prow-oos { color: var(--text-muted); font-weight: 600; }
.prow-price { flex: none; display: flex; flex-direction: column; align-items: flex-end;
  gap: 0.1rem; text-align: right; min-width: 0; }
.prow-now { font-weight: 800; color: var(--heading); font-size: 1rem; white-space: nowrap; }
.prow-none { font-weight: 600; color: var(--text-muted); font-size: 0.82rem; }
.prow-was { font-size: 0.75rem; color: var(--text-muted); text-decoration: line-through; }
.prow-off { font-size: 0.68rem; font-weight: 800; background: var(--accent);
  color: var(--accent-ink); border-radius: 3px; padding: 0.05rem 0.3rem; white-space: nowrap; }
.prow-coupon { font-size: 0.68rem; color: var(--text-muted); overflow-wrap: anywhere; }
.prow-coupon b { color: var(--heading); }
/* OOS: dimmed in BOTH views, and already sorted last by _order_for. */
.prow.is-oos { opacity: 0.55; }
.prow.is-oos:hover { opacity: 0.8; }
.pcard.is-oos { opacity: 0.6; }
.pcard.is-oos:hover { opacity: 0.85; }

/* ---- view toggle (grid | list) ------------------------------------------ */
.viewtoggle { display: inline-flex; gap: 0; border: 1px solid var(--border);
  border-radius: 6px; overflow: hidden; }
.viewtoggle a { display: inline-flex; align-items: center; justify-content: center;
  min-width: 38px; min-height: 34px; color: var(--text-muted); text-decoration: none; }
.viewtoggle a + a { border-left: 1px solid var(--border); }
.viewtoggle a:hover { color: var(--accent); }
.viewtoggle a.is-on { background: var(--accent); color: var(--accent-ink); }
.viewtoggle svg { display: block; }

/* Two DISTINCT desktop list densities, ≥1000px:
   - lv-a (compact): TWO rows per line. A single column of 98px rows loses to a
     4-up grid on a wide screen (9 vs 16 per 900px); shrinking the thumb to win
     would defeat the point, so the compact list runs two columns instead — the
     horizontal space between a title and its price was dead anyway. Beats the
     grid on count AND still shows every title in full.
   - lv-b (full-width): ONE row per line, spanning the content column —
     thumbnail left, full title through the middle, price/badges/merchant right.
     This is the original single-row list spec. It MUST NOT inherit the 2-col
     grid, which is the bug that made buttons 2 and 3 look identical: the grid
     rule below is scoped to lv-a so the full-width mode stays one-per-row. */
@media (min-width: 1000px) {
  .plist.lv-a { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.4rem 0.6rem; }
  .plist.lv-b { display: block; }
  .plist.lv-b .prow + .prow { margin-top: 0.35rem; }
}
/* Phones: let the price wrap UNDER the main block instead of squeezing the
   title into a narrow column. Wider title = fewer wrapped lines = shorter rows,
   which is what makes list viable as the mobile default. */
@media (max-width: 639px) {
  .prow-link { flex-wrap: wrap; }
  .prow-main { flex: 1 1 60%; }
  .prow-price { flex: 1 1 100%; flex-direction: row; align-items: baseline;
    justify-content: flex-start; gap: 0.45rem; padding-left: calc(80px + 0.7rem); }
  .prow-coupon { flex-basis: 100%; padding-left: calc(80px + 0.7rem); }
}

/* MOBILE LIST-VIEW VARIANTS (?lv=a|b) — two candidate fixes for the too-small
   thumbnail, both built from the SAME product_row markup so the choice is pure
   CSS. Mobile-only (this max-width block); desktop list is the 2-col grid above,
   untouched. object-fit: contain + a reserved frame in both, so a 4.6:1 rifle
   and a 1:1 pistol both present sensibly. */
@media (max-width: 639px) {
  /* A — 128px thumb left, full title/price right. Keeps list's density edge.
     flex-basis 0 (NOT auto): with flex-wrap on, a basis of `auto` = the long
     title's content width, which exceeds the row so the title wraps to its own
     line before flex-shrink can apply. Basis 0 gives it a zero hypothetical
     size, so it fits beside the thumb and grows to fill the remaining width. */
  .plist.lv-a .prow-media { width: 128px; }
  .plist.lv-a .prow-img { width: 128px; height: 110px; aspect-ratio: auto;
    object-fit: contain; background: #fff; }
  .plist.lv-a .prow-main { flex: 1 1 0; }
  .plist.lv-a .prow-price { padding-left: calc(128px + 0.7rem); }
  .plist.lv-a .prow-coupon { padding-left: calc(128px + 0.7rem); }

  /* B — full-width image above, info below. A large single-column card. */
  .plist.lv-b .prow-link { flex-direction: column; flex-wrap: nowrap; gap: 0.5rem; }
  .plist.lv-b .prow-media { width: 100%; }
  .plist.lv-b .prow-img { width: 100%; height: auto; aspect-ratio: 16 / 10;
    object-fit: contain; background: #fff; }
  .plist.lv-b .prow-main { flex: none; width: 100%; }
  .plist.lv-b .prow-price { flex: none; width: 100%; padding-left: 0; }
  .plist.lv-b .prow-coupon { padding-left: 0; }
}

/* Final density trim: tighter row padding/gap (grid gap is lv-a's alone) so two
   columns clear the grid outright rather than tying it. Row padding/title size
   apply to both list densities. */
@media (min-width: 1000px) {
  .plist.lv-a { gap: 0.3rem 0.5rem; }
  .prow-link { padding: 0.4rem 0.5rem; gap: 0.6rem; }
  .prow-name { font-size: 0.84rem; line-height: 1.3; }
}
/* Phones: a 64px thumb gives the title ~16px more per line, which is the
   difference between a 3-line and a 2-line wrap on a median title. */
@media (max-width: 639px) {
  .prow-media, .prow-img { width: 64px; }
  .prow-img { height: 64px; }
  .prow-price, .prow-coupon { padding-left: calc(64px + 0.7rem); }
}
