/* ============================================================================
   Ferum Review — an editorial skin for interior & building-material reviews
   ============================================================================

   HOW THIS FILE IS LOADED
   ---------------------------------------------------------------------------
   default/templates/base.html always loads the default theme first, then layers
   the active theme on top:

       /themes/default/assets/theme.css        ← layout, .fr-* classes, tokens
       /themes/ferum-review/assets/theme.css   ← THIS FILE (overrides)

   That is why this theme ships no base.html of its own beyond a pass-through:
   cloning the default shell would mean maintaining a second copy of the preload
   script, plugin slots and script tags for no visual gain.

   No build step. Hand-written plain CSS, served by Axum unchanged.

   DESIGN INTENT
   ---------------------------------------------------------------------------
   The audience is architects, interior designers and contractors. They judge a
   platform the way they judge a portfolio: type, spacing, and how images are
   treated. The default theme is *dense* — 11px row padding, 80x50 thumbnails,
   16px titles — which reads as an admin dashboard. This theme trades density
   for authority:

     · Editorial type scale. Big, tight headlines (-0.035em); micro-labels in
       uppercase at 0.14em tracking — the visual grammar of spec sheets and
       architectural drawings.
     · Air. Row padding roughly doubles; sections breathe.
     · Photography gets room. Feed thumbnails go 80x50 → 148x104, product
       imagery loses its chrome and gains a slow hover zoom.
     · Restraint with colour. Amber appears as precise 2px rules and small
       marks, never as large fills. On a materials board the *photographs*
       supply the colour; the interface must not compete with them.

   FONTS
   ---------------------------------------------------------------------------
   The app's CSP is `default-src 'self'` / `font-src 'self' data:` — every CDN
   is blocked. No web-font binaries ship here, so --fr-font-sans is a *stack*:
   it uses Inter / Be Vietnam Pro / Plus Jakarta Sans when the visitor has them
   and degrades to the platform UI font otherwise. Nothing 404s.

   Self-hosting the real thing is the single highest-impact upgrade left in this
   file. Drop the .woff2 files into this theme's assets/fonts/ and uncomment:

   @font-face {
     font-family: "Inter";
     src: url("/themes/ferum-review/assets/fonts/Inter-Variable.woff2") format("woff2");
     font-weight: 100 900;
     font-display: swap;
   }
   @font-face {
     font-family: "Be Vietnam Pro";
     src: url("/themes/ferum-review/assets/fonts/BeVietnamPro-Regular.woff2") format("woff2");
     font-weight: 400;
     font-display: swap;
   }

   PALETTE
   ---------------------------------------------------------------------------
   Slate    #0F172A / #1E293B  structure: footer, headings, buttons, ink
   Concrete #EFEBE4            page canvas + recessed surfaces — a warm stone
                               grey (R−B = 11), not the cool slate the brief
                               also listed and not cream
   Paper    #FFFFFF            cards, panels, topbar — the content plane
   Amber    #D97706            rating + accent GRAPHICS (stars, bars, rules)
   Bronze   #92400E            accent TEXT (links). #D97706 is 3.19:1 on white:
                               fine for graphics at the 3:1 bar, short of AA's
                               4.5:1 for text. So amber paints marks and rules;
                               bronze paints anything readable, and clears AA on
                               both the paper and the canvas.
   Border   #DCD5C9
   ============================================================================ */

/* ── Palette constants ───────────────────────────────────────────────────── */
:root {
  --fr-slate-950: #020617;
  --fr-slate-900: #0f172a;
  --fr-slate-800: #1e293b;
  --fr-slate-700: #334155;
  --fr-slate-600: #475569;
  --fr-slate-500: #64748b;
  --fr-slate-200: #e2e8f0;
  --fr-slate-100: #f1f5f9;
  --fr-slate-50: #f8fafc;

  --fr-amber-900: #7c2d12;
  --fr-amber-800: #92400e;
  --fr-amber-700: #b45309;
  --fr-amber-600: #d97706;
  --fr-amber-400: #fbbf24;
  --fr-amber-200: #fde68a;
  --fr-amber-100: #fef3c7;

  --fr-accent: var(--fr-amber-600); /* graphics: stars, bars, rules */
  /* Accent TEXT had to darken one step when the canvas left near-white:
     amber-700 (#B45309) measures 5.1:1 on paper but only 4.29:1 on the canvas —
     it passed AA on one plane and failed on the other, the kind of gap that
     slips through when only one background is ever checked. amber-800 clears
     4.5:1 on both (7.09:1 on paper, 5.97:1 on canvas), so accent text is safe
     wherever it lands. Amber-600 is unchanged — it only ever paints graphics,
     which answer to the 3:1 bar. */
  --fr-accent-text: var(--fr-amber-800);
  --fr-accent-deep: var(--fr-amber-900); /* hover / emphasis */

  --fr-font-sans: "Inter", "Be Vietnam Pro", "Plus Jakarta Sans", system-ui,
    -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans",
    sans-serif;

  /* Micro-label grammar, reused everywhere a section is titled */
  --fr-label-size: 0.6875rem;
  --fr-label-tracking: 0.14em;

  --fr-ease: cubic-bezier(0.2, 0.6, 0.2, 1);
}

/* ── Light tokens ────────────────────────────────────────────────────────── */
:root,
[data-bs-theme="light"] {
  /* Structure. Hover goes LIGHTER (800) because the base is already near-black;
     the default theme's color-mix(...,#000) would be invisible against it. */
  --ferum-primary: var(--fr-slate-900);
  --ferum-primary-dark: var(--fr-slate-800);
  --bs-primary: var(--ferum-primary);
  --bs-primary-rgb: 15, 23, 42;

  /* Cool ink on warm paper. Every value clears AA on BOTH planes — the canvas
     is the tighter of the two, so it is the one quoted. */
  --bs-body-color: var(--fr-slate-800); /* 12.31:1 on canvas */
  --bs-secondary-color: var(--fr-slate-600); /*  6.38:1 on canvas */
  --bs-tertiary-color: #5b6b82; /*  4.57:1 on canvas, 5.43:1 on paper */
  --bs-emphasis-color: var(--fr-slate-950);

  /* Links are bronze so they stay distinguishable from near-black body text */
  --bs-link-color: var(--fr-accent-text);
  --bs-link-hover-color: var(--fr-accent-deep);

  /* ── Surfaces ─────────────────────────────────────────────────────────────
     Three planes that must stay tellable apart at a glance:
       recessed  #EFEBE4  canvas, left rail, empty wells
       sheet     #FFFFFF  cards, panels, topbar — the content
       anchor    #0F172A  footer only

     An earlier revision put the canvas at #F8FAFC against #FFFFFF sheets. That
     is 1.05:1 — about 1.9 L* — which is below the threshold where a boundary
     reads at all, so every plane fused into one white field and the page looked
     unfinished rather than restrained. The canvas now measures 1.19:1 against
     the sheet: enough for cards to sit ON something, still quiet.

     The ground is a warm stone grey, per the brief's "Warm Concrete Gray" — the
     cool slate hexes it also listed are not what concrete looks like.

     Warmth is kept deliberately low: R−B = 11. Push that past ~18 and the grey
     turns to cream, which is both the wrong material and a look that has become
     a default. Real concrete is near-neutral with a slight warm cast, and low
     chroma is also what protects the accent: ground and amber now share a hue
     family, so the separation has to come from saturation instead. A washed-out
     ground would have flattened the one colour this design spends.

     Ink stays slate — cool type on warm paper. That temperature split is what
     printed matter actually looks like, and it keeps the structural colour
     tied to the footer and headings. */
  --fr-surface: #ffffff;
  --fr-surface-sunken: #efebe4;
  --bs-body-bg: #efebe4;
  --bs-secondary-bg: #f5f2ec;
  --bs-tertiary-bg: #f5f2ec;
  --bs-border-color: #dcd5c9;
  --fr-hairline: #eae5dc;

  /* Left sidebar nav. These three are the DEFAULT theme's own tokens and are
     consumed by the sidebar, not the topbar — keep them tuned for a light
     surface. (Earlier revisions reused them for the dark bar, which made
     sidebar hover white-on-white and therefore invisible.) */
  --fr-nav-hover-bg: rgba(15, 23, 42, 0.05);
  --fr-nav-active-bg: rgba(217, 119, 6, 0.12);
  --fr-nav-active-color: var(--fr-accent-text);

  /* ── Topbar (--fr-tb-*) ───────────────────────────────────────────────────
     Scoped separately from --fr-nav-* so the bar can be re-skinned without
     touching the sidebar. Every .fr-topbar rule reads only these tokens, so
     switching the bar light↔dark is this block and nothing else.

     Shipping LIGHT. Architecture and interior publications — Dezeen, ArchDaily,
     Architonic, Kinfolk, Vitra — are near-unanimously light-headed; a dark bar
     is the vernacular of developer tooling (Linear, Vercel, GitHub). A light bar
     also stops the header competing with material photography, which is the
     content that has to win on this board.

     For the slate masthead instead, swap in:
       --fr-tb-bg: var(--fr-slate-900);      --fr-tb-fg: #ffffff;
       --fr-tb-fg-muted: rgba(255,255,255,.68);
       --fr-tb-border: rgba(255,255,255,.08);
       --fr-tb-hover-bg: rgba(255,255,255,.10);
       --fr-tb-field-bg: rgba(255,255,255,.07);
       --fr-tb-field-bg-focus: rgba(255,255,255,.13);
       --fr-tb-field-border: rgba(255,255,255,.12);
       --fr-tb-ring: rgba(255,255,255,.28);
       --fr-tb-outline-fg: #ffffff;
       --fr-tb-outline-hover-bg: #ffffff;
       --fr-tb-outline-hover-fg: var(--fr-slate-900); */
  --fr-tb-bg: #ffffff;
  --fr-tb-fg: var(--fr-slate-900);
  --fr-tb-fg-muted: var(--fr-slate-600);
  --fr-tb-border: #dcd5c9;
  --fr-tb-hover-bg: rgba(15, 23, 42, 0.055);
  --fr-tb-field-bg: #f5f2ec;
  --fr-tb-field-bg-focus: #ffffff;
  --fr-tb-field-border: #dcd5c9;
  --fr-tb-ring: #dcd5c9;
  --fr-tb-outline-fg: var(--fr-slate-900);
  --fr-tb-outline-hover-bg: var(--fr-slate-900);
  --fr-tb-outline-hover-fg: #ffffff;

  /* Amber drives every "warning" surface: stars, rating bars, verdict pills.
     Retargeting the Bootstrap token means zero markup changes. */
  --bs-warning: var(--fr-accent);
  --bs-warning-rgb: 217, 119, 6;
  --bs-warning-text-emphasis: var(--fr-amber-800);
  --bs-warning-bg-subtle: var(--fr-amber-100);
  --bs-warning-border-subtle: var(--fr-amber-200);

  /* Verified-purchase green, pulled toward the palette's cooler register */
  --bs-success: #047857;
  --bs-success-rgb: 4, 120, 87;
  --bs-success-text-emphasis: #065f46;
  --bs-success-bg-subtle: #d1fae5;
  --bs-success-border-subtle: #a7f3d0;

  /* Shape — tight radii read as considered rather than playful */
  --bs-border-radius: 6px;
  --bs-border-radius-sm: 4px;
  --bs-border-radius-lg: 10px;
  --bs-border-radius-xl: 14px;

  /* Shadows — slate-tinted, wide and faint, never grey haze */
  --bs-box-shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.05);
  --bs-box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04),
    0 8px 24px -12px rgba(15, 23, 42, 0.12);
  --bs-box-shadow-lg: 0 24px 48px -24px rgba(15, 23, 42, 0.25);

  /* A taller bar gives the brand room to read as a masthead */
  --fr-topbar-height: 64px;

  --fr-thread-title-hover: var(--fr-accent-text);
}

/* ── Dark tokens ─────────────────────────────────────────────────────────────
   color_scheme is "light", but users can force dark in preferences, so the
   theme must stay legible there. Amber-400 replaces bronze for links (bronze
   is too dark on a slate canvas). */
[data-bs-theme="dark"] {
  --ferum-primary: var(--fr-slate-600);
  --ferum-primary-dark: var(--fr-slate-500);
  --bs-primary: var(--ferum-primary);
  --bs-primary-rgb: 71, 85, 105;

  --bs-body-color: #e2e8f0;
  --bs-secondary-color: #a3b1c6;
  --bs-tertiary-color: #8595ad;
  --bs-emphasis-color: #f8fafc;

  --bs-link-color: var(--fr-amber-400);
  --bs-link-hover-color: #fcd34d;

  --fr-surface: #101827;
  --fr-surface-sunken: #0b1220;
  --bs-body-bg: var(--fr-slate-950);
  --bs-secondary-bg: #131c2e;
  --bs-tertiary-bg: #131c2e;
  --bs-border-color: #1f2b40;
  --fr-hairline: #18233a;

  --fr-nav-hover-bg: rgba(255, 255, 255, 0.07);
  --fr-nav-active-bg: rgba(251, 191, 36, 0.14);
  --fr-nav-active-color: var(--fr-amber-400);

  /* Topbar sits one step above the canvas so the bar still reads as a masthead */
  --fr-tb-bg: var(--fr-surface);
  --fr-tb-fg: #e8edf5;
  --fr-tb-fg-muted: #a3b1c6;
  --fr-tb-border: #1f2b40;
  --fr-tb-hover-bg: rgba(255, 255, 255, 0.07);
  --fr-tb-field-bg: #0b1220;
  --fr-tb-field-bg-focus: #131c2e;
  --fr-tb-field-border: #1f2b40;
  --fr-tb-ring: #24344e;
  --fr-tb-outline-fg: #e8edf5;
  --fr-tb-outline-hover-bg: #e8edf5;
  --fr-tb-outline-hover-fg: var(--fr-slate-900);

  --bs-warning: var(--fr-amber-400);
  --bs-warning-rgb: 251, 191, 36;
  --bs-warning-text-emphasis: var(--fr-amber-200);
  --bs-warning-bg-subtle: #3a2a08;
  --bs-warning-border-subtle: #5c4310;

  --bs-success: #10b981;
  --bs-success-rgb: 16, 185, 129;
  --bs-success-text-emphasis: #6ee7b7;
  --bs-success-bg-subtle: #062e24;
  --bs-success-border-subtle: #0c4a3a;

  --fr-thread-title-hover: var(--fr-amber-400);
}

/* ============================================================================
   BUG FIX — sticky header
   ============================================================================
   The default theme ships `html, body { height: 100%; margin: 0 }` from
   src/_admin.css (built theme.css ~line 1799). It is meant for the admin
   shell's fixed-height flex layout, but it is written as a GLOBAL selector, so
   it also applies to every public page.

   Consequence: <body> is capped at viewport height. A `position: sticky`
   element cannot escape its containing block, so .fr-topbar — a direct child of
   <body> — stops sticking the moment you scroll past 100vh. The header simply
   scrolls away and never comes back.

   Restoring auto height fixes it. This is safe: admin pages load the DEFAULT
   theme's stylesheet (templates/admin/base.html hardcodes
   /themes/{{ default_theme_slug }}/assets/theme.css), never this file, so the
   admin shell keeps its 100% height.

   Proper upstream fix is to scope that rule to .adm-shell in _admin.css. */
html,
body {
  height: auto;
  min-height: 100%;
}

/* The cover-image dropzone's wrapping-text bug that used to be patched here is
   now fixed at source — app/new_thread.html carries `text-center` on that flex
   column — so the theme no longer compensates for it. */

/* ============================================================================
   TYPOGRAPHY
   ============================================================================ */

/* Base size stays on the default theme's 0.9375rem contract.
   ─────────────────────────────────────────────────────────────────────────────
   An earlier revision raised this to 1rem for "editorial readability". That is
   a +6.7% scale on EVERY component in the app, and the app's layouts were
   measured at 15px — so tight spots overflow. The cover-image dropzone in
   app/new_thread.html was the first casualty: its one-line prompt wrapped, and
   because that flex column centres the span but never sets text-align, the
   wrapped line hugged the left edge and looked broken.

   Enlarging the chrome was the wrong lever anyway. Reading comfort is wanted in
   the prose people actually read, so the size goes on .post-content below and
   the interface keeps the metrics it was built for. Line-height still rises —
   that buys legibility without changing any element's width. */
body {
  font-family: var(--fr-font-sans);
  font-size: 0.9375rem;
  line-height: 1.65;
  letter-spacing: -0.003em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1,
h2,
h3,
h4,
h5,
h6,
.h1,
.h2,
.h3,
.h4,
.h5,
.h6 {
  font-family: var(--fr-font-sans);
  color: var(--bs-emphasis-color);
  font-weight: 700;
  letter-spacing: -0.025em;
  line-height: 1.2;
}

/* Page titles are written as <h1 class="h4"> in the shipped templates, so the
   Bootstrap size class has to be out-specified rather than replaced. */
h1.h3,
h1.h4 {
  font-size: clamp(1.6rem, 1.15rem + 1.7vw, 2.25rem);
  letter-spacing: -0.035em;
  line-height: 1.12;
}

/* Numbers that get compared column-to-column must not jitter */
.fr-spec,
.fr-histo-row,
.fr-rating,
.fr-highlight-pill,
.fr-product-price,
.fr-panel-stat,
.fr-panel-stat-row,
.fr-stat-band,
.fr-review-chip,
.fr-thread-stats,
time {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* ── The micro-label: this theme's signature ─────────────────────────────────
   Uppercase, widely tracked, hairline-thin amber rule beneath. It is the
   grammar of a technical drawing's title block, and it is what separates this
   from a generic Bootstrap panel. Applied to every section header. */
.fr-panel-header,
.fr-cat-header {
  font-size: var(--fr-label-size);
  font-weight: 700;
  letter-spacing: var(--fr-label-tracking);
  text-transform: uppercase;
  color: var(--bs-secondary-color);
  background: transparent;
  border-bottom: 1px solid var(--bs-border-color);
  padding: 0.9rem 1.25rem 0.75rem;
  position: relative;
}
.fr-panel-header::after,
.fr-cat-header::after {
  content: "";
  position: absolute;
  left: 1.25rem;
  bottom: -1px;
  width: 26px;
  height: 2px;
  background: var(--fr-accent);
}
.fr-cat-header {
  padding: 1rem 1.5rem 0.85rem;
}
.fr-cat-header::after {
  left: 1.5rem;
}
.fr-cat-header:hover {
  background: transparent;
}

/* A signature only signifies while it stays rare. With the rule on every panel
   header, one home screen showed five amber underlines at once — at that point
   it stops marking hierarchy and becomes wallpaper. Keep it on main-column
   section heads; the right rail is secondary, so it gets the micro-label
   without the rule. */
.fr-right-col .fr-panel-header::after {
  content: none;
}
/* The default gives right-rail headers a 2px left stripe painted with
   --ferum-primary. That token is admin-editable (Settings → Primary colour), so
   the moment someone picks a bright colour the rail starts shouting in it and
   the accent is diluted all over again — exactly the problem the underline rule
   above was tightened to solve. Pin the stripe to a theme-owned neutral: it
   stays a quiet rhythm marker (1.86:1 on the canvas — legible, recessive) and
   cannot be repainted from the admin panel. */
.fr-right-col .fr-panel-header,
.fr-right-col .fr-panel-section {
  border-left-color: #b9ad98;
  padding-bottom: 0.55rem;
}

/* The topbar search stays visible on desktop everywhere, homepage included, and
   is now the ONLY search on the page: the masthead used to carry a second field,
   which an earlier rule tried to reconcile by hiding the topbar bar on masthead
   pages. Both attempts were treating the symptom. The duplicate was removed
   instead (see "Masthead actions" below) — the topbar bar is the persistent,
   sticky one, the masthead's scrolled away, and two unlabelled role="search"
   landmarks are announced identically by a screen reader. If a search field is
   ever put back in the masthead, it needs an aria-label distinguishing it from
   this one, not a rule hiding one of the two. */
/* Buttons living inside a label row must not inherit the uppercase tracking */
.fr-cat-header .btn,
.fr-cat-header .badge {
  text-transform: none;
  letter-spacing: normal;
  font-size: 0.8125rem;
  font-weight: 500;
}

/* ============================================================================
   TOPBAR
   ============================================================================
   Reads --fr-tb-* exclusively. No literal colours below, so the bar can be
   flipped light↔dark purely from the token block above. */

/* A sheet floating over the canvas. The faint drop shadow does the separating
   that the slate bar used to do by brute contrast. */
.fr-topbar {
  background: var(--fr-tb-bg);
  border-bottom: 1px solid var(--fr-tb-border);
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
  color: var(--fr-tb-fg);
}

/* The default paints the wordmark with --ferum-primary; restate it here so the
   bar stays legible whichever way the tokens are set. */
.fr-topbar .fr-brand-name {
  color: var(--fr-tb-fg);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: -0.02em;
}
.fr-topbar .fr-brand-slogan {
  color: var(--fr-tb-fg-muted);
  letter-spacing: 0.02em;
}
/* Amber block with a slate glyph is 5.9:1 and works on either ground — the one
   piece of the bar that stays constant across the flip. */
.fr-topbar .fr-brand-mark {
  background: var(--fr-accent);
  color: var(--fr-slate-900);
  border-radius: 5px;
}

.fr-topbar .fr-icon-btn,
.fr-topbar .fr-topbar-menu-btn {
  color: var(--fr-tb-fg-muted);
}
.fr-topbar .fr-icon-btn:hover,
.fr-topbar .fr-topbar-menu-btn:hover {
  background: var(--fr-tb-hover-bg);
  color: var(--fr-tb-fg);
}
.fr-topbar .fr-icon-btn:focus-visible,
.fr-topbar .fr-topbar-menu-btn:focus-visible {
  outline: 2px solid var(--fr-accent);
  outline-offset: 2px;
}

/* Notification bell is a light-DOM web component (shadow: "none") whose icon
   inherits currentColor, so colouring the wrapper is enough. */
.fr-topbar ferum-notification-bell {
  color: var(--fr-tb-fg-muted);
  display: inline-flex;
}
.fr-topbar ferum-notification-bell:hover {
  color: var(--fr-tb-fg);
}

.fr-topbar .fr-avatar-btn:hover {
  background: var(--fr-tb-hover-bg);
}
.fr-topbar .fr-avatar-btn img,
.fr-topbar .fr-avatar-btn .fr-avatar-img,
.fr-topbar .fr-avatar-initials {
  border-color: var(--fr-tb-ring);
}
.fr-topbar .fr-avatar-btn:hover img,
.fr-topbar .fr-avatar-btn:hover .fr-avatar-img,
.fr-topbar .fr-avatar-btn:hover .fr-avatar-initials {
  border-color: var(--fr-accent);
}
.fr-topbar .fr-avatar-initials {
  background: var(--fr-slate-700);
  color: #fff;
}

/* Search reads as a recessed well on either ground */
.fr-topbar .fr-topbar-search {
  background: var(--fr-tb-field-bg);
  border-color: var(--fr-tb-field-border);
  height: 38px;
  border-radius: 999px;
  padding: 0 1rem;
}
.fr-topbar .fr-topbar-search:focus-within {
  background: var(--fr-tb-field-bg-focus);
  border-color: var(--fr-accent);
  box-shadow: 0 0 0 3px rgba(217, 119, 6, 0.2);
}
.fr-topbar .fr-topbar-search input {
  color: var(--fr-tb-fg);
}
.fr-topbar .fr-topbar-search input::placeholder {
  color: var(--fr-tb-fg-muted);
  opacity: 0.85;
}
.fr-topbar .fr-topbar-search-icon {
  color: var(--fr-tb-fg-muted);
}
.fr-topbar .fr-topbar-search-btn:hover .fr-topbar-search-icon {
  color: var(--fr-tb-fg);
}

/* Auth buttons need bar-local values: the global slate primary disappears on a
   slate bar, and pure white disappears on a white one. Register stays amber
   either way — it is the only saturated element in the header. */
.fr-topbar .btn-outline-primary {
  --bs-btn-color: var(--fr-tb-outline-fg);
  --bs-btn-border-color: var(--fr-tb-ring);
  --bs-btn-hover-bg: var(--fr-tb-outline-hover-bg);
  --bs-btn-hover-color: var(--fr-tb-outline-hover-fg);
  --bs-btn-hover-border-color: var(--fr-tb-outline-hover-bg);
  --bs-btn-active-bg: var(--fr-tb-outline-hover-bg);
  --bs-btn-active-color: var(--fr-tb-outline-hover-fg);
}
.fr-topbar .btn-primary {
  --bs-btn-bg: var(--fr-accent);
  --bs-btn-border-color: var(--fr-accent);
  --bs-btn-color: var(--fr-slate-900);
  --bs-btn-hover-bg: var(--fr-amber-700);
  --bs-btn-hover-border-color: var(--fr-amber-700);
  --bs-btn-hover-color: #fff;
  --bs-btn-active-bg: var(--fr-amber-800);
  --bs-btn-active-color: #fff;
}

/* ============================================================================
   SURFACES
   ============================================================================ */

.card {
  --bs-card-bg: var(--fr-surface);
  border-color: var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg);
}
.fr-panel {
  background: var(--fr-surface);
  border-color: var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg);
  margin-bottom: 1.25rem;
  overflow: hidden;
}
.fr-panel-body {
  padding: 1rem 1.25rem 1.15rem;
}
/* The rail is navigation, not content — it belongs to the canvas plane so the
   main column reads as the single sheet you are meant to look at. White-on-white
   made rail and content indistinguishable. */
.fr-sidebar-left {
  background: transparent;
  border-right-color: var(--bs-border-color);
}

/* ── Tonal anchor ────────────────────────────────────────────────────────────
   With the masthead now light, the page had no dark mass anywhere and read as
   weightless — the criticism that the interface was "too white". The footer is
   the right place to put it back: it closes the page, balances the header, and
   sits below the content so it never competes with photography. It is also the
   only element still carrying the slate the palette is built on. */
.fr-footer {
  background: var(--fr-slate-900);
  border-top: none;
  color: rgba(255, 255, 255, 0.66);
}
.fr-footer .fr-footer-link,
.fr-footer a {
  color: rgba(255, 255, 255, 0.72);
}
.fr-footer .fr-footer-link:hover,
.fr-footer a:hover {
  color: #ffffff;
}
.fr-footer .fr-footer-copy {
  color: rgba(255, 255, 255, 0.45);
}
/* The footer brand name. This theme keeps the footer dark in BOTH colour
   schemes, so it cannot inherit the default theme's colours, which assume the
   footer sits on the same surface as the page:

     resting  .fr-footer-brand-name  → var(--bs-body-color) = #1e293b in light
              mode, which is 1.23:1 on this footer's #0f172a — invisible.
     hover    .fr-footer-brand:hover → var(--ferum-primary), and in light mode
              this theme sets --ferum-primary to --fr-slate-900, the footer's
              own background: 1:1, the text disappears entirely on hover.

   Note the class: the footer's span is `.fr-footer-brand-name`. An earlier rule
   here targeted `.fr-brand-name`, which only ever exists in the topbar, so it
   matched nothing and the default's dark colour won. Both states are pinned to
   literal values below rather than palette variables, because the whole failure
   above came from variables that track the *page* surface, not this one. */
.fr-footer .fr-footer-brand-name {
  color: #ffffff;
}
.fr-footer .fr-footer-brand:hover .fr-footer-brand-name {
  color: var(--fr-amber-400);
}
.fr-footer .fr-brand-mark {
  background: var(--fr-accent);
  color: var(--fr-slate-900);
}
[data-bs-theme="dark"] .fr-footer {
  background: #060d1a;
}
.form-control,
.form-select {
  background-color: var(--fr-surface);
}

/* ============================================================================
   MASTHEAD — moved out of this theme
   ============================================================================
   The .fr-hero* rules that lived here styled a masthead built into
   templates/home.html. Both are gone: the masthead is now the `home-hero`
   plugin, which ships its own `.fh-hero*` styles inside bundle.js so it renders
   on any theme, not just this one.

   Its styles read this theme's tokens with fallbacks — var(--fr-accent,
   var(--bs-primary)) and friends — so it still looks native here while working
   on a theme that defines none of them. Nothing needs to be added back on this
   side; adding .fr-hero rules again would style an element no longer rendered.
   See examples/plugins/home-hero/. */

/* ── Browse-by-type strip ──────────────────────────────────────────────────────
   Three category doors under the masthead: icon chip, one-word label, a chevron
   that leans in on hover. Cards, not buttons — they are navigation, and the amber
   is spent on the icon chip and the hover rule, never a full fill. */
.fr-typenav {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.75rem;
  margin-bottom: 1.5rem;
}
.fr-typenav-tile {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  min-height: var(--fr-tap-target);
  padding: 0.85rem 1.05rem;
  color: var(--bs-body-color);
  text-decoration: none;
  background: var(--fr-surface);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg);
  box-shadow: var(--bs-box-shadow-sm);
  transition: border-color 0.15s var(--fr-ease), box-shadow 0.15s var(--fr-ease),
    transform 0.15s var(--fr-ease);
}
.fr-typenav-tile:hover {
  color: var(--bs-body-color);
  border-color: var(--fr-accent);
  box-shadow: var(--bs-box-shadow);
  transform: translateY(-1px);
}
.fr-typenav-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 2.5rem;
  height: 2.5rem;
  font-size: 1.05rem;
  color: var(--bs-warning-text-emphasis);
  background: var(--bs-warning-bg-subtle);
  border-radius: 50%;
}
.fr-typenav-label {
  flex-grow: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.95rem;
  font-weight: 650;
}
.fr-typenav-go {
  flex-shrink: 0;
  font-size: 0.8rem;
  color: var(--bs-tertiary-color);
  transition: transform 0.15s var(--fr-ease), color 0.15s var(--fr-ease);
}
.fr-typenav-tile:hover .fr-typenav-go {
  color: var(--fr-accent-text);
  transform: translateX(3px);
}
@media (prefers-reduced-motion: reduce) {
  .fr-typenav-tile,
  .fr-typenav-go {
    transition: none;
  }
  .fr-typenav-tile:hover {
    transform: none;
  }
  .fr-typenav-tile:hover .fr-typenav-go {
    transform: none;
  }
}
/* One word per tile fits three-up down to a small tablet; below that they stack so
   the icon + label never crush. */
@media (max-width: 575.98px) {
  .fr-typenav {
    grid-template-columns: 1fr;
    gap: 0.5rem;
  }
}

/* ============================================================================
   FEED — the first impression
   ============================================================================ */

.fr-feed {
  background: var(--fr-surface);
  border-color: var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg);
  box-shadow: var(--bs-box-shadow-sm);
}

/* Roughly double the default's 0.6875rem/1rem padding. Density was the single
   biggest reason the board read as an admin table rather than a publication. */
.fr-thread-row {
  gap: 1.25rem;
  padding: 1.35rem 1.5rem;
  border-bottom: 1px solid var(--fr-hairline);
  transition: background 0.18s var(--fr-ease);
}
.fr-thread-row:hover {
  background: var(--fr-surface-sunken);
}
.fr-thread-row--pinned {
  border-left: 2px solid var(--fr-accent);
  background: transparent;
}
.fr-thread-row--pinned:hover {
  background: var(--fr-surface-sunken);
}

/* Photography is the product here: 80x50 → 148x104, with a slow zoom. */
.fr-thread-thumb {
  width: 148px;
  height: 104px;
  border-radius: var(--bs-border-radius);
  margin-top: 0.15rem;
  background: var(--fr-surface-sunken);
  transition: transform 0.5s var(--fr-ease);
  will-change: transform;
}
.fr-thread-row {
  overflow: hidden;
}
.fr-thread-row:hover .fr-thread-thumb {
  transform: scale(1.035);
}

.fr-thread-title {
  font-size: 1.1875rem;
  font-weight: 650;
  line-height: 1.28;
  letter-spacing: -0.021em;
}
.fr-thread-excerpt {
  font-size: 0.9375rem;
  line-height: 1.6;
  margin-top: 0.4rem;
  -webkit-line-clamp: 2;
  color: var(--bs-secondary-color);
}
.fr-thread-meta {
  margin-top: 0.7rem;
  font-size: 0.8125rem;
  gap: 0 0.5rem;
  color: var(--bs-tertiary-color);
}
.fr-thread-stats {
  gap: 0.4rem;
  font-size: 0.8125rem;
  min-width: 62px;
}

@media (max-width: 575.98px) {
  .fr-thread-row {
    padding: 1.1rem 1.15rem;
    gap: 1rem;
  }
  .fr-thread-title {
    font-size: 1.0625rem;
  }
}

/* ── Sort tabs — editorial underline instead of pills ────────────────────── */
.fr-sort-tabs {
  /* 0.9rem, NOT the 1.5rem gutter: each tab carries its own 0.6rem of padding
     and the two stack, so 1.5rem here put the first tab's TEXT at 2.1rem while
     the header title and the ::after accent rule both sit at 1.5rem. What has
     to land on the gutter is the text, not the scroll container's edge. */
  padding: 0 0.9rem;
  border-bottom: 1px solid var(--bs-border-color);
  gap: 0.35rem;
}
.fr-sort-tab {
  font-size: 0.8125rem;
  font-weight: 550;
  letter-spacing: 0.005em;
  padding: 0.85rem 0.6rem;
  border-radius: 0;
  border-bottom: 2px solid transparent;
  background: transparent;
  color: var(--bs-tertiary-color);
  transition: color 0.15s var(--fr-ease), border-color 0.15s var(--fr-ease);
}
.fr-sort-tab:hover {
  background: transparent;
  color: var(--bs-body-color);
  border-bottom-color: var(--bs-border-color);
}
.fr-sort-tab--active {
  background: transparent;
  color: var(--bs-emphasis-color);
  font-weight: 700;
  border-bottom-color: var(--fr-accent);
}

/* ── Filter chips — the narrowing axis, visually distinct from the tabs ──── */
/* The tabs above are editorial underlines that pick ONE ordering; these are
   outlined toggles that ADD or REMOVE a constraint. Two different jobs get two
   different shapes, otherwise a reader cannot predict which control makes rows
   disappear. */
.fr-feed-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0 1rem;
  border-bottom: 1px solid var(--bs-border-color);
}
.fr-feed-controls .fr-sort-tabs {
  border-bottom: none;
  padding-right: 0.5rem;
}
.fr-filter-chips {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  /* Vertical padding must stay SYMMETRIC. .fr-feed-controls centres each group
     as a box, so an asymmetric 0/0.6rem made the 53.6px chip box centre its
     44px of content 4.8px above the tab text beside it — the row read as
     crooked even though both groups were "centred". Height now comes from the
     chips' own min-height and the tabs' own padding. */
  padding: 0 1.5rem 0 0.5rem;
  flex-wrap: wrap;
}
.fr-filter-chips__label {
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--bs-tertiary-color);
}
.fr-filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  min-height: var(--fr-tap-target);
  padding: 0.2rem 0.7rem;
  font-size: 0.78rem;
  font-weight: 550;
  color: var(--bs-tertiary-color);
  text-decoration: none;
  white-space: nowrap;
  /* NOT --bs-border-color. That token is a hairline separator (#dcd5c9 on the
     white card, #1f2b40 on #101827) and lands at ~1.46:1 against the surface
     the chip sits on — so the outline that is this control's ONLY distinction
     from the tabs above was very nearly invisible. WCAG 1.4.11 asks 3:1 for a
     control boundary; 65% of --bs-secondary-color gives 3.2:1 light and 4.2:1
     dark in this palette, and still reads as an outline rather than a button. */
  border: 1px solid color-mix(in srgb, var(--bs-secondary-color) 65%, transparent);
  border-radius: 2px;
  background: transparent;
  transition: color 0.15s var(--fr-ease), border-color 0.15s var(--fr-ease),
    background-color 0.15s var(--fr-ease);
}
.fr-filter-chip:hover {
  color: var(--bs-body-color);
  border-color: var(--fr-accent);
  text-decoration: none;
}
.fr-filter-chip--on {
  color: var(--fr-accent-text);
  border-color: var(--fr-accent);
  background: color-mix(in srgb, var(--fr-accent) 12%, transparent);
  font-weight: 700;
}

/* ── Mobile: the fixed tab bar this theme's editorial inset would otherwise
   undo ────────────────────────────────────────────────────────────────────
   The default theme turns the sort strip into equal-width thirds here so all
   three orderings stay visible with nothing scrolling or clipped. This theme
   inherits that (it never sets `flex` on a tab), but its own 1.5rem editorial
   gutter and 0.6rem tab padding eat ~67px of a 360px screen — enough that
   every English label wrapped to two lines. Full-bleed on this breakpoint
   only: a tab bar running edge to edge under the card header is the idiom
   anyway, and it buys back the width the labels need.

   The two rows also need their own hairline between them. The rule above
   strips the tab strip's border when it sits inside .fr-feed-controls, which
   is right while the two groups share one line and wrong once they stack —
   the active tab's 2px indicator would float with nothing beneath it. */
@media (max-width: 575.98px) {
  .fr-feed-controls {
    justify-content: flex-start;
  }
  /* On the gutter, unlike desktop. The tabs are equal thirds with CENTRED
     labels here, so a label's position is a function of its third, not of any
     padding — what reads as aligned is the strip's own left and right edge
     meeting the accent rule, the title and the chip row below. Going full-bleed
     (an earlier attempt at buying label width) left this the only element in
     the card ignoring the 1.5rem grid, and it showed. */
  .fr-feed-controls .fr-sort-tabs {
    padding: 0 1.5rem;
    gap: 0;
    border-bottom: 1px solid var(--bs-border-color);
  }
  /* 0.75rem buys back the width the gutter just took: three thirds of
     (360 - 48) leave ~84px of label space, and this theme's desktop 0.8125rem
     would wrap every English label to two lines inside that. Anything that
     still does not fit wraps rather than truncates — the 44px target has room
     for two centred lines. */
  .fr-sort-tab {
    padding: 0.6rem 0.35rem;
    font-size: 0.75rem;
  }
  /* Symmetric, and on this theme's own 1.5rem gutter rather than the default
     theme's 0.875rem — the shorthand there sets all four sides. */
  .fr-filter-chips {
    width: 100%;
    padding: 0.5rem 1.5rem;
  }
  .fr-filter-chips__label {
    display: none;
  }
}

/* ── Category pill & tags ────────────────────────────────────────────────── */
.fr-category-pill {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border-radius: 3px;
}
.fr-category-pill:hover {
  border-color: var(--fr-accent);
  color: var(--fr-accent-text);
}

/* ============================================================================
   PRODUCT SHELF & CARDS
   ============================================================================ */

.fr-shelf {
  gap: 1rem;
  padding: 1.25rem 1.5rem !important;
}
.fr-shelf-card {
  flex-basis: 232px;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg);
  overflow: hidden;
  background: var(--fr-surface);
  box-shadow: none !important;
  transition: border-color 0.2s var(--fr-ease), transform 0.25s var(--fr-ease),
    box-shadow 0.25s var(--fr-ease);
}
.fr-shelf-card:hover {
  border-color: var(--fr-slate-500);
  transform: translateY(-3px);
  box-shadow: var(--bs-box-shadow) !important;
}
.fr-shelf-card .ratio {
  overflow: hidden;
}
.fr-shelf-card img {
  transition: transform 0.6s var(--fr-ease);
}
.fr-shelf-card:hover img {
  transform: scale(1.06);
}
.fr-shelf-card .card-body {
  padding: 0.85rem 0.95rem 1rem !important;
}
.fr-shelf-card .fw-semibold {
  font-size: 0.9375rem !important;
  letter-spacing: -0.015em;
  line-height: 1.35;
}
@media (max-width: 575.98px) {
  /* The shelf is a 2-column wrapping grid on mobile (layout lives in the default
     theme's .fr-shelf rules; this only tunes ferum-review's card chrome for the
     narrower cards). Trimmed padding and a slightly smaller title keep each card
     near a thumb-friendly height so two columns of them don't run tall. */
  .fr-shelf {
    gap: 0.6rem;
    padding: 0.85rem 0.9rem !important;
  }
  .fr-shelf-card .card-body {
    padding: 0.6rem 0.65rem 0.7rem !important;
  }
  .fr-shelf-card .fw-semibold {
    font-size: 0.875rem !important;
  }
}

/* The shelf's grid, its one-row resting size and the show-more control at its
   foot all live in the default theme and are inherited unchanged — this theme
   only re-skins the cards above. */

.fr-product-card {
  border-radius: var(--bs-border-radius-lg);
  overflow: hidden;
  transition: border-color 0.2s var(--fr-ease), transform 0.25s var(--fr-ease),
    box-shadow 0.25s var(--fr-ease);
}
.fr-product-card:hover {
  border-color: var(--fr-slate-500);
  box-shadow: var(--bs-box-shadow);
}
.fr-product-card img {
  transition: transform 0.6s var(--fr-ease);
}
.fr-product-card:hover img {
  transform: scale(1.05);
}
.fr-product-name {
  font-size: 0.9375rem;
  letter-spacing: -0.015em;
}

/* ── Stat band — the numbers should feel like a report cover ─────────────── */
.fr-stat-band {
  padding: 0.5rem 0 !important;
}
.fr-stat-band-item .h4 {
  font-size: 2rem;
  font-weight: 750;
  letter-spacing: -0.04em;
  line-height: 1;
}
.fr-stat-band-item .small {
  font-size: var(--fr-label-size) !important;
  font-weight: 600;
  letter-spacing: var(--fr-label-tracking);
  text-transform: uppercase;
  margin-top: 0.4rem;
  color: var(--bs-tertiary-color) !important;
}
.fr-stat-band-item + .fr-stat-band-item {
  border-left-color: var(--bs-border-color);
}

/* ============================================================================
   PRODUCT DETAIL
   ============================================================================ */

/* Let the material photograph carry the page — drop the border, keep a hairline
   and give it a generous frame. */
.fr-product-thumb {
  background: var(--fr-surface-sunken);
  border: 1px solid var(--fr-hairline) !important;
  border-radius: var(--bs-border-radius-lg) !important;
  overflow: hidden;
}
.fr-product-thumb img {
  transition: transform 0.7s var(--fr-ease);
}
.fr-product-thumb:hover img {
  transform: scale(1.04);
}

/* ── Missing photography: architectural hatch, not a grey void ───────────────
   On a materials board the photograph IS the product, so an empty image slot is
   the most damaging thing on the page — it reads as broken. Rather than a grey
   box with a faded glyph, fill it the way a drawing hatches an unspecified
   material: 45° rules at low contrast. That reads as deliberate instead of
   missing. A real <img> paints straight over it, so this costs nothing when
   photography does exist. */
.fr-product-thumb,
.fr-shelf-card .ratio,
.fr-product-card .ratio {
  background-color: var(--fr-surface-sunken);
  background-image: repeating-linear-gradient(
    45deg,
    rgba(15, 23, 42, 0.055) 0 1px,
    rgba(15, 23, 42, 0) 1px 9px
  );
}
[data-bs-theme="dark"] .fr-product-thumb,
[data-bs-theme="dark"] .fr-shelf-card .ratio,
[data-bs-theme="dark"] .fr-product-card .ratio {
  background-image: repeating-linear-gradient(
    45deg,
    rgba(255, 255, 255, 0.05) 0 1px,
    rgba(255, 255, 255, 0) 1px 9px
  );
}
/* The fallback glyph was at 25% opacity — close to invisible, which is what
   made the slot look empty rather than pending. */
.fr-product-thumb .fa-couch,
.fr-shelf-card .fa-couch,
.fr-product-card .fa-couch {
  opacity: 0.34 !important;
  color: var(--bs-tertiary-color);
}

.pdp-thumbs {
  gap: 0.6rem;
}
.pdp-thumb {
  width: 68px;
  height: 68px;
  border-width: 1px;
  border-color: var(--bs-border-color);
  border-radius: var(--bs-border-radius);
}
.pdp-thumb:hover {
  border-color: var(--fr-slate-500);
}
.pdp-thumb.is-active {
  border-color: var(--fr-accent);
  box-shadow: 0 0 0 2px rgba(217, 119, 6, 0.22);
}

/* ── Spec datasheet ──────────────────────────────────────────────────────────
   Backs the structured grid in templates/catalog/product.html. Hairline rules
   and uppercase micro-labels; deliberately reads as a technical datasheet. */
.fr-spec {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1px;
  margin: 0 0 1.5rem;
  background: var(--bs-border-color); /* gap colour = hairline rules */
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  overflow: hidden;
}
@media (min-width: 576px) {
  .fr-spec {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
.fr-spec-item {
  background: var(--fr-surface);
  padding: 0.8rem 1rem;
  min-width: 0;
}
.fr-spec-item--wide {
  grid-column: 1 / -1;
}
.fr-spec-label {
  margin: 0 0 0.3rem;
  font-size: var(--fr-label-size);
  font-weight: 700;
  letter-spacing: var(--fr-label-tracking);
  text-transform: uppercase;
  color: var(--bs-tertiary-color);
}
.fr-spec-value {
  margin: 0;
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--bs-emphasis-color);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
  min-width: 0;
  line-height: 1.4;
}
/* The hover-underline is for plain links in a spec cell (brand, category).
   `.fr-review-chip` must be excluded: a chip is a pill bordered on all four
   sides, and this selector — one point more specific than `.fr-review-chip` —
   would repaint its bottom edge transparent, leaving the pill visibly open at
   the bottom and, on hover, amber on one edge and grey on the other three.
   Chips already have their own hover from `.fr-review-chip--link:hover`. */
.fr-spec-value a:not(.fr-review-chip) {
  color: var(--bs-emphasis-color);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 0.15s var(--fr-ease), color 0.15s var(--fr-ease);
}
.fr-spec-value a:not(.fr-review-chip):hover {
  color: var(--fr-accent-text);
  border-bottom-color: var(--fr-accent);
  text-decoration: none;
}
.fr-spec-brand-logo {
  width: 22px;
  height: 22px;
  border-radius: 3px;
  object-fit: cover;
  flex-shrink: 0;
}
.fr-spec-verified {
  color: var(--bs-success);
  font-size: 0.85rem;
}

/* Price gets the largest non-heading treatment on the page */
.col-md-7 > .fs-5.fw-semibold {
  font-size: 1.75rem !important;
  font-weight: 750 !important;
  letter-spacing: -0.035em;
  color: var(--bs-emphasis-color);
  margin-bottom: 1.25rem !important;
  font-variant-numeric: tabular-nums;
}

/* ============================================================================
   RATING
   ============================================================================ */

.fr-rating .fa-star,
.fr-rating .fa-star-half-stroke {
  color: var(--fr-accent) !important;
}
/* The default theme hard-codes #f5a623 on picked stars in the review composer */
.fr-star-btn .fa-solid {
  color: var(--fr-accent);
}
.fr-star-btn .fa-regular {
  color: var(--bs-border-color);
}

.progress {
  background-color: #e6e1d7;
  border-radius: 999px;
}
[data-bs-theme="dark"] .progress {
  background-color: #1b2740;
}
.progress-bar.bg-warning {
  background-color: var(--fr-accent) !important;
}

.fr-histo-row {
  gap: 0.7rem;
  padding: 0.16rem 0;
  font-size: 0.75rem;
}
.fr-histo-label,
.fr-histo-count {
  color: var(--bs-tertiary-color);
  font-weight: 600;
}

/* The headline score — big, tight, tabular. */
.fr-highlights .display-6 {
  font-size: 3.25rem;
  font-weight: 750;
  letter-spacing: -0.045em;
  font-variant-numeric: tabular-nums;
}
.fr-highlight-pill {
  border-radius: var(--bs-border-radius);
  font-weight: 600;
  padding: 0.5rem 0.85rem;
  font-size: 0.8125rem;
}
.fr-highlight-pill .fr-highlight-val {
  margin-left: 0.2rem;
  padding: 0.05rem 0.45rem;
  border-radius: 999px;
  background: rgba(15, 23, 42, 0.08);
  font-variant-numeric: tabular-nums;
}

/* ── Chips ───────────────────────────────────────────────────────────────── */
.fr-review-chip {
  background: transparent;
  border-color: var(--bs-border-color);
  color: var(--bs-secondary-color);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.045em;
  text-transform: uppercase;
  padding: 0.2rem 0.55rem;
}

/* ============================================================================
   THREADS & POSTS
   ============================================================================ */

/* The opening post of a review thread is the verdict — mark it with an amber
   rule so it reads as the primary claim, replies as commentary on it. */
.fr-post-op {
  border-left: 2px solid var(--fr-accent);
}
.fr-post-best-answer {
  border-left: 2px solid var(--bs-success);
}
.fr-best-answer-header {
  background: var(--bs-success-bg-subtle);
  color: var(--bs-success-text-emphasis);
  font-weight: 600;
  letter-spacing: 0.02em;
}
/* The one place the editorial size belongs: the review itself. Nothing here is
   a fixed-width component, so enlarging it cannot overflow a layout. */
.post-content {
  font-size: 1.0625rem;
  line-height: 1.75;
}
.post-content h2,
.post-content h3 {
  margin-top: 1.75rem;
  margin-bottom: 0.6rem;
}
.post-content img {
  border-radius: var(--bs-border-radius);
}
.post-content blockquote {
  border-left: 2px solid var(--fr-accent);
  padding-left: 1rem;
  color: var(--bs-secondary-color);
  font-style: normal;
}

.fr-thread-hero {
  border-radius: var(--bs-border-radius-lg);
  overflow: hidden;
}

/* ── Trust badge ─────────────────────────────────────────────────────────── */
.fr-trust-badge {
  background: transparent;
  color: var(--bs-tertiary-color);
  border: 1px solid var(--bs-border-color);
  font-weight: 600;
  font-size: 0.62rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* ============================================================================
   CONTROLS
   ============================================================================ */

.btn {
  letter-spacing: -0.005em;
  font-weight: 550;
  border-radius: var(--bs-border-radius);
}
.btn-primary {
  --bs-btn-hover-bg: var(--ferum-primary-dark);
  --bs-btn-hover-border-color: var(--ferum-primary-dark);
}
/* A dedicated amber CTA for "write a review"-class actions. Amber + slate text
   is 5.9:1; amber + white would be 3.6:1, so the label stays dark. */
.btn-accent {
  --bs-btn-bg: var(--fr-accent);
  --bs-btn-border-color: var(--fr-accent);
  --bs-btn-color: var(--fr-slate-900);
  --bs-btn-hover-bg: var(--fr-amber-700);
  --bs-btn-hover-border-color: var(--fr-amber-700);
  --bs-btn-hover-color: #fff;
  --bs-btn-active-bg: var(--fr-amber-800);
  --bs-btn-active-border-color: var(--fr-amber-800);
  --bs-btn-active-color: #fff;
  font-weight: 650;
  letter-spacing: 0.005em;
}
.btn-lg {
  font-size: 1rem;
  padding: 0.75rem 1.5rem;
}

/* Search page submit — recoloured to the amber accent so it matches .btn-accent,
   the theme's CTA fill, rather than the default theme's slate primary. Amber fill
   + slate text is the same accessible pairing .btn-accent uses. */
.fr-search-submit {
  background: var(--fr-accent);
  border-color: var(--fr-accent);
  color: var(--fr-slate-900);
}
.fr-search-submit:hover {
  background: var(--fr-amber-700);
  border-color: var(--fr-amber-700);
  color: #fff;
}

:focus-visible {
  outline-color: var(--fr-accent);
}

/* ── Tables ──────────────────────────────────────────────────────────────── */
.table {
  --bs-table-bg: var(--fr-surface);
  --bs-table-border-color: var(--bs-border-color);
  font-variant-numeric: tabular-nums;
}
.table > thead {
  background: var(--fr-surface-sunken);
}
.table > thead th {
  font-size: var(--fr-label-size);
  font-weight: 700;
  letter-spacing: var(--fr-label-tracking);
  text-transform: uppercase;
  color: var(--bs-tertiary-color);
}

/* ── Mobile bottom nav ───────────────────────────────────────────────────── */
.fr-bottom-nav {
  background: var(--fr-surface);
  border-top: 1px solid var(--bs-border-color);
}
.fr-bn-cta {
  background: var(--fr-accent);
  color: var(--fr-slate-900);
}
.fr-fab {
  background: var(--fr-accent);
  color: var(--fr-slate-900);
  box-shadow: var(--bs-box-shadow-lg);
}
.fr-fab:hover {
  background: var(--fr-amber-700);
  color: #fff;
}

/* ============================================================================
   ACCESSIBILITY
   ============================================================================ */

/* NF-UX-02: interactive controls in this theme's own components hold the 44px
   tap target on touch pointers, where the default theme relaxes .btn-sm to 36. */
@media (pointer: coarse) {
  .fr-spec-value a,
  .fr-filter-bar .btn,
  .fr-filter-bar .form-select,
  .fr-sort-tab,
  .fr-filter-chip {
    min-height: var(--fr-tap-target);
  }
  .pdp-thumb {
    width: 60px;
    height: 60px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .fr-thread-row:hover .fr-thread-thumb,
  .fr-shelf-card:hover img,
  .fr-shelf-card:hover,
  .fr-product-card:hover img,
  .fr-product-thumb:hover img,
  .fr-star-btn:hover {
    transform: none;
  }
}
