/* FlowAds v2 — shared template stylesheet.
 * Phase D deliverable 4 (revised Phase D2, James's visual-gate feedback
 * 2026-07-02): ONE shared CSS approach, loaded/emitted once per page by
 * Phase E/F, all rules scoped under the .fa2- prefix. Mobile-first: base
 * rules are the single-column/small-screen layout, min-width media queries
 * progressively enhance. No JS required for any of it (C-1).
 *
 * ============================================================================
 * THE THREE-LAYER STYLING ARCHITECTURE (D2 orchestrator decision — James's
 * #1 cross-cutting demand: nothing visual hardcoded)
 * ============================================================================
 *   1. Base structure — this file's SELECTORS. Layout/mechanics only
 *      (flex/grid direction, aspect-ratio, image dimensions). No color, no
 *      font-family, no font-size, no radius, no spacing literal appears
 *      inside a selector body below the :root block — every one of those
 *      is a var(--fa2-*) reference. Enforced by grep test (see
 *      tests/templates-config.test.js): zero hex colors / px font-sizes
 *      outside :root.
 *   2. Theme variables — the :root block immediately below ("EDIT ME").
 *      Every color/font-family/font-size/radius/spacing knob for every
 *      template lives here as one flat, documented list.
 *   3. Per-site / per-template / per-brand override CSS from the DB (C-2) —
 *      NOT in this file. v2/templates/index.js's buildPageCss() takes raw
 *      CSS strings the render layer reads from site_config
 *      (template_css_override, template_css_override:<templateId>,
 *      brand_css_override:<brand_slug>) and emits them, sanitized, AFTER
 *      this stylesheet in the same <style> block — they win by ordinary
 *      cascade/source-order just by redeclaring a --fa2-* variable, no
 *      !important required. See buildPageCss()'s doc comment in index.js
 *      for the exact wiring contract.
 * ============================================================================
 */

:root {
  /* ==========================================================================
   * EDIT ME — FlowAds v2 template theme variables.
   * This is the ONE place every template's default look is defined. A
   * site-wide, per-template, or per-brand override (site_config, layer 3
   * above) re-declares any of these under a more specific/later selector —
   * nothing here needs to change to retheme a site.
   * ========================================================================== */

  /* ---- Typography -------------------------------------------------------- */
  --fa2-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --fa2-line-height: 1.45;
  --fa2-title-font-size: 0.98rem;       /* grid/stacked/vstack item titles */
  --fa2-title-font-size-lg: 1.28rem;    /* hero title */
  --fa2-subtitle-font-size: 0.88rem;
  --fa2-desc-font-size: 0.85rem;
  --fa2-header-font-size: 1.08rem;
  --fa2-header-weight: 700;
  --fa2-price-font-size: 1.05rem;
  --fa2-price-weight: 700;
  --fa2-price-was-font-size: 0.85rem;
  --fa2-badge-font-size: 0.7rem;
  --fa2-badge-weight: 700;
  --fa2-num-font-size: 0.8rem;
  --fa2-source-font-size: 0.72rem;
  --fa2-source-weight: 600;
  --fa2-disclosure-font-size: 0.72rem;
  --fa2-disclosure-link-font-size: 0.72rem;

  /* Truncation — item 4 (alignment). -webkit-line-clamp accepts a var()
   * integer directly; the max-height rule a few lines below it in each
   * selector is the plain-text fallback for engines without line-clamp
   * support (clips, no ellipsis, still keeps card heights consistent). */
  --fa2-title-lines: 2;
  --fa2-desc-lines: 2;

  /* ---- Color SCHEMES (coupled bg + foreground-on pairs) -------------------
   * Phase M, M1c — James's design principle (memory
   * feedback_color-schemes-as-coupled-pairs, 2026-07-03): a color choice is
   * never an independent bg/text pair that can drift apart and fail
   * contrast — it's a NAMED SCHEME, always overridden as a unit. Every
   * element below that needs a background + a legible foreground on it
   * (the CTA, the badge/accent, the rank-number circle, on-card text) points
   * at one of these pairs via var() rather than owning its own literal.
   * Because CSS custom properties resolve at USE time (not at declaration
   * time), a site override that redeclares just e.g.
   * `--fa2-scheme-amber-bg`/`-on` together automatically re-covers every
   * element wired to that scheme, and structurally CANNOT touch only one
   * half of a pair the way independent `--fa2-cta-bg`/`--fa2-cta-text`
   * variables previously could (the exact white-on-#ffd100 = 1.46:1 failure
   * class this restructure closes). tests/color-scheme-contrast.test.js
   * enforces every `--fa2-scheme-*-bg`/`-on` pair here at >= 4.5:1 WCAG AA —
   * a scheme that fails contrast fails the build.
   *
   * amber   = the "light scheme" (light bg, near-black on) — the default
   *           CTA. James: attractive amber/gold + near-black text
   *           (~11:1 here; his #ffd100 was illustrative of the HUE, paired
   *           dark to actually be legible).
   * crimson = "dark scheme" (saturated bg, white on) — badge/accent.
   * navy    = "dark scheme" (dark bg, white on) — the stacked rank-number
   *           circle.
   * surface = the neutral on-card text scheme (near-white bg, body/muted
   *           text) — source/brand labels and disclosure copy read against
   *           the plain card surface, not a colored chip, so they draw
   *           their foreground from THIS scheme rather than an independent
   *           literal of their own. */
  --fa2-scheme-amber-bg: #ffc107;
  --fa2-scheme-amber-on: #171310;
  --fa2-scheme-crimson-bg: #c22f3d;
  --fa2-scheme-crimson-on: #ffffff;
  --fa2-scheme-navy-bg: #1d3557;
  --fa2-scheme-navy-on: #ffffff;
  --fa2-scheme-surface-bg: #ffffff;
  --fa2-scheme-surface-on: #1f2937;
  --fa2-scheme-surface-on-muted: #6b7280;

  /* ---- Colors -------------------------------------------------------------
   * "Accent" is the one shared brand color (badges, banner border, stacked
   * rank circle) — separate from the CTA color, which is deliberately its
   * own high-contrast token (item 2) so a themed accent never has to also
   * double as a legible button color. Everything below is either a plain
   * surface/text role or an ALIAS of a scheme pair above — never a second,
   * independent copy of the same color. */
  --fa2-color-text: var(--fa2-scheme-surface-on);
  --fa2-color-text-secondary: #374151;
  --fa2-color-text-muted: var(--fa2-scheme-surface-on-muted);
  --fa2-color-border: #e5e7eb;
  --fa2-color-placeholder-bg: #e5e7eb;
  --fa2-color-price: #111827;
  --fa2-color-price-was: #9ca3af;
  --fa2-color-surface: var(--fa2-scheme-surface-bg);

  --fa2-accent: var(--fa2-scheme-crimson-bg);
  --fa2-badge-bg: var(--fa2-accent);
  --fa2-badge-text: var(--fa2-scheme-crimson-on);
  --fa2-num-bg: var(--fa2-scheme-navy-bg);
  --fa2-num-text: var(--fa2-scheme-navy-on);
  --fa2-source-color: var(--fa2-scheme-surface-on-muted);
  --fa2-brand-color: var(--fa2-scheme-surface-on);
  --fa2-disclosure-color: var(--fa2-scheme-surface-on-muted);
  --fa2-disclosure-link-color: var(--fa2-scheme-surface-on);

  /* CTA — item 2 (Phase M redo, M1c). Amber scheme: near-black on amber/
   * gold, ~11:1 — comfortably above the 4.5:1 WCAG AA threshold, verified
   * programmatically for every scheme pair (not just this one) in
   * tests/color-scheme-contrast.test.js. */
  --fa2-cta-bg: var(--fa2-scheme-amber-bg);
  --fa2-cta-text: var(--fa2-scheme-amber-on);
  --fa2-cta-weight: 700;
  --fa2-cta-font-size: 0.95rem;
  --fa2-cta-radius: 6px;
  --fa2-cta-padding: 0.75rem 1.3rem;
  /* Small CTA variant (vertical-stack's per-row button, hero-card's
   * secondary link sizing) — its own theme knobs, not a hardcoded literal
   * on the selector, per the item-1 "nothing outside :root" rule. */
  --fa2-cta-font-size-sm: 0.82rem;
  --fa2-cta-padding-sm: 0.4rem 0.85rem;

  /* ---- Radius -------------------------------------------------------------- */
  --fa2-radius-sm: 6px;
  --fa2-radius: 8px;
  --fa2-radius-lg: 10px;
  --fa2-badge-radius: 4px;

  /* ---- Spacing scale (denser defaults — James's whitespace note) --------- */
  --fa2-space-2xs: 0.25rem;
  --fa2-space-xs: 0.35rem;
  --fa2-space-sm: 0.5rem;
  --fa2-space-md: 0.65rem;
  --fa2-space-lg: 0.9rem;
  --fa2-space-xl: 1.1rem;

  /* ---- Layout knobs (still theme-level, not structural) ------------------ */
  --fa2-meta-row-height: 1.55rem;       /* item 4: reserved price/source slot */
  --fa2-banner-hero-media-width: 62%;   /* item 5: 1-item banner image width */
}

.fa2-block {
  box-sizing: border-box;
  font-family: var(--fa2-font-family);
  color: var(--fa2-color-text);
  line-height: var(--fa2-line-height);
  margin: var(--fa2-space-xl) 0;
}
.fa2-block *,
.fa2-block *::before,
.fa2-block *::after {
  box-sizing: inherit;
}
.fa2-block img {
  max-width: 100%;
  height: auto;
  display: block;
}
.fa2-block a {
  text-decoration: none;
  color: inherit;
}
/* CTA contrast fix (item 2 root cause): `.fa2-block a` above is an
 * element+class selector (specificity 0,1,1), which used to beat the
 * single-class `.fa2-btn` rule (0,1,0) and forced the button's text back to
 * the block's dark text color regardless of --fa2-cta-text — the button
 * LOOKED dark-on-dark even though the CSS "said" white. Excluding .fa2-btn
 * here (rather than bumping .fa2-btn's own specificity, which would ripple
 * into every override selector) is the minimal, cascade-correct fix. */
.fa2-block a:not(.fa2-btn) {
  color: inherit;
}

/* Shared atoms ------------------------------------------------------- */

.fa2-image--placeholder {
  background: var(--fa2-color-placeholder-bg);
  border-radius: var(--fa2-radius-sm);
  display: block;
}

/* Image pipeline (2026-07-11, docs/plans/briefs/image-pipeline.md §3.2).
 * A PROCESSED row renders its self-hosted variants as
 * <picture class="fa2-picture"><source webp><img class="fa2-*__image"></picture>
 * (templates/item-helpers.js's imageHtml). `display: contents` makes the
 * <picture> wrapper LAYOUT-TRANSPARENT: the <img> stays the flex/grid item its
 * parent's rules already target, so every existing per-template image rule
 * (.fa2-grid__image, .fa2-hero__image, ...) applies EXACTLY as it does to an
 * unprocessed row's bare <img> — no template CSS changes, no layout shift when
 * a row flips from unprocessed to processed.
 *
 * Every browser since 2018 supports display:contents; an older one falls back
 * to a picture element's default `display: inline`, which is also what it would
 * have done with no rule at all. UNPROCESSED rows emit no <picture> whatsoever,
 * so this selector never matches for them. */
.fa2-picture {
  display: contents;
}

.fa2-source {
  display: block;
  font-size: var(--fa2-source-font-size);
  font-weight: var(--fa2-source-weight);
  color: var(--fa2-source-color);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  margin: 0 0 var(--fa2-space-2xs);
}

/* brand label (Phase M, M1b) — "who it's about", distinct from .fa2-source
 * ("where the click lands"). Deliberately NOT uppercase/letter-spaced like
 * .fa2-source, so the two read as visually distinct roles rather than two
 * copies of the same chip. */
.fa2-brand {
  display: block;
  font-size: var(--fa2-source-font-size);
  font-weight: 500;
  font-style: italic;
  color: var(--fa2-brand-color);
  margin: 0 0 var(--fa2-space-2xs);
}

.fa2-price-row {
  display: inline-flex;
  align-items: baseline;
  gap: var(--fa2-space-sm);
  margin: 0;
}
.fa2-price {
  font-weight: var(--fa2-price-weight);
  font-size: var(--fa2-price-font-size);
  color: var(--fa2-color-price);
}
.fa2-price--was {
  font-weight: 400;
  font-size: var(--fa2-price-was-font-size);
  color: var(--fa2-color-price-was);
  text-decoration: line-through;
}

/* item 4 + item 8: the reserved-height meta slot. A fixed floor height
 * (min-height) keeps a price-less card from collapsing shorter than a priced
 * neighbor in the same row. Holds the source label (item 8, shown on EVERY
 * unit) and, below it, the price row when a price exists — so when there is
 * no price the slot still carries the source label ("render source label
 * there when no price") and never goes empty. */
.fa2-grid__meta,
.fa2-banner__meta,
.fa2-stacked__meta {
  min-height: var(--fa2-meta-row-height);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--fa2-space-2xs);
}
.fa2-grid__meta,
.fa2-stacked__meta {
  align-items: flex-start;
}
.fa2-banner__meta {
  align-items: center;
}
/* source/brand labels inside a meta slot drop their own bottom margin —
 * the slot's gap handles the spacing between them and the price row. */
.fa2-grid__meta .fa2-source,
.fa2-banner__meta .fa2-source,
.fa2-stacked__meta .fa2-source,
.fa2-grid__meta .fa2-brand,
.fa2-banner__meta .fa2-brand,
.fa2-stacked__meta .fa2-brand {
  margin: 0;
}

.fa2-btn {
  display: inline-block;
  padding: var(--fa2-cta-padding);
  border-radius: var(--fa2-cta-radius);
  font-weight: var(--fa2-cta-weight);
  font-size: var(--fa2-cta-font-size);
  color: var(--fa2-cta-text);
  background-color: var(--fa2-cta-bg);
  text-align: center;
  line-height: 1.2;
}
.fa2-btn:hover {
  filter: brightness(1.08);
}

.fa2-badge {
  display: inline-block;
  padding: var(--fa2-space-2xs) var(--fa2-space-sm);
  border-radius: var(--fa2-badge-radius);
  font-size: var(--fa2-badge-font-size);
  font-weight: var(--fa2-badge-weight);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--fa2-badge-text);
  background-color: var(--fa2-badge-bg);
}

.fa2-header {
  font-size: var(--fa2-header-font-size);
  font-weight: var(--fa2-header-weight);
  margin: 0 0 var(--fa2-space-md);
}

.fa2-disclosure {
  font-size: var(--fa2-disclosure-font-size);
  color: var(--fa2-disclosure-color);
  margin: var(--fa2-space-md) 0 0;
}
.fa2-disclosure__link {
  font-size: var(--fa2-disclosure-link-font-size);
  color: var(--fa2-disclosure-link-color);
  text-decoration: underline;
}

/* hero ----------------------------------------------------------------- */

.fa2-hero {
  display: flex;
  flex-direction: column;
  gap: var(--fa2-space-lg);
  border: 1px solid var(--fa2-color-border);
  border-radius: var(--fa2-radius-lg);
  padding: var(--fa2-space-lg);
}
.fa2-hero__media {
  position: relative;
}
.fa2-hero__image {
  width: 100%;
  border-radius: var(--fa2-radius);
}
.fa2-hero__media .fa2-badge {
  position: absolute;
  top: var(--fa2-space-sm);
  left: var(--fa2-space-sm);
}
.fa2-hero__title {
  font-size: var(--fa2-title-font-size-lg);
  margin: 0 0 var(--fa2-space-xs);
}
.fa2-hero__subtitle {
  margin: 0 0 var(--fa2-space-sm);
  color: var(--fa2-color-text-muted);
  font-size: var(--fa2-subtitle-font-size);
}
.fa2-hero__desc {
  margin: 0 0 var(--fa2-space-sm);
  color: var(--fa2-color-text-secondary);
  font-size: var(--fa2-desc-font-size);
}
/* Phase M, M1c (compat audit §E.3): the CTA previously sat inline right
 * after the price row (both are inline-level siblings inside the plain
 * block `.fa2-hero__body`), landing on the same line, left-aligned, with
 * dead whitespace to the right. Giving it its own block-level line (via
 * `display:block` + intrinsic width) and centering that intrinsic-width box
 * with `margin:auto` breaks it out of the price's line and centers it
 * within the remaining line space, exactly as the audit's fix recommends. */
.fa2-hero__cta {
  display: block;
  width: max-content;
  max-width: 100%;
  margin: var(--fa2-space-sm) auto 0;
}

@media (min-width: 640px) {
  .fa2-hero--pos-left {
    flex-direction: row;
  }
  .fa2-hero--pos-right {
    flex-direction: row-reverse;
  }
  .fa2-hero--pos-left .fa2-hero__media,
  .fa2-hero--pos-right .fa2-hero__media {
    flex: 0 0 45%;
  }
  .fa2-hero--pos-left .fa2-hero__body,
  .fa2-hero--pos-right .fa2-hero__body {
    flex: 1;
  }
}

/* vertical-stack --------------------------------------------------------- */

.fa2-vstack__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--fa2-space-sm);
}
.fa2-vstack__item {
  border: 1px solid var(--fa2-color-border);
  border-radius: var(--fa2-radius);
}
/* Phase M, M1c (compat audit §D.2 — confirmed v1 regression): a per-row CTA
 * button was added back. It's a SIBLING of the content link, not nested
 * inside it (nested <a> tags are invalid HTML and browsers reparent them
 * unpredictably) — `.fa2-vstack__row` is the flex row that holds both, so
 * "click anywhere on the row" still works via the content link while the
 * button gets its own explicit affordance. */
.fa2-vstack__row {
  display: flex;
  align-items: center;
  gap: var(--fa2-space-sm);
  padding: var(--fa2-space-md);
}
.fa2-vstack__link {
  display: flex;
  align-items: center;
  gap: var(--fa2-space-md);
  flex: 1;
  min-width: 0;
}
.fa2-vstack__cta {
  flex: 0 0 auto;
  padding: var(--fa2-cta-padding-sm);
  font-size: var(--fa2-cta-font-size-sm);
}
.fa2-vstack__image {
  flex: 0 0 auto;
  width: 64px;
  height: 64px;
  border-radius: var(--fa2-radius-sm);
  object-fit: cover;
}
.fa2-vstack__num {
  flex: 0 0 auto;
  width: 1.5rem;
  text-align: center;
  font-weight: 700;
  color: var(--fa2-color-text-muted);
}
.fa2-vstack__text {
  display: flex;
  flex-direction: column;
  gap: var(--fa2-space-2xs);
  min-width: 0;
}
.fa2-vstack__title {
  font-weight: 600;
  font-size: var(--fa2-title-font-size);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fa2-vstack--compact .fa2-vstack__row {
  padding: var(--fa2-space-xs);
}
.fa2-vstack--compact .fa2-vstack__image {
  width: 48px;
  height: 48px;
}

@media (min-width: 640px) {
  .fa2-vstack__image {
    width: 96px;
    height: 96px;
  }
  .fa2-vstack__title {
    white-space: normal;
  }
}

/* basket-grid ------------------------------------------------------------ */

.fa2-grid__cards {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--fa2-space-lg);
  /* align-items:stretch is the grid default and is what makes every card in
   * a row equal-height (item 4) — left explicit so the intent survives a
   * future override that changes something else on this rule. */
  align-items: stretch;
}
.fa2-grid__card {
  border: 1px solid var(--fa2-color-border);
  border-radius: var(--fa2-radius);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.fa2-grid__image {
  width: 100%;
  aspect-ratio: 16 / 11;
  object-fit: cover;
}
.fa2-grid__body {
  padding: var(--fa2-space-md);
  display: flex;
  flex-direction: column;
  gap: var(--fa2-space-xs);
  flex: 1;
}
.fa2-grid__title {
  font-size: var(--fa2-title-font-size);
  font-weight: 700;
  margin: 0;
  line-height: var(--fa2-line-height);
  max-height: calc(var(--fa2-title-lines) * var(--fa2-line-height) * 1em);
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: var(--fa2-title-lines);
  -webkit-box-orient: vertical;
}
.fa2-grid__desc {
  font-size: var(--fa2-desc-font-size);
  color: var(--fa2-color-text-secondary);
  margin: 0;
  line-height: var(--fa2-line-height);
  max-height: calc(var(--fa2-desc-lines) * var(--fa2-line-height) * 1em);
  display: -webkit-box;
  -webkit-line-clamp: var(--fa2-desc-lines);
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.fa2-grid__card--minimal {
  border: none;
  border-bottom: 1px solid var(--fa2-color-border);
  border-radius: 0;
}
/* item 4: pin the CTA to the bottom of every card regardless of how much
 * title/description/meta content sits above it. .fa2-grid__body is a flex
 * column (above); margin-top:auto on the last flex child pushes everything
 * before it up and the button itself down to the shared baseline.
 * Phase M, M1c (compat audit §E.2): align-self was flex-start, smashing the
 * button against the card's left edge — changed to center. */
.fa2-grid__body > .fa2-btn {
  margin-top: auto;
  align-self: center;
}

@media (min-width: 560px) {
  .fa2-grid__cards {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 900px) {
  .fa2-grid__cards {
    grid-template-columns: repeat(var(--fa2-cols, 3), 1fr);
  }
}

/* basket-banner ------------------------------------------------------------ */

.fa2-banner {
  border: 2px solid var(--fa2-accent);
  border-radius: var(--fa2-radius-lg);
  padding: var(--fa2-space-lg);
  position: relative;
}
.fa2-banner__badge {
  display: inline-block;
  background: var(--fa2-accent);
  color: var(--fa2-badge-text);
  font-weight: var(--fa2-badge-weight);
  font-size: var(--fa2-badge-font-size);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: var(--fa2-space-2xs) var(--fa2-space-md);
  border-radius: 999px;
  margin-bottom: var(--fa2-space-md);
}

/* Multi-item (2-3) layout — a row of equal-height cards. */
.fa2-banner__cards {
  display: flex;
  flex-direction: column;
  gap: var(--fa2-space-lg);
  align-items: stretch;
}
.fa2-banner__card {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--fa2-space-sm);
}
.fa2-banner__image {
  width: 140px;
  height: 140px;
  object-fit: cover;
  border-radius: var(--fa2-radius);
}
.fa2-banner__title {
  font-size: var(--fa2-title-font-size);
  font-weight: 700;
  margin: 0;
  line-height: var(--fa2-line-height);
  max-height: calc(var(--fa2-title-lines) * var(--fa2-line-height) * 1em);
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: var(--fa2-title-lines);
  -webkit-box-orient: vertical;
}
.fa2-banner__card > .fa2-btn {
  margin-top: auto;
}

@media (min-width: 640px) {
  .fa2-banner__cards {
    flex-direction: row;
  }
}

/* Single-item (item 5) layout — the redesign. Full-height image (cover),
 * roughly 60% width on desktop, text block filling the rest — replaces the
 * old "one centered card in a wide empty banner" look. */
.fa2-banner--single .fa2-banner__hero {
  display: flex;
  flex-direction: column;
  gap: var(--fa2-space-lg);
}
.fa2-banner__hero-media {
  flex: 0 0 auto;
}
.fa2-banner__hero-image {
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  border-radius: var(--fa2-radius);
}
.fa2-banner__hero-body {
  display: flex;
  flex-direction: column;
  gap: var(--fa2-space-xs);
}
.fa2-banner__hero-title {
  font-size: var(--fa2-title-font-size-lg);
  margin: 0;
}
.fa2-banner__hero-desc {
  font-size: var(--fa2-desc-font-size);
  color: var(--fa2-color-text-secondary);
  margin: 0;
  line-height: var(--fa2-line-height);
  max-height: calc(var(--fa2-desc-lines) * var(--fa2-line-height) * 1em);
  display: -webkit-box;
  -webkit-line-clamp: var(--fa2-desc-lines);
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Phase M, M1c (compat audit §E.2): align-self was flex-start; centered. */
.fa2-banner__hero-cta {
  margin-top: var(--fa2-space-xs);
  align-self: center;
}

@media (min-width: 640px) {
  .fa2-banner--single .fa2-banner__hero {
    flex-direction: row;
    align-items: stretch;
  }
  .fa2-banner__hero-media {
    flex: 0 0 var(--fa2-banner-hero-media-width);
    align-self: stretch;
  }
  .fa2-banner__hero-image {
    height: 100%;
    aspect-ratio: auto;
  }
  .fa2-banner__hero-body {
    flex: 1;
    justify-content: center;
  }
}

/* basket-stacked ------------------------------------------------------------ */

.fa2-stacked__list {
  display: flex;
  flex-direction: column;
  gap: var(--fa2-space-lg);
}
.fa2-stacked__item {
  display: flex;
  flex-direction: column;
  gap: var(--fa2-space-sm);
  border: 1px solid var(--fa2-color-border);
  border-radius: var(--fa2-radius);
  padding: var(--fa2-space-md);
  position: relative;
}
.fa2-stacked__num {
  position: absolute;
  top: var(--fa2-space-sm);
  left: var(--fa2-space-sm);
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 50%;
  background: var(--fa2-num-bg);
  color: var(--fa2-num-text);
  font-weight: 700;
  font-size: var(--fa2-num-font-size);
  display: flex;
  align-items: center;
  justify-content: center;
}
.fa2-stacked__image {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--fa2-radius-sm);
}
.fa2-stacked__title {
  font-size: var(--fa2-title-font-size);
  font-weight: 700;
  margin: 0;
  line-height: var(--fa2-line-height);
  max-height: calc(var(--fa2-title-lines) * var(--fa2-line-height) * 1em);
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: var(--fa2-title-lines);
  -webkit-box-orient: vertical;
}
.fa2-stacked__desc {
  font-size: var(--fa2-desc-font-size);
  color: var(--fa2-color-text-secondary);
  margin: 0;
  line-height: var(--fa2-line-height);
  max-height: calc(var(--fa2-desc-lines) * var(--fa2-line-height) * 1em);
  display: -webkit-box;
  -webkit-line-clamp: var(--fa2-desc-lines);
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.fa2-stacked--compact .fa2-stacked__item {
  padding: var(--fa2-space-sm);
  gap: var(--fa2-space-xs);
}
.fa2-stacked__body {
  display: flex;
  flex-direction: column;
  gap: var(--fa2-space-xs);
}
/* Phase M, M1c (compat audit §E.2): align-self was flex-start; centered. */
.fa2-stacked__body > .fa2-btn {
  margin-top: auto;
  align-self: center;
}

@media (min-width: 640px) {
  .fa2-stacked__item {
    flex-direction: row;
    align-items: stretch;
  }
  .fa2-stacked__image {
    width: 200px;
    flex: 0 0 auto;
  }
  .fa2-stacked__body {
    flex: 1;
  }
}

/* sponsor chrome (Phase M, M1b) --------------------------------------------
 * A wrapper OPTION composing with any template's output, never a template
 * of its own (Piece 7) — see lib/sponsorChrome.js. Appended as a footer
 * line below whatever the inner template rendered, mirroring v1's exact
 * basket sponsor-footer placement (compat audit §B: sponsor footer, THEN
 * disclosure). */
.fa2-sponsor-wrap {
  display: block;
}
.fa2-sponsor-line {
  margin: var(--fa2-space-sm) 0 0;
  font-size: var(--fa2-disclosure-font-size);
  color: var(--fa2-scheme-surface-on-muted);
  text-align: center;
}
.fa2-sponsor-line__link {
  color: var(--fa2-scheme-surface-on);
  font-weight: 600;
  text-decoration: underline;
}

/* hero-card (Phase M, M1a) --------------------------------------------------
 * v1's most-used legacy template (47 live uses) — a STACKED card (badge ->
 * image -> body -> alternatives -> disclosure), distinct from `hero`'s
 * side-by-side image/body layout, matching v1's actual generateHeroCardHTML
 * shape (app.js) rather than reusing hero's. The alternatives drawer is a
 * native <details>/<summary> — no script tags anywhere (C-1), the browser's
 * built-in disclosure behavior replaces v1's JS toggle handler. */
.fa2-herocard {
  border: 1px solid var(--fa2-color-border);
  border-radius: var(--fa2-radius-lg);
  overflow: hidden;
}
.fa2-herocard__badge {
  display: block;
  border-radius: 0;
  text-align: center;
  padding: var(--fa2-space-sm) var(--fa2-space-md);
}
.fa2-herocard__image-link {
  display: block;
}
.fa2-herocard__image {
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
}
.fa2-herocard__body {
  padding: var(--fa2-space-lg);
  display: flex;
  flex-direction: column;
  gap: var(--fa2-space-xs);
}
.fa2-herocard__title-link {
  display: block;
}
.fa2-herocard__title {
  font-size: var(--fa2-title-font-size-lg);
  margin: 0;
}
.fa2-herocard__quote {
  margin: 0;
  padding: var(--fa2-space-sm) var(--fa2-space-md);
  border-left: 3px solid var(--fa2-accent);
  font-style: italic;
  color: var(--fa2-color-text-secondary);
  font-size: var(--fa2-desc-font-size);
}
.fa2-herocard__ctas {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--fa2-space-sm);
  margin-top: var(--fa2-space-xs);
}
.fa2-herocard__secondary {
  font-size: var(--fa2-cta-font-size-sm);
  font-weight: 600;
  color: var(--fa2-scheme-surface-on);
  text-decoration: underline;
}
.fa2-herocard__alts {
  border-top: 1px solid var(--fa2-color-border);
  padding: var(--fa2-space-md) var(--fa2-space-lg);
}
.fa2-herocard__alts-toggle {
  cursor: pointer;
  font-weight: 600;
  font-size: var(--fa2-subtitle-font-size);
  color: var(--fa2-scheme-surface-on);
}
.fa2-herocard__alts-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: var(--fa2-space-sm);
  margin-top: var(--fa2-space-sm);
}
.fa2-herocard__alt {
  display: flex;
  flex-direction: column;
  gap: var(--fa2-space-2xs);
  border: 1px solid var(--fa2-color-border);
  border-radius: var(--fa2-radius-sm);
  padding: var(--fa2-space-xs);
}
.fa2-herocard__alt-image {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: var(--fa2-radius-sm);
}
.fa2-herocard__alt-title {
  font-size: var(--fa2-desc-font-size);
  font-weight: 600;
  line-height: var(--fa2-line-height);
  max-height: calc(2 * var(--fa2-line-height) * 1em);
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.fa2-herocard__alt-price {
  font-size: var(--fa2-price-was-font-size);
  font-weight: 700;
  color: var(--fa2-color-price);
}

/* ---------------------------------------------------------------------------
 * Atlas 2026-07-12 — two live-verified fixes from the MWB canary.
 * Appended at the end deliberately: both rules must win on source order over
 * the component rules above (identical specificity), without !important.
 * ------------------------------------------------------------------------- */

/* 1. Square/portrait source images must not be cropped into a landscape slot.
 *    Amazon product shots are 1500x1500; the hero-card slot is 16:10 with
 *    object-fit: cover, which lopped the top and bottom off the product.
 *    imageFitClass() (item-helpers.js) stamps this class from the row's stored
 *    image_width/image_height, so genuinely-landscape art (Viator 720x480) is
 *    untouched and keeps filling the slot edge to edge. */
.fa2-image--contain {
  object-fit: contain;
  background: #ffffff;
}

/* 2. The disclosure ("Sponsored") is a <p>, so a host site's own paragraph rule
 *    (e.g. `.article p { margin-bottom: 1.5rem }`, specificity 0,2,0) outranks
 *    `.fa2-disclosure` (0,1,0) and adds a phantom gap under every ad block.
 *    Seen live on MenWhoBlog. Match that specificity from inside our own block
 *    so the host template cannot reintroduce the space. */
.fa2-block .fa2-disclosure {
  margin: var(--fa2-space-md) 0 0;
}
