/* ================================================================
   Ferum Board — Default Theme  (source entry)
   Build:  npm run build  (in themes/default/)
   Output: assets/theme.css  — served by Axum unchanged
================================================================ */

/* ── Design tokens — light (default) ─────────────────────────── */

:root,
[data-bs-theme="light"] {
  /* Primary accent — dark variant derived via color-mix so any server-injected
     --ferum-primary override automatically propagates to hover/active states. */
  --ferum-primary:       #6366f1;
  --ferum-primary-dark:  color-mix(in srgb, var(--ferum-primary) 82%, #000);
  --bs-primary:          var(--ferum-primary);
  --bs-primary-rgb:      99, 102, 241;

  /* Text hierarchy — secondary/tertiary must stay ≥ 4.5:1 (WCAG AA, NF-UX-04)
     on --bs-body-bg AND on --bs-secondary-bg (chips, search box, placeholders). */
  --bs-body-color:       #111827;
  --bs-secondary-color:  #4b5563;  /* 7.6:1 on #fff */
  --bs-tertiary-color:   #687080;  /* 5.0:1 on #fff, 4.6:1 on #f3f4f6 */
  --bs-emphasis-color:   #030712;
  --bs-link-color:       var(--ferum-primary);
  --bs-link-hover-color: var(--ferum-primary-dark);

  /* Surfaces */
  --bs-body-bg:          #ffffff;
  --bs-secondary-bg:     #f3f4f6;
  --bs-tertiary-bg:      #f9fafb;

  /* Borders & shape */
  --bs-border-color:     #e5e7eb;
  --bs-border-radius:    8px;
  --bs-border-radius-sm: 6px;
  --bs-border-radius-lg: 12px;
  --bs-border-radius-xl: 16px;

  /* Shadows */
  --bs-box-shadow-sm:    0 1px 2px rgba(0, 0, 0, 0.05);
  --bs-box-shadow:       0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -1px rgba(0, 0, 0, 0.05);
  --bs-box-shadow-lg:    0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -2px rgba(0, 0, 0, 0.04);

  /* Navigation */
  --fr-nav-hover-bg:     rgba(0, 0, 0, 0.04);
  --fr-nav-active-bg:    rgba(var(--bs-primary-rgb), 0.10);
  --fr-nav-active-color: var(--ferum-primary);

  /* Layout */
  --fr-topbar-height:     56px;
  --fr-bottom-nav-height: 60px;
  --fr-tap-target:        44px;

  /* Thread cards */
  --fr-thread-title-hover: var(--ferum-primary);
}

/* ── Design tokens — dark ─────────────────────────────────────── */

[data-bs-theme="dark"] {
  /* In dark mode "dark" means lighter (more pastel) for legibility on dark bg */
  --ferum-primary:       #818cf8;
  --ferum-primary-dark:  color-mix(in srgb, var(--ferum-primary) 80%, #fff);
  --bs-primary:          var(--ferum-primary);
  --bs-primary-rgb:      129, 140, 248;

  /* Text hierarchy — same AA constraint as light mode, against #0f1117/#1a1d27. */
  --bs-body-color:       #e2e8f0;
  --bs-secondary-color:  #94a3b8;  /* 7.4:1 on #0f1117 */
  --bs-tertiary-color:   #7b8aa0;  /* 5.4:1 on #0f1117, 4.8:1 on #1a1d27 */
  --bs-emphasis-color:   #f8fafc;
  --bs-link-color:       var(--ferum-primary);
  --bs-link-hover-color: var(--ferum-primary-dark);

  --bs-body-bg:          #0f1117;
  --bs-secondary-bg:     #1a1d27;
  --bs-tertiary-bg:      #141721;
  --bs-border-color:     #1f2330;

  --fr-nav-hover-bg:     rgba(255, 255, 255, 0.06);
  --fr-nav-active-bg:    rgba(var(--bs-primary-rgb), 0.15);
  --fr-nav-active-color: var(--ferum-primary);

  --fr-thread-title-hover: var(--ferum-primary);
}

/* ── User preference selectors ────────────────────────────────── */

[data-font-size="small"]  { font-size: 87.5%; }

[data-font-size="large"]  { font-size: 112.5%; }

[data-layout="compact"] .fr-thread-row   { padding: 0.4375rem 1rem; }

[data-layout="compact"] .fr-notif-row    { padding: 0.4375rem 1rem; }

[data-layout="compact"] .fr-thread-thumb { width: 56px; height: 36px; }

/* ── Self-hosted UI font ──────────────────────────────────────────
   Inter is served from /static/fonts (not Google Fonts) so it complies
   with the CSP (style-src/font-src 'self') and keeps the app fully
   self-contained. Single variable file covers weights 400–700. */

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/static/fonts/inter-latin-variable.woff2') format('woff2');
}

/* ── Base typography ──────────────────────────────────────────── */

html {
  scroll-behavior: smooth;
}

/* The feed's sort/filter links navigate to #feed, so fragment scrolling is no
   longer a rare event on this site — which makes an unconditional smooth
   scroll a motion-sensitivity problem rather than a nicety (WCAG 2.3.3). */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  font-size: 0.9375rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

:focus-visible {
  outline: 2px solid var(--ferum-primary);
  outline-offset: 2px;
}

:target {
  scroll-margin-top: calc(var(--fr-topbar-height) + 0.5rem);
}

/* ── Bootstrap component overrides ───────────────────────────── */

/* Buttons */

.btn {
  font-weight: 500;
  letter-spacing: -0.01em;
  min-height: var(--fr-tap-target);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background-color 0.15s, border-color 0.15s, color 0.15s, box-shadow 0.15s;
}

.btn-sm  { min-height: 36px; font-size: 0.8125rem; }

.btn-xs  { min-height: 28px; font-size: 0.75rem; padding: 0 0.625rem; }

.btn-primary {
  --bs-btn-bg:           var(--ferum-primary);
  --bs-btn-border-color: var(--ferum-primary);
  --bs-btn-hover-bg:     var(--ferum-primary-dark);
  --bs-btn-hover-border-color: var(--ferum-primary-dark);
  --bs-btn-active-bg:    var(--ferum-primary-dark);
  --bs-btn-color:        #fff;
  --bs-btn-hover-color:  #fff;
  --bs-btn-active-color: #fff;
}

.btn-outline-primary {
  --bs-btn-color:        var(--ferum-primary);
  --bs-btn-border-color: var(--ferum-primary);
  --bs-btn-hover-bg:     var(--ferum-primary);
  --bs-btn-hover-color:  #fff;
  --bs-btn-active-bg:    var(--ferum-primary-dark);
}

.btn-link {
  --bs-btn-color:       var(--ferum-primary);
  --bs-btn-hover-color: var(--ferum-primary-dark);
  min-height: unset;
}

/* Cards */

.card {
  --bs-card-border-color: var(--bs-border-color);
  --bs-card-bg: var(--bs-body-bg);
  box-shadow: none;
  border-radius: var(--bs-border-radius);
}

.card-header {
  background: var(--bs-tertiary-bg);
  border-bottom-color: var(--bs-border-color);
  font-size: 0.875rem;
}

.card-footer {
  background: var(--bs-tertiary-bg);
  border-top-color: var(--bs-border-color);
}

/* Forms */

.form-control,
.form-select {
  font-size: 0.9375rem;
  border-color: var(--bs-border-color);
  background-color: var(--bs-body-bg);
  color: var(--bs-body-color);
  border-radius: var(--bs-border-radius-sm);
  transition: border-color 0.15s, box-shadow 0.15s;
}

.form-control:focus,
.form-select:focus {
  border-color: var(--ferum-primary);
  box-shadow: 0 0 0 3px rgba(var(--bs-primary-rgb), 0.12);
  background-color: var(--bs-body-bg);
  color: var(--bs-body-color);
}

[data-bs-theme="dark"] .form-control,
[data-bs-theme="dark"] .form-select {
  background-color: var(--bs-secondary-bg);
}

.form-label {
  font-weight: 500;
  font-size: 0.875rem;
  margin-bottom: 0.375rem;
}

.form-text {
  font-size: 0.8125rem;
  color: var(--bs-secondary-color);
}

.input-group-text {
  background: var(--bs-secondary-bg);
  border-color: var(--bs-border-color);
  color: var(--bs-secondary-color);
}

/* Dropdowns */

.dropdown-menu {
  --bs-dropdown-border-color: var(--bs-border-color);
  --bs-dropdown-bg: var(--bs-body-bg);
  --bs-dropdown-link-color: var(--bs-body-color);
  --bs-dropdown-link-hover-bg: var(--fr-nav-hover-bg);
  --bs-dropdown-link-hover-color: var(--bs-body-color);
  --bs-dropdown-link-active-bg: var(--fr-nav-active-bg);
  --bs-dropdown-link-active-color: var(--fr-nav-active-color);
  box-shadow: var(--bs-box-shadow-lg);
  border-radius: var(--bs-border-radius);
  font-size: 0.875rem;
  padding: 0.25rem;
  min-width: 160px;
}

.dropdown-item {
  border-radius: var(--bs-border-radius-sm);
  padding: 0.4375rem 0.625rem;
  min-height: 36px;
  display: flex;
  align-items: center;
  font-weight: 500;
  transition: background 0.1s;
}

.dropdown-header {
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--bs-tertiary-color);
  padding: 0.5rem 0.625rem 0.25rem;
}

.dropdown-divider { border-color: var(--bs-border-color); margin: 0.25rem 0; }

/* Pagination */

.page-link {
  color: var(--bs-secondary-color);
  background: var(--bs-body-bg);
  border-color: var(--bs-border-color);
  font-size: 0.875rem;
  min-width: 36px;
  text-align: center;
  transition: background 0.1s, color 0.1s;
}

.page-link:hover {
  color: var(--bs-body-color);
  background: var(--bs-secondary-bg);
  border-color: var(--bs-border-color);
}

.page-item.active .page-link {
  background: var(--ferum-primary);
  border-color: var(--ferum-primary);
  color: #fff;
}

.page-item.disabled .page-link {
  color: var(--bs-tertiary-color);
  background: var(--bs-body-bg);
}

/* Breadcrumb */

.breadcrumb { font-size: 0.8125rem; }

.breadcrumb-item a { color: var(--bs-secondary-color); text-decoration: none; transition: color 0.1s; }

.breadcrumb-item a:hover { color: var(--bs-body-color); }

.breadcrumb-item.active { color: var(--bs-tertiary-color); }

.breadcrumb-item + .breadcrumb-item::before { color: var(--bs-border-color); }

/* Modals */

.modal-content {
  border-radius: var(--bs-border-radius-lg);
  border-color: var(--bs-border-color);
  background: var(--bs-body-bg);
}

.modal-header { border-bottom-color: var(--bs-border-color); padding: 1rem 1.25rem; }

.modal-footer { border-top-color: var(--bs-border-color); padding: 0.875rem 1.25rem; }

.modal-body   { padding: 1.25rem; }

/* List groups */

.list-group-item {
  border-color: var(--bs-border-color);
  background: var(--bs-body-bg);
  color: var(--bs-body-color);
}

.list-group-item-action:hover { background: var(--fr-nav-hover-bg); color: var(--bs-body-color); }

/* ── Chip + typeahead combobox ──────────────────────────────────
   One control that holds selected chips and a search field. Used by both the
   admin catalogue modal and the public add-product dialog — the two must pick
   materials the same way. */

.fr-chip-input {
  min-height: calc(1.5em + .75rem + 2px);
  cursor: text;
}

.fr-chip-input:focus-within {
  border-color: var(--bs-primary);
  box-shadow: 0 0 0 .25rem rgba(var(--bs-primary-rgb), .25);
}

/* Result menu for the chip typeahead. The background is explicit and opaque on
   purpose: `.list-group` itself paints nothing, so any part of the menu not
   covered by a row — the strip above the first item, the gap left when the rows
   are shorter than the menu — lets the helper text underneath show through.
   No border here: `.list-group-item` already draws one on every edge, so adding
   another would double the outline. */

.fr-typeahead-menu {
  position: absolute;
  z-index: 1060;
  width: 100%;
  top: calc(100% + 2px);
  max-height: 220px;
  overflow-y: auto;
  background-color: var(--bs-body-bg);
  border-radius: var(--bs-border-radius);
  box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .175);
}

/* Flipped above the field when there is not enough room below it. Inside a
   modal the menu would otherwise hang over the footer's own buttons. */

.fr-typeahead-menu.fr-typeahead-up {
  top: auto;
  bottom: calc(100% + 2px);
}

/* Alerts */

.alert { border-radius: var(--bs-border-radius); }

/* ── Topbar ───────────────────────────────────────────────────── */

.navbar {
  height: var(--fr-topbar-height);
  background: var(--bs-body-bg) !important;
  border-bottom: 1px solid var(--bs-border-color) !important;
  box-shadow: var(--bs-box-shadow-sm);
  padding: 0 1rem;
}

.navbar .nav-link {
  color: var(--bs-secondary-color);
  font-weight: 500;
  font-size: 0.875rem;
  min-height: var(--fr-tap-target);
  display: flex;
  align-items: center;
  padding: 0 0.5rem;
  transition: color 0.15s;
}

.navbar .nav-link:hover,
.navbar .nav-link.active { color: var(--bs-body-color); }

.navbar .form-control {
  font-size: 0.875rem;
  height: 36px;
  background: var(--bs-secondary-bg);
  border-color: var(--bs-border-color);
}

.navbar .form-control:focus {
  background: var(--bs-body-bg);
  border-color: var(--ferum-primary);
  box-shadow: 0 0 0 3px rgba(var(--bs-primary-rgb), 0.12);
}

/* ── Icon button ──────────────────────────────────────────────── */

.fr-icon-btn {
  width: var(--fr-tap-target);
  height: var(--fr-tap-target);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  color: var(--bs-secondary-color);
  border-radius: var(--bs-border-radius);
  cursor: pointer;
  transition: background 0.1s, color 0.1s;
  padding: 0;
  font-size: 1rem;
  flex-shrink: 0;
}

.fr-icon-btn:hover { background: var(--bs-secondary-bg); color: var(--bs-body-color); }

.fr-icon-btn:focus-visible { outline: 2px solid var(--ferum-primary); outline-offset: 2px; }

/* ── Brand block ──────────────────────────────────────────────── */

.fr-topbar-brand {
  text-decoration: none;
  display: flex;
  align-items: center;
  gap: 0.625rem;
  flex-shrink: 0;
  min-width: 0;
}

.fr-brand-mark {
  width: 32px;
  height: 32px;
  border-radius: 7px;
  background: var(--ferum-primary);
  color: #fff;
  font-size: 0.9375rem;
  font-weight: 800;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  letter-spacing: -0.01em;
  line-height: 1;
}

.fr-brand-img {
  height: 32px;
  width: auto;
  border-radius: 6px;
  object-fit: contain;
  flex-shrink: 0;
}

.fr-brand-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.fr-brand-name {
  font-size: 0.9375rem;
  font-weight: 700;
  color: var(--ferum-primary);
  letter-spacing: -0.02em;
  line-height: 1.15;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 180px;
}

.fr-brand-slogan {
  font-size: 0.6875rem;
  font-weight: 400;
  color: var(--bs-secondary-color);
  line-height: 1.1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 180px;
}

/* ── 3-column app layout ──────────────────────────────────────── */

:root {
  --fr-sidebar-width: 220px;
}

.fr-layout {
  display: flex;
  min-height: calc(100dvh - var(--fr-topbar-height));
  max-width: 1440px;
  margin-inline: auto;
}

.fr-sidebar-left {
  width: var(--fr-sidebar-width);
  flex-shrink: 0;
  border-right: 1px solid var(--bs-border-color);
  background: var(--bs-body-bg);
  position: sticky;
  top: var(--fr-topbar-height);
  height: calc(100dvh - var(--fr-topbar-height));
  overflow-y: auto;
  padding: 0.5rem 0.5rem 2rem;
  scrollbar-width: thin;
}

.fr-sidebar-left::-webkit-scrollbar { width: 4px; }

.fr-sidebar-left::-webkit-scrollbar-thumb { background: var(--bs-border-color); border-radius: 2px; }

.fr-page {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.fr-page-inner {
  flex: 1;
  display: flex;
  gap: 1.5rem;
  padding: 1.25rem 1.5rem;
  margin: 0;
  width: 100%;
  box-sizing: border-box;
}

.fr-main-col {
  flex: 1;
  min-width: 0;
}

/* Reading pages (thread): cap the main column for a comfortable line length,
   then center the [main + rail] group so the leftover space falls into
   balanced outer margins instead of a gap between the two columns. The inner
   gutter stays tight (2rem) so the rail reads as attached to the post. */

.fr-page-inner--reading               { justify-content: center; gap: 2rem; }

.fr-page-inner--reading .fr-main-col  { max-width: 800px; }

.fr-right-col {
  width: 280px;
  flex-shrink: 0;
  display: none;            /* shown from small-laptop width up (query below) */
}

/* Bring the right rail in earlier (1100px) so its content no longer vanishes
   for the whole small-laptop range. Main stays ≥ ~560px at this width. */

@media (min-width: 1100px) {
  .fr-right-col { display: block; }
}

@media (max-width: 1199.98px) {
  .fr-right-col { width: 240px; }
}

@media (max-width: 991.98px) {
  .fr-sidebar-left { display: none !important; }
  .fr-page-inner { padding: 1rem; }
}

/* ── Mobile sidebar drawer ────────────────────────────────────── */

/* The overlay's layout lives here rather than inline on the element in
   nav.html: an inline `display:none` cannot be transitioned out of (the panel
   could only ever slide IN), and inline styles are also the one thing a child
   theme can never restyle. */

#mobileSidebar {
  display: flex;
  flex-direction: row;
  position: fixed;
  inset: var(--fr-topbar-height) 0 0 0;
  z-index: 1040;
  /* Hidden state is `visibility`, not `display`, so the panel can slide OUT as
     well as in. visibility is a DISCRETE property, so it must flip late on
     close (delay 0.26s) and immediately on open (delay 0s) — a plain
     `transition: visibility 0.26s` flips it at the 50% mark and eats half the
     animation. Do not "simplify" these delays. */
  visibility: hidden;
  transition: visibility 0s linear 0.26s;
}

#mobileSidebar.is-open {
  visibility: visible;
  transition: visibility 0s linear 0s;
}

/* Above lg the rail is back in the flow; the overlay has no reason to exist. */

@media (min-width: 992px) {
  #mobileSidebar { display: none; }
}

/* Re-enable the sidebar panel that the media query hides above   */

#mobileSidebar .fr-sidebar-left {
  display: flex !important;
  flex-direction: column;
  position: relative;   /* override sticky — parent is already fixed */
  top: 0;
  height: 100%;
  box-shadow: 4px 0 24px rgba(0, 0, 0, 0.18);
  transform: translateX(-100%);
  transition: transform 0.26s cubic-bezier(0.4, 0, 0.2, 1);
  will-change: transform;
  /* The drawer floats over page content, so it MUST be opaque — but it reuses
     .fr-sidebar-left, and a child theme may legitimately make the DESKTOP rail
     transparent (ferum-review does exactly that: the rail belongs to the canvas
     plane). This ID selector outranks any child-theme class rule, and the
     !important closes the remaining hole — a user-uploaded theme writing
     `.fr-sidebar-left{background:transparent!important}` would otherwise punch
     through and leave the drawer see-through again.
     Themes wanting a different drawer colour set --fr-drawer-bg; var
     substitution happens on the winning declaration, so the escape hatch
     survives the !important. */
  background: var(--fr-drawer-bg, var(--bs-body-bg)) !important;
}

#mobileSidebar.is-open .fr-sidebar-left {
  transform: translateX(0);
}

/* Backdrop fade */

#mobileSidebar .fr-drawer-backdrop {
  flex: 1;
  background: rgba(0, 0, 0, 0.45);
  opacity: 0;
  transition: opacity 0.26s ease;
}

#mobileSidebar.is-open .fr-drawer-backdrop {
  opacity: 1;
}

/* Body scroll lock while any drawer is open — public or admin. _admin.css
   compiles into this same asset, which admin pages load too, so one rule
   serves both. */

body.fr-drawer-open { overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
  #mobileSidebar,
  #mobileSidebar .fr-sidebar-left,
  #mobileSidebar .fr-drawer-backdrop { transition: none; }
}

/* ── Sidebar nav links (left sidebar) ────────────────────────── */

.fr-nav-link--child {
  padding-left: 1.5rem;
}

/* ── Icon column — fixed-width icon in a flex row (panel / list items) */

.fr-icon-col {
  width: 1rem;
  text-align: center;
  flex-shrink: 0;
}

.fr-nav-link-badge {
  font-size: 0.5625rem;
  font-weight: 700;
  line-height: 1;
  padding: 3px 5px;
  min-width: 16px;
}

/* ── Compact top bar (new design) ────────────────────────────── */

/* .fr-brand-mark/-img/-text/-name/-slogan already live in _navbar.css —
   this section only adds the new topbar shell + search + actions. */

.fr-topbar {
  height: var(--fr-topbar-height);
  background: var(--bs-body-bg);
  border-bottom: 1px solid var(--bs-border-color);
  position: sticky;
  top: 0;
  z-index: 1030;
  box-shadow: var(--bs-box-shadow-sm);
}

.fr-topbar-inner {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  height: 100%;
  padding: 0 1rem;
  max-width: 1440px;
  margin-inline: auto;
  /* Anchor for the mobile search dropdown (below), which is position:absolute. */
  position: relative;
}

/* .fr-topbar-brand already lives in _navbar.css */

.fr-topbar-search {
  flex: 1;
  min-width: 0; /* allow shrinking below content size on narrow viewports */
  max-width: 480px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  background: var(--bs-secondary-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  padding: 0 0.75rem;
  gap: 0.5rem;
  height: 36px;
  transition: border-color 0.15s, box-shadow 0.15s;
}

.fr-topbar-search:focus-within {
  border-color: var(--ferum-primary);
  box-shadow: 0 0 0 3px rgba(var(--bs-primary-rgb), 0.12);
  background: var(--bs-body-bg);
}

.fr-topbar-search input {
  border: none;
  background: transparent;
  outline: none;
  font-size: 0.875rem;
  color: var(--bs-body-color);
  flex: 1;
  min-width: 0;
}

.fr-topbar-search input::placeholder { color: var(--bs-tertiary-color); }

.fr-topbar-search-icon { color: var(--bs-tertiary-color); font-size: 0.8125rem; flex-shrink: 0; }

.fr-topbar-search-btn {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  display: flex;
  align-items: center;
  flex-shrink: 0;
  line-height: 1;
}

.fr-topbar-search-btn:hover .fr-topbar-search-icon { color: var(--bs-body-color); }

/* Mobile search. Below lg the inline pill cannot share the row with brand +
   actions, so it collapses to the icon button in the actions row (.fr-topbar-
   search-toggle, d-lg-none in nav.html). Tapping it adds .search-open to the
   topbar (initSearchToggle in ferum-utils.js), which drops the field as a
   full-width bar under the sticky header. Without this block the field stays
   inline and gets crushed next to the toggle — the bug where both showed at once. */

@media (max-width: 991.98px) {
  .fr-topbar-search {
    position: absolute;
    top: calc(100% + 0.4rem);
    left: 1rem;
    right: 1rem;
    max-width: none;
    margin: 0;
    height: 40px;
    display: none;
    z-index: 1035;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
  }
  .fr-topbar.search-open .fr-topbar-search { display: flex; }
}

.fr-topbar-actions {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  flex-shrink: 0;
  margin-left: auto;
}

.fr-topbar-menu-btn {
  width: var(--fr-tap-target);
  height: var(--fr-tap-target);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  color: var(--bs-secondary-color);
  cursor: pointer;
  border-radius: var(--bs-border-radius);
  font-size: 1rem;
  flex-shrink: 0;
}

.fr-topbar-menu-btn:hover { background: var(--bs-secondary-bg); color: var(--bs-body-color); }

/* ── User avatar button in topbar ────────────────────────────── */

.fr-avatar-btn {
  width: var(--fr-tap-target);
  height: var(--fr-tap-target);
  border-radius: var(--bs-border-radius);
  border: none;
  padding: 0;
  overflow: visible;
  background: transparent;
  color: #fff;
  font-weight: 700;
  font-size: 0.875rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.1s;
  flex-shrink: 0;
}

.fr-avatar-btn:hover { background: var(--bs-secondary-bg); }

.fr-avatar-btn img,
.fr-avatar-btn .fr-avatar-img {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 2px solid var(--bs-border-color);
  object-fit: cover;
  transition: border-color 0.15s;
  flex-shrink: 0;
  display: block;
}

.fr-avatar-btn:hover img,
.fr-avatar-btn:hover .fr-avatar-img { border-color: var(--ferum-primary); }

.fr-avatar-initials {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 2px solid var(--bs-border-color);
  background: var(--ferum-primary, var(--bs-primary));
  color: #fff;
  font-size: 0.8rem;
  font-weight: 700;
  line-height: 1;
  transition: border-color 0.15s;
  flex-shrink: 0;
}

.fr-avatar-btn:hover .fr-avatar-initials { border-color: var(--ferum-primary); }

/* ── Right sidebar panel row links ───────────────────────────── */

.fr-panel-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.375rem 0.875rem;
  font-size: 0.875rem;
  color: var(--bs-secondary-color);
  text-decoration: none;
  transition: background 0.1s, color 0.1s;
  min-height: 36px;
}

a.fr-panel-row:hover {
  background: var(--fr-nav-hover-bg);
  color: var(--bs-body-color);
  text-decoration: none;
}

/* ── Right sidebar panel cards ────────────────────────────────── */

.fr-panel {
  background: var(--bs-tertiary-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  overflow: hidden;
  margin-bottom: 0.75rem;
}

.fr-panel-section {
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--bs-tertiary-color);
  padding: 0.75rem 0.875rem 0.375rem;
}

.fr-panel-body {
  padding: 0.625rem 0.875rem 0.75rem;
}

/* Key-value stat row at the bottom of a panel (border-top separator) */

.fr-panel-stat {
  padding: 0.5rem 0.875rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 0.8125rem;
  color: var(--bs-secondary-color);
  border-top: 1px solid var(--bs-border-color);
}

.fr-panel-stat .fw-semibold { color: var(--bs-body-color); }

/* Legacy inline stat rows (inside panel body) */

.fr-panel-stat-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 0.8125rem;
  color: var(--bs-secondary-color);
  padding: 0.1875rem 0;
}

.fr-panel-stat-val { font-weight: 600; color: var(--bs-body-color); }

/* ── Thread feed ──────────────────────────────────────────────── */

.fr-feed {
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  overflow: hidden;
  background: var(--bs-body-bg);
  transition: opacity 0.15s ease;
}

/* In-place swap (ferum-feed.js) is pending. The 0.15s DELAY is the whole point:
   a swap that lands quickly — which is most of them — finishes before the dim
   is ever painted, so the common case shows no loading state at all and only a
   genuinely slow one gets feedback. Removing the class fades back through the
   undelayed rule above. `pointer-events` applies immediately either way, which
   is what stops a second tap racing the first. */

.fr-feed--loading {
  opacity: 0.55;
  pointer-events: none;
  transition: opacity 0.15s ease 0.15s;
}

.fr-thread-row {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 0.6875rem 1rem;
  border-bottom: 1px solid var(--bs-border-color);
  text-decoration: none;
  color: inherit;
  transition: background 0.1s;
  position: relative;
}

.fr-thread-row:last-child { border-bottom: none; }

.fr-thread-row:hover { background: var(--bs-tertiary-bg); text-decoration: none; color: inherit; }

.fr-thread-row--pinned {
  border-left: 3px solid var(--ferum-primary);
  background: color-mix(in srgb, var(--ferum-primary) 5%, var(--bs-body-bg));
}

.fr-thread-row--pinned:hover {
  background: color-mix(in srgb, var(--ferum-primary) 8%, var(--bs-tertiary-bg));
}

.fr-thread-thumb {
  width: 80px;
  height: 50px;
  object-fit: cover;
  border-radius: 4px;
  flex-shrink: 0;
  margin-top: 2px;
  align-self: flex-start;
}

.fr-thread-body  { flex: 1; min-width: 0; }

.fr-thread-title {
  font-size: 1rem;
  font-weight: 600;
  color: var(--bs-body-color);
  text-decoration: none;
  line-height: 1.45;
  transition: color 0.1s;
  word-break: break-word;
}

.fr-thread-row:hover .fr-thread-title { color: var(--fr-thread-title-hover); }

.fr-thread-excerpt {
  font-size: 0.8125rem;
  color: var(--bs-secondary-color);
  margin-top: 0.125rem;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.fr-thread-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 0.375rem;
  margin-top: 0.3rem;
  font-size: 0.8125rem;
  color: var(--bs-secondary-color);
}

.fr-thread-meta a { color: inherit; text-decoration: none; }

.fr-thread-meta a:hover { color: var(--bs-body-color); text-decoration: underline; }

.fr-thread-meta-tags {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  flex-wrap: wrap;
  margin-left: 0.125rem;
}

.fr-thread-stats {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.25rem;
  font-size: 0.8125rem;
  color: var(--bs-tertiary-color);
  flex-shrink: 0;
  white-space: nowrap;
  min-width: 52px;
}

/* ── Thread status icons ──────────────────────────────────────── */

.fr-status-icon         { font-size: 0.75rem; }

.fr-status-icon--pinned { color: var(--ferum-primary); }

.fr-status-icon--solved { color: #10b981; }

.fr-status-icon--locked { color: #f59e0b; }

/* ── Category pill ────────────────────────────────────────────── */

.fr-category-pill {
  display: inline-flex;
  align-items: center;
  padding: 0 0.5rem;
  height: 20px;
  border-radius: 10px;
  font-size: 0.75rem;
  font-weight: 500;
  background: var(--bs-secondary-bg);
  color: var(--bs-secondary-color);
  text-decoration: none;
  border: 1px solid var(--bs-border-color);
  transition: background 0.1s, color 0.1s;
  white-space: nowrap;
  line-height: 1;
}

.fr-category-pill:hover {
  background: var(--fr-nav-hover-bg);
  color: var(--bs-body-color);
  text-decoration: none;
}

/* ── Tag chips ────────────────────────────────────────────────── */

.fr-tag-chip {
  display: inline-flex;
  align-items: center;
  padding: 0 0.375rem;
  height: 18px;
  border-radius: 3px;
  font-size: 0.6875rem;
  font-weight: 500;
  background: color-mix(in srgb, var(--tag-color, #6b7280) 12%, transparent);
  color: color-mix(in srgb, var(--tag-color, #6b7280) 85%, var(--bs-body-color));
  text-decoration: none;
  border: 1px solid color-mix(in srgb, var(--tag-color, #6b7280) 22%, var(--bs-border-color));
  white-space: nowrap;
  line-height: 1;
  transition: background 0.12s;
}

.fr-tag-chip:hover {
  background: color-mix(in srgb, var(--tag-color, #6b7280) 22%, transparent);
  text-decoration: none;
}

.fr-tag-more {
  font-size: 0.6875rem;
  color: var(--bs-tertiary-color);
  font-weight: 500;
}

/* ── Collapsible category header (forum index / home feed) ────── */

.fr-cat-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.6875rem 1rem;
  background: var(--bs-tertiary-bg);
  cursor: pointer;
  user-select: none;
  transition: background 0.1s;
}

.fr-cat-header:hover { background: var(--bs-secondary-bg); }

/* Keyboard-operable collapse header — inset ring, .fr-feed clips outset */

.fr-cat-header:focus-visible {
  outline: 2px solid var(--ferum-primary);
  outline-offset: -2px;
}

/* Sort / filter tab bar */

.fr-sort-tabs {
  display: flex;
  gap: 0;
  border-bottom: 1px solid var(--bs-border-color);
  /* Kept only as a safety net for a locale long enough to overflow a narrow
     desktop column. On mobile the tabs are equal-width and cannot overflow, so
     nothing scrolls there — which is why there is no scroll-shadow trick here
     any more. The one that used to live here painted a stray vertical smudge in
     the empty space after the last tab: this element is a shrink-to-fit flex
     item, so its right edge sits mid-row, and the "cover" gradient was too soft
     to hide the shadow it was supposed to hide. */
  overflow-x: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.fr-sort-tabs::-webkit-scrollbar { display: none; }

.fr-sort-tab {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  /* 1rem, matching .fr-cat-header and .fr-thread-row: this is what puts the
     first tab's LABEL on the card's gutter. The tab strip has no padding of its
     own, so the tab's padding is the whole inset — at 0.875rem the label sat
     2px inside every other row in the card. */
  padding: 0.5rem 1rem;
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--bs-secondary-color);
  text-decoration: none;
  white-space: nowrap;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color 0.15s, border-color 0.15s;
  background: none;
  border-top: none;
  border-left: none;
  border-right: none;
  cursor: pointer;
}

.fr-sort-tab:hover { color: var(--bs-body-color); text-decoration: none; }

.fr-sort-tab--active {
  color: var(--ferum-primary);
  border-bottom-color: var(--ferum-primary);
  font-weight: 600;
}

/* Filter chips — a SECOND axis, and the visual language is different on
   purpose. The tabs are underlined and share one baseline (pick one ordering);
   the chips are pill-shaped, outlined and individually toggleable (add or
   remove a narrowing). When the two looked identical, readers could not tell
   which control would reorder the page and which would empty it. */

.fr-feed-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 1rem;
  border-bottom: 1px solid var(--bs-border-color);
}

/* The tab strip owns the underline when it sits inside the wrapper. */

.fr-feed-controls .fr-sort-tabs { border-bottom: none; }

.fr-filter-chips {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  /* Symmetric vertically, and on the gutter horizontally. Both halves were
     wrong: an asymmetric 0.25rem/0.5rem made .fr-feed-controls centre this
     group's content a couple of pixels above the tab labels beside it, and a
     right padding of 0 left the last chip flush against the card border while
     every thread row below it stopped at 1rem. On desktop this group is
     right-aligned, so the right value is the one that shows. */
  padding: 0 1rem 0 0;
  flex-wrap: wrap;
}

.fr-filter-chips__label {
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--bs-secondary-color);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.fr-filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  /* min-height keeps the 44px tap target (NF-UX-02) without inflating the
     chip's visual weight past the tab strip beside it. */
  min-height: 44px;
  padding: 0.25rem 0.75rem;
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--bs-secondary-color);
  text-decoration: none;
  white-space: nowrap;
  /* NOT --bs-border-color. That token is a hairline SEPARATOR (#e5e7eb on
     white, #1f2330 on #0f1117) and measures ~1.2:1 against the feed background
     in both themes — so the pill outline, which is the entire visual signal
     that these are a different axis from the tabs above, was invisible. WCAG
     1.4.11 wants 3:1 for a control boundary; 65% of --bs-secondary-color gives
     3.2:1 light / 3.4:1 dark and still reads as an outline, not a button. */
  border: 1px solid color-mix(in srgb, var(--bs-secondary-color) 65%, transparent);
  border-radius: 999px;
  background: none;
  transition: color 0.15s, border-color 0.15s, background-color 0.15s;
}

/* Touch has no hover and no title tooltip, so an active chip needs to say
   "tap again to remove" in the mark itself. */

.fr-filter-chip__clear { opacity: 0.75; margin-left: 0.1rem; }

.fr-filter-chip:hover {
  color: var(--bs-body-color);
  border-color: var(--bs-secondary-color);
  text-decoration: none;
}

.fr-filter-chip--on {
  color: #fff;
  background-color: var(--ferum-primary);
  border-color: var(--ferum-primary);
  font-weight: 600;
}

.fr-filter-chip--on:hover {
  color: #fff;
  filter: brightness(0.94);
}

/* ── Mobile: fixed tab bar + detached chips ──────────────────────────────
   Five controls will not fit on one line of a 360px phone — that is
   arithmetic, not a layout to be cleverer about. So they stay on two rows,
   and the work goes into making each row look deliberate and hide nothing.

   Row 1 is a FIXED (equal-width, gutter-to-gutter) tab bar, the standard mobile
   idiom for a small set of mutually exclusive orderings. It replaced a
   shrink-to-fit horizontal scroller, which is what made this area feel
   unfinished: the strip ended in the middle of the row, the third tab was
   clipped behind a hidden scrollbar at narrow widths, and it needed an edge
   shadow to hint at the overflow. Equal-width thirds cannot overflow, so all
   three sorts are always visible and none of that machinery is needed.

   Row 2 stays detached pills. A connected, full-width bar against separate
   floating pills is a stronger read of "pick one" vs "toggle each" than the
   underline-plus-pills pairing was, which is what lets the uppercase FILTER
   label drop entirely here — the word was carrying that distinction on its
   own before and the shape now carries it.

   It is dropped, NOT swapped for a funnel glyph. A funnel reads as "opens a
   filter panel", so a decorative aria-hidden one sitting flush against two
   real controls invites a tap that does nothing; and spending width on a
   group label here contradicts the line above, which hides each tab's own
   icon to buy width for its label. If the filter set ever outgrows one row,
   the funnel comes back as a real <button> opening a sheet, with a count. */

@media (max-width: 575.98px) {
  .fr-feed-controls { justify-content: flex-start; }

  .fr-sort-tabs {
    width: 100%;
    /* On the gutter. The labels are centred in equal thirds here, so what reads
       as aligned is this strip's own left and right edge meeting the card
       header and the chip row — not any label position. */
    padding: 0 1rem;
    /* No scroller on this breakpoint, so no clipped tab to hint at. */
    overflow-x: visible;
    /* The tab bar owns its own baseline here: with the rows stacked, the
       active tab's 2px indicator would otherwise float above the chip row
       with no rule to sit on. */
    border-bottom: 1px solid var(--bs-border-color);
  }

  .fr-sort-tab {
    /* Equal thirds. `0` basis, not `auto`, so a long label cannot win extra
       width and push its neighbours out of the row. */
    flex: 1 1 0;
    justify-content: center;
    text-align: center;
    /* Wrapping instead of ellipsis: Vietnamese runs ~25% longer than English
       ("Nhiều thảo luận nhất"), and two centred lines at this line-height fit
       inside the 44px target with room to spare. Truncating a control's own
       name is worse than letting it use the second line. */
    white-space: normal;
    line-height: 1.15;
    font-size: 0.75rem;
    padding: 0.375rem 0.25rem;
    /* NF-UX-02 — the chips already carried this and the tabs did not, so the
       two axes stacked at 37px and 44px. */
    min-height: var(--fr-tap-target);
  }

  /* Decorative (aria-hidden) and ~20px each, which is width these equal-width
     segments would rather spend on the label. Kept on desktop. */
  .fr-sort-tab > i { display: none; }

  /* Same 1rem gutter as the strip above and the thread rows below, so all
     three left edges agree. */
  .fr-filter-chips { width: 100%; padding: 0.5rem 1rem; }
  .fr-filter-chips__label { display: none; }
}

/* ── Thread hero banner (thumbnail as cover, thread detail page) ── */

.fr-thread-hero {
  position: relative;
  border-radius: var(--bs-border-radius-lg);
  overflow: hidden;
  background: var(--bs-secondary-bg);
}

.fr-thread-hero-img {
  display: block;
  width: 100%;
  height: 240px;
  object-fit: cover;
}

@media (max-width: 575.98px) {
  .fr-thread-hero-img { height: 180px; }
}

.fr-thread-hero-overlay {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    rgba(0, 0, 0, 0.82) 0%,
    rgba(0, 0, 0, 0.50) 45%,
    rgba(0, 0, 0, 0.10) 100%
  );
  display: flex;
  align-items: flex-end;
}

.fr-thread-hero-body {
  padding: 1.25rem 1.5rem;
  width: 100%;
}

/* ── OP (original post) card accent ───────────────────────────── */

.fr-post-op {
  border-left: 3px solid var(--ferum-primary) !important;
}

/* ── Search page ─────────────────────────────────────────────────
   Search results reuse .fr-feed / .fr-thread-row, so their styling lives with
   the feed; these are the page-specific bits (the search field, the quiet filter
   row, and the removable active-category chip). */

.fr-search-head { margin-bottom: 1.5rem; }

/* Field + trailing submit button: input and button are separate, with a gap, so
   this reads as the same idiom as the topbar's search rather than as a third
   shape. (It used to mirror .fr-hero-search, a second search field in the
   ferum-review masthead; that duplicate has since been removed.) */

.fr-search-field {
  display: flex;
  gap: 0.5rem;
  height: 48px;
}

.fr-search-field input {
  flex: 1;
  min-width: 0;
  height: 100%;
  padding: 0 1rem;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg);
  background: var(--bs-body-bg);
  color: var(--bs-body-color);
  font-size: 1rem;
  outline: none;
  transition: border-color 0.15s, box-shadow 0.15s;
}

.fr-search-field input:focus {
  border-color: var(--ferum-primary);
  box-shadow: 0 0 0 3px rgba(var(--bs-primary-rgb), 0.12);
}

.fr-search-field input::placeholder { color: var(--bs-tertiary-color); }

/* The browser's native search-clear "×" clashes with the theme — remove it. */

.fr-search-field input::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}

/* Submit — a restrained fill sized to the field, always an obvious tap target.
   Each theme recolours it to its own accent (see ferum-review's override);
   default uses the site primary. */

.fr-search-submit {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0 1.15rem;
  border: 1px solid var(--ferum-primary);
  border-radius: var(--bs-border-radius-lg);
  background: var(--ferum-primary);
  color: #fff;
  font-size: 0.9rem;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}

.fr-search-submit:hover {
  background: var(--ferum-primary-dark);
  border-color: var(--ferum-primary-dark);
  color: #fff;
}

/* Filter — secondary, quiet; it is not the point of the page. */

.fr-search-filter {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-top: 0.65rem;
}

.fr-search-filter-label {
  margin: 0;
  font-size: 0.8rem;
  color: var(--bs-tertiary-color);
  white-space: nowrap;
}

.fr-search-filter .form-select { max-width: 240px; }

/* The product tab carries four controls plus a button; they wrap on a phone
   rather than shrinking each select below a usable width. */

.fr-search-filter .form-select.w-auto { max-width: 100%; }

/* Refine toolbar — sits under the tab strip, because the tab decides which of
   these controls exist. */

.fr-search-refine { margin-bottom: 1rem; }

.fr-refine-controls {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.fr-refine-filters {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.fr-refine-filters .form-select {
  max-width: 100%;
  border-color: var(--bs-border-color);
  background-color: var(--bs-body-bg);
  color: var(--bs-body-color);
  font-size: 0.8125rem;
  border-radius: var(--bs-border-radius);
  transition: border-color 0.15s ease, background-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
}

.fr-refine-filters .form-select:hover {
  border-color: color-mix(in srgb, var(--bs-secondary-color) 40%, var(--bs-border-color));
  background-color: var(--bs-tertiary-bg);
}

.fr-refine-filters .form-select:focus {
  border-color: var(--ferum-primary);
  box-shadow: 0 0 0 3px rgba(var(--bs-primary-rgb), 0.12);
}

.fr-refine-filters .form-select.is-active {
  border-color: var(--ferum-primary);
  background-color: color-mix(in srgb, var(--ferum-primary) 6%, var(--bs-body-bg));
  color: var(--ferum-primary);
  font-weight: 600;
}

/* Sorting is contextually tied to the results grid header */

.fr-search-sort {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  line-height: 1;
}

.fr-search-filter-label {
  margin: 0;
  font-size: 0.8125rem;
  color: var(--bs-secondary-color);
  white-space: nowrap;
  line-height: 1;
}

.fr-sort-select {
  display: inline-block;
  width: auto;
  border: none !important;
  background-color: transparent !important;
  box-shadow: none !important;
  padding: 0.15rem 1.15rem 0.15rem 0.2rem !important;
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%234b5563' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e") !important;
  background-repeat: no-repeat !important;
  background-position: right 0.1rem center !important;
  background-size: 10px 10px !important;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--bs-body-color);
  cursor: pointer;
  -webkit-appearance: none !important;
  -moz-appearance: none !important;
  appearance: none !important;
  transition: color 0.15s ease;
}

.fr-sort-select:hover {
  color: var(--ferum-primary);
}

.fr-sort-select:focus {
  outline: none;
  color: var(--ferum-primary);
}

[data-bs-theme="dark"] .fr-sort-select {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%2394a3b8' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e") !important;
}

/* On a phone four dropdowns push the results a screen and a half down, so they
   collapse behind a disclosure. The toggle carries the active count: hiding
   controls is fine, hiding the fact that some are *set* is not. */

.fr-refine-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 44px;
  padding: 0 1rem;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  background: var(--bs-body-bg);
  color: var(--bs-body-color);
  font-size: 0.9rem;
  font-weight: 600;
}

@media (max-width: 767.98px) {
  .fr-refine-controls { display: none; }
  .fr-refine-controls.is-open {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.65rem;
    margin-top: 0.75rem;
    padding: 0.875rem;
    background-color: var(--bs-tertiary-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: 0.75rem;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.03);
  }
  .fr-refine-filters {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.65rem;
    width: 100%;
  }
  .fr-refine-filters .form-select {
    width: 100% !important;
    max-width: 100% !important;
    min-height: 42px; /* Thumb-friendly touch target */
    font-size: 0.875rem;
    border-radius: var(--bs-border-radius);
    background-color: var(--bs-body-bg);
  }
  .fr-search-sort {
    margin-top: 0.25rem;
  }
}

/* Active filters, as removable pills. The row exists because four dropdowns
   cannot answer "what is narrowing this page?" at a glance. */

.fr-search-chips {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
  margin: -0.5rem 0 1rem;
}

/* Quiet escape hatch next to the filter bar. A text link, not a button: it
   undoes a choice, it is not a fifth control competing with the four above. */

.fr-search-clear {
  font-size: 0.82rem;
  color: var(--bs-secondary-color);
  text-decoration: none;
  align-self: center;
}

.fr-search-clear:hover { color: var(--ferum-primary); text-decoration: underline; }

.fr-search-count { font-size: 0.9rem; color: var(--bs-secondary-color); }

/* Active category as a removable pill; doubles as the clear-filter control. */

.fr-search-catchip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.25rem 0.7rem;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--bs-body-color);
  background: var(--bs-secondary-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: 999px;
  text-decoration: none;
  transition: color 0.15s, border-color 0.15s;
}

.fr-search-catchip:hover { color: var(--ferum-primary); border-color: var(--ferum-primary); }

/* Result-kind tabs ─────────────────────────────────────────────
   Products and discussions are separate result sets, never interleaved, so the
   switch between them is a real navigation control rather than a filter chip.
   The count on each tab is the load-bearing part: it tells you what is on the
   tab you are not looking at. Horizontally scrollable so three tabs plus counts
   never wrap on a narrow phone. */

.fr-search-tabs {
  display: flex;
  gap: 0.25rem;
  margin-bottom: 1.25rem;
  border-bottom: 1px solid var(--bs-border-color);
  overflow-x: auto;
  scrollbar-width: none;
}

.fr-search-tabs::-webkit-scrollbar { display: none; }

.fr-search-tab {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  /* 44px min tap target (NF-UX-02) without padding the row out of proportion. */
  min-height: 44px;
  padding: 0 0.9rem;
  border-bottom: 2px solid transparent;
  color: var(--bs-secondary-color);
  font-size: 0.9rem;
  font-weight: 600;
  white-space: nowrap;
  text-decoration: none;
  transition: color 0.15s, border-color 0.15s;
}

.fr-search-tab:hover { color: var(--bs-body-color); }

.fr-search-tab.active {
  color: var(--ferum-primary);
  border-bottom-color: var(--ferum-primary);
}

/* Catalogue sub-nav: the tab strip shares its row with the "suggest a product"
   action, so the rule under it has to run the full width of that row. Left on
   the tabs alone it stopped where the last tab did, leaving the action floating
   over a gap and the rule looking like it had been cut short. The border moves
   to the row; the tabs keep their per-tab underline for the active state. */

.fr-catalog-nav {
  border-bottom: 1px solid var(--bs-border-color);
}

.fr-catalog-nav .fr-search-tabs {
  border-bottom: none;
  /* The row owns the spacing below now; the strip sits flush on the rule. */
  margin-bottom: -1px;
}

.fr-search-tab-count {
  padding: 0.05rem 0.45rem;
  border-radius: 999px;
  background: var(--bs-secondary-bg);
  color: var(--bs-secondary-color);
  font-size: 0.72rem;
  font-weight: 700;
  line-height: 1.5;
}

.fr-search-tab.active .fr-search-tab-count {
  background: rgba(var(--bs-primary-rgb), 0.14);
  color: var(--ferum-primary);
}

/* Section header for the mixed "All" view: names the block and offers the way
   into the full set, so the preview row never looks like the whole answer. */

.fr-search-section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}

.fr-search-section-title {
  margin: 0;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--bs-secondary-color);
}

/* Count beside the section title on the "All" view, so every section states its
   size whether or not it also has a "see all" link. */

.fr-search-section-count {
  margin-left: 0.4rem;
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--bs-tertiary-color);
}

.fr-search-section-more {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ferum-primary);
  text-decoration: none;
  white-space: nowrap;
}

.fr-search-section-more:hover { text-decoration: underline; }

/* ── Post content ─────────────────────────────────────────────── */

.post-content {
  line-height: 1.75;
  font-size: 0.9375rem;
  color: var(--bs-body-color);
  overflow-wrap: break-word;
  word-break: break-word;
}

.post-content h1, .post-content h2, .post-content h3,
.post-content h4, .post-content h5, .post-content h6 {
  margin-top: 1.5em;
  margin-bottom: 0.5em;
  font-weight: 600;
  line-height: 1.3;
}

.post-content p { margin-bottom: 0.875em; }

.post-content pre {
  background: var(--bs-secondary-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-sm);
  padding: 1rem;
  overflow-x: auto;
  font-size: 0.875em;
  line-height: 1.6;
  margin: 1em 0;
}

.post-content code {
  background: var(--bs-secondary-bg);
  border-radius: 3px;
  padding: 0.1em 0.375em;
  font-size: 0.875em;
  color: var(--bs-body-color);
}

.post-content pre code { background: none; padding: 0; font-size: inherit; }

.post-content blockquote {
  border-left: 3px solid var(--ferum-primary);
  padding: 0.5rem 1rem;
  margin: 1rem 0;
  color: var(--bs-secondary-color);
  background: rgba(var(--bs-primary-rgb), 0.04);
  border-radius: 0 var(--bs-border-radius-sm) var(--bs-border-radius-sm) 0;
}

.post-content img {
  max-width: 100%;
  height: auto;
  border-radius: var(--bs-border-radius-sm);
}

.post-content a { color: var(--bs-link-color); word-break: break-word; }

.post-content a:hover { color: var(--bs-link-hover-color); }

.post-content ul, .post-content ol { padding-left: 1.5em; margin-bottom: 0.875em; }

.post-content li { margin-bottom: 0.25em; }

.post-content hr { border-color: var(--bs-border-color); margin: 1.5rem 0; }

.post-content table {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: 1rem;
  font-size: 0.9em;
  overflow-x: auto;
  display: block;
}

.post-content th, .post-content td {
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--bs-border-color);
  text-align: left;
}

.post-content th { background: var(--bs-secondary-bg); font-weight: 600; }

/* Best answer highlight */

.fr-post-best-answer {
  border-left: 4px solid #10b981 !important;
  background: color-mix(in srgb, #10b981 5%, var(--bs-body-bg)) !important;
}

/* NOTE: .fr-best-answer-header's *effective* color/background/border-bottom-color
   are actually set by the !important duplicate in _utilities.css (later import
   wins ties among !important rules) — this block only contributes font-weight,
   which nothing else overrides. Pre-existing duplication in the shipped CSS,
   reproduced here verbatim rather than silently resolved. */

.fr-best-answer-header {
  background: color-mix(in srgb, #10b981 10%, var(--bs-body-bg));
  color: #059669;
  font-weight: 600;
  border-bottom-color: color-mix(in srgb, #10b981 20%, var(--bs-border-color));
}

[data-bs-theme="dark"] .fr-post-best-answer {
  background: color-mix(in srgb, #10b981 8%, var(--bs-body-bg)) !important;
}

[data-bs-theme="dark"] .fr-best-answer-header {
  color: #34d399;
}

/* Deep-link highlight */

.card:target {
  border-color: var(--ferum-primary) !important;
  box-shadow: 0 0 0 3px rgba(var(--bs-primary-rgb), 0.15) !important;
}

/* ── Notification feed ────────────────────────────────────────── */

.fr-notif-feed {
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  overflow: hidden;
  background: var(--bs-body-bg);
}

.fr-notif-row {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--bs-border-color);
  transition: background 0.1s;
  position: relative;
}

.fr-notif-row:last-child  { border-bottom: none; }

.fr-notif-row:hover       { background: var(--bs-tertiary-bg); }

.fr-notif-row--unread     { background: rgba(var(--bs-primary-rgb), 0.04); }

.fr-notif-icon {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--bs-secondary-bg);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.875rem;
  flex-shrink: 0;
  color: var(--bs-secondary-color);
  margin-top: 2px;
}

/* ── Notification icon colors ─────────────────────────────────── */

.fr-notif-icon--reply    { color: var(--ferum-primary); }

.fr-notif-icon--mention  { color: #0ea5e9; }

.fr-notif-icon--reaction { color: #ef4444; }

.fr-notif-icon--solved   { color: #10b981; }

.fr-notif-icon--warn     { color: #f59e0b; }

.fr-notif-icon--system   { color: var(--bs-secondary-color); }

/* ── Notification body/time text ──────────────────────────────── */

.fr-notif-body    { font-size: 0.8125rem; color: var(--bs-secondary-color); min-width: 0; }

.fr-notif-time    { font-size: 0.75rem;   color: var(--bs-tertiary-color); white-space: nowrap; }

.fr-notif-content { min-width: 0; flex: 1; }

/* ── Mobile bottom navigation ─────────────────────────────────── */

.fr-bottom-nav {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  height: var(--fr-bottom-nav-height);
  background: var(--bs-body-bg);
  border-top: 1px solid var(--bs-border-color);
  display: flex;
  align-items: stretch;
  z-index: 1030;
  padding-bottom: env(safe-area-inset-bottom, 0);
}

.fr-bn-item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  font-size: 0.625rem;
  font-weight: 500;
  color: var(--bs-tertiary-color);
  text-decoration: none;
  border: none;
  background: none;
  cursor: pointer;
  transition: color 0.1s;
  padding: 0;
  position: relative;
}

.fr-bn-item:hover,
.fr-bn-item.active { color: var(--ferum-primary); text-decoration: none; }

.fr-bn-item i { font-size: 1.1rem; line-height: 1; }

.fr-bn-badge {
  position: absolute;
  top: 6px;
  left: calc(50% + 4px);
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 8px;
  background: #ef4444;
  color: #fff;
  font-size: 0.5625rem;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}

.fr-bn-cta {
  width: 40px;
  height: 28px;
  border-radius: 14px;
  background: var(--ferum-primary);
  color: #fff !important;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1rem;
  transition: transform 0.1s;
}

.fr-bn-cta:hover { transform: scale(1.06); }

@media (max-width: 991.98px) {
  body { padding-bottom: var(--fr-bottom-nav-height); }
}

@media (min-width: 992px) {
  .fr-bottom-nav { display: none !important; }
}

/* ── Auth pages ───────────────────────────────────────────────── */

/* .fr-auth-page replaces the old .fr-auth-bg + .fr-auth-container pairing —
   it is both the full-viewport background AND the centering wrapper. */

.fr-layout--auth                  { min-height: 100dvh !important; }

.fr-layout--auth .fr-sidebar-left { display: none !important; }

.fr-layout--auth .fr-page-inner   { padding: 0; gap: 0; }

.fr-layout--auth .fr-footer       { display: none !important; }

.fr-auth-page {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 2rem 1rem;
  background:
    radial-gradient(ellipse at 65% 0%,   rgba(var(--bs-primary-rgb), 0.10) 0%, transparent 55%),
    radial-gradient(ellipse at 25% 100%, rgba(var(--bs-primary-rgb), 0.05) 0%, transparent 50%),
    var(--bs-body-bg);
}

.fr-auth-card {
  width: 100%;
  max-width: 420px;
  border-color: rgba(var(--bs-primary-rgb), 0.15) !important;
  box-shadow: var(--bs-box-shadow-lg) !important;
}

/* ── Brand above the card — vertical stack: mark → name → slogan ─ */

.fr-auth-brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  text-align: center;
  text-decoration: none;
  margin-bottom: 2rem;
}

.fr-auth-brand-mark {
  width: 52px;
  height: 52px;
  border-radius: 14px;
  background: var(--ferum-primary);
  color: #fff;
  font-size: 1.5rem;
  font-weight: 800;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  letter-spacing: -0.01em;
  line-height: 1;
  box-shadow: 0 4px 14px rgba(var(--bs-primary-rgb), 0.35);
  margin-bottom: 0.25rem;
}

.fr-auth-brand-img {
  height: 52px;
  width: auto;
  object-fit: contain;
  border-radius: 10px;
  margin-bottom: 0.25rem;
}

.fr-auth-brand-name {
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--bs-body-color);
  letter-spacing: -0.02em;
  line-height: 1.2;
}

.fr-auth-brand-slogan {
  font-size: 0.8125rem;
  font-weight: 400;
  color: var(--bs-secondary-color);
  line-height: 1.4;
  max-width: 280px;
}

/* Password show/hide toggle */

.fr-password-wrapper { position: relative; }

.fr-password-wrapper .form-control { padding-right: 2.5rem; }

.fr-password-toggle {
  position: absolute;
  right: 0.75rem;
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: none;
  color: var(--bs-secondary-color);
  cursor: pointer;
  padding: 0;
  line-height: 1;
  font-size: 0.875rem;
}

.fr-password-toggle:hover { color: var(--bs-body-color); }

/* "or" divider */

.fr-auth-divider {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  color: var(--bs-tertiary-color);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.fr-auth-divider::before,
.fr-auth-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--bs-border-color);
}

/* ── Sidebar nav ──────────────────────────────────────────────── */

.fr-nav-link {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0 0.75rem;
  min-height: 36px;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--bs-secondary-color);
  text-decoration: none;
  border-radius: var(--bs-border-radius-sm);
  margin: 0.0625rem 0;
  transition: background 0.1s, color 0.1s;
}

.fr-nav-link:hover  { background: var(--fr-nav-hover-bg); color: var(--bs-body-color); }

.fr-nav-link.active { background: var(--fr-nav-active-bg); color: var(--fr-nav-active-color); font-weight: 600; }

.fr-nav-section {
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--bs-tertiary-color);
  padding: 0.75rem 0.75rem 0.25rem;
}

/* ── Home sidebar categories panel ───────────────────────────── */

.fr-panel-header {
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--bs-tertiary-color);
  padding: 0.625rem 0.875rem;
  border-bottom: 1px solid var(--bs-border-color);
}

/* ── Right sidebar panels: section separator instead of card boxes ──
   Base .fr-layout / .fr-page-inner--reading / .fr-right-col rules live
   in _layout.css. These are refinements layered on top. */

.fr-right-col .fr-panel {
  background: transparent;
  border: none;
  border-top: 1px solid var(--bs-border-color);
  border-radius: 0;
  margin-bottom: 0;
  padding-top: 0.875rem;
  padding-bottom: 0.875rem;
}

.fr-right-col .fr-panel:first-child {
  border-top: none;
  padding-top: 0;
}

/* Stat rows: more breathing room than the 3px global default */

.fr-right-col .fr-panel-stat-row { padding: 0.3125rem 0; }

/* The panel owns the space above and below its own separator rule (0.875rem
   each). A .fr-panel-body inside it adds another 0.75rem, so a panel that ended
   in a body sat ~10px lower off its rule than one that ended in rows — 31px
   under the /forum stats against 20-22px under every link list in the same rail.
   The last body in a panel gives that back; bodies followed by something else
   keep their padding, because there it separates two blocks rather than
   doubling the panel's own margin. */

.fr-right-col .fr-panel > .fr-panel-body:last-child { padding-bottom: 0; }

/* Section labels: left accent stripe as rhythm marker + brand signal */

.fr-right-col .fr-panel-header,
.fr-right-col .fr-panel-section {
  border-bottom: none;
  border-left: 2px solid var(--ferum-primary);
  padding-left: 0.75rem;          /* 0.75rem + 2px ≈ aligned with panel body */
  padding-bottom: 0.375rem;
  color: var(--bs-secondary-color);
  font-weight: 700;
}

/* Stat values: slightly larger than label to create label → value hierarchy */

.fr-right-col .fr-panel-stat-val {
  font-weight: 700;
  font-size: 0.9375rem;
}

/* Same treatment for fr-panel-stat numbers (thread/reply counts) */

.fr-right-col .fr-panel-stat .fw-semibold {
  font-weight: 700;
  font-size: 0.9375rem;
}

/* Browse rows that carry state and depth — the catalogue rail's category list.
   A plain .fr-panel-row can say "here is a link"; it cannot say "this is the one
   you are looking at" or "this one sits under that one", and the catalogue
   taxonomy is two levels deep. Same active treatment as .fr-nav-link.active in
   the left sidebar, so "selected" looks the same on both rails. */

.fr-right-col .fr-panel-row.active {
  background: var(--fr-nav-active-bg);
  color: var(--fr-nav-active-color);
  font-weight: 600;
}

.fr-right-col .fr-panel-row--child { padding-left: 1.75rem; }

/* Nav icons in sidebar rows: primary color at 65% instead of generic gray at 50% */

.fr-right-col .fr-icon-col {
  color: var(--ferum-primary);
  opacity: 0.65;
}

.fr-right-col a.fr-panel-row:hover .fr-icon-col {
  opacity: 1;
}

/* Stat-row icons (in home community/thread stats): same treatment */

.fr-right-col .fr-panel-stat i,
.fr-right-col .fr-panel-stat-row > span:first-child i {
  color: var(--ferum-primary);
  opacity: 0.6;
}

/* Preserve explicit semantic colors (text-success, text-warning, text-danger, text-info) */

.fr-right-col .fr-panel-stat i.text-success,
.fr-right-col .fr-panel-stat-row > span:first-child i.text-success { color: var(--bs-success) !important; opacity: 1; }

.fr-right-col .fr-panel-stat i.text-warning,
.fr-right-col .fr-panel-stat-row > span:first-child i.text-warning { color: var(--bs-warning) !important; opacity: 1; }

.fr-right-col .fr-panel-stat i.text-danger,
.fr-right-col .fr-panel-stat-row > span:first-child i.text-danger  { color: var(--bs-danger)  !important; opacity: 1; }

.fr-right-col .fr-panel-stat i.text-info,
.fr-right-col .fr-panel-stat-row > span:first-child i.text-info    { color: var(--bs-info)    !important; opacity: 1; }

/* ── Profile page ─────────────────────────────────────────────── */

.fr-profile-cover {
  height: 180px;
  background: linear-gradient(140deg,
    hsl(243, 75%, 35%) 0%,
    hsl(220, 65%, 42%) 45%,
    hsl(190, 70%, 45%) 100%
  );
  border-radius: var(--bs-border-radius) var(--bs-border-radius) 0 0;
  overflow: hidden;
  position: relative;
}

.fr-profile-cover img { width: 100%; height: 100%; object-fit: cover; }

.fr-profile-avatar-wrap {
  display: inline-block;
  margin-top: -52px;
  position: relative;
  z-index: 1;
}

.fr-profile-avatar {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  border: 4px solid var(--bs-body-bg);
  background: var(--bs-secondary-bg);
  object-fit: cover;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
}

.fr-profile-avatar-placeholder {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  border: 4px solid var(--bs-body-bg);
  background: linear-gradient(135deg, var(--ferum-primary) 0%, var(--ferum-primary-dark) 100%);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2.25rem;
  font-weight: 700;
  color: #fff;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
}

/* ── Profile stat labels ──────────────────────────────────────── */

.fr-stat-label { color: var(--bs-tertiary-color); font-size: 0.875rem; }

/* ── Profile tab bar ──────────────────────────────────────────── */

.fr-profile-tab-bar {
  display: flex;
  background: var(--bs-card-bg, var(--bs-body-bg));
  border-top: 1px solid var(--bs-border-color);
}

.fr-profile-tab {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.125rem;
  /* Strip all browser button styling */
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  background: transparent;
  border: none;
  border-right: 1px solid var(--bs-border-color);
  color: var(--bs-secondary-color);
  cursor: pointer;
  transition: color 0.15s, background-color 0.15s;
  min-height: var(--fr-tap-target);
  padding: 0.75rem 0.5rem;
  font-family: inherit;
  font-size: inherit;
  line-height: 1;
}

.fr-profile-tab:last-child { border-right: none; }

/* Inset focus ring — the parent card is overflow:hidden, an outset ring would clip */

.fr-profile-tab:focus-visible {
  outline: 2px solid var(--ferum-primary);
  outline-offset: -2px;
}

.fr-profile-tab:hover {
  background-color: rgba(var(--bs-primary-rgb), 0.04);
  color: var(--bs-body-color);
}

.fr-profile-tab:active {
  background-color: rgba(var(--bs-primary-rgb), 0.08);
}

.fr-profile-tab--active {
  color: var(--ferum-primary) !important;
  background-color: rgba(var(--bs-primary-rgb), 0.06) !important;
  box-shadow: inset 0 -3px 0 var(--ferum-primary) !important;
}

.fr-profile-tab-count {
  font-size: 1.125rem;
  font-weight: 700;
  line-height: 1;
  color: var(--bs-body-color);
}

.fr-profile-tab--active .fr-profile-tab-count {
  color: var(--ferum-primary);
}

.fr-profile-tab-label {
  font-size: 0.6875rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--bs-tertiary-color);
  line-height: 1;
}

.fr-profile-tab--active .fr-profile-tab-label {
  color: var(--ferum-primary);
  opacity: 0.85;
}

/* ── Profile action buttons (Follow / Edit profile) ───────────── */

.fr-profile-action-btn {
  min-height: 36px;
  padding-inline: 1rem;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* ── Account page cover preview ──────────────────────────────── */

.fr-cover-preview {
  height: 100px;
  border: 1px solid var(--bs-border-color);
}

.fr-cover-preview-img { width: 100%; height: 100%; object-fit: cover; display: block; }

.fr-cover-placeholder {
  width: 100%; height: 100%;
  background: linear-gradient(135deg, hsl(210,55%,38%) 0%, hsl(190,65%,52%) 100%);
  padding: .5rem 1rem; text-align: center;
}

[data-bs-theme="dark"] .fr-cover-placeholder { filter: brightness(.55) saturate(1.3); }

/* ── Alpine: hide x-cloak elements until Alpine initialises ── */

[x-cloak] { display: none !important; }

/* ── Filter/search rows — all sibling controls match input height ── */

.adm-filter-row {
  display: flex;
  align-items: stretch;
  flex-wrap: wrap;
  gap: .5rem;
}

/* ── Admin shell ──────────────────────────────────────────────── */

html, body { height: 100%; margin: 0; }

.adm-shell {
  --adm-header-h: 64px;
  display: flex;
  min-height: 100vh;
}

/* ── Sidebar ──────────────────────────────────────────────────── */

.adm-sidebar {
  width: 220px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  background: var(--bs-body-bg);
  border-right: 1px solid var(--bs-border-color);
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

.adm-brand {
  display: flex;
  align-items: center;
  gap: .5rem;
  height: var(--adm-header-h);
  padding: 0 1.25rem;
  text-decoration: none;
  border-bottom: 1px solid var(--bs-border-color);
  flex-shrink: 0;
}

.adm-brand-text {
  display: flex;
  flex-direction: column;
  line-height: 1.2;
}

.adm-brand-name {
  font-weight: 700;
  font-size: .85rem;
  color: var(--bs-body-color);
}

.adm-brand-sub {
  font-size: .7rem;
  color: var(--bs-secondary-color);
}

/* ── Sidebar nav ────────────────────────────────────────────── */

.adm-nav {
  flex: 1;
  padding: .5rem .75rem 0;
  overflow-y: auto;
}

.adm-nav-label {
  display: block;
  padding: .75rem .5rem .2rem;
  font-size: .6rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--bs-secondary-color);
}

.adm-nav-label:first-child { padding-top: .35rem; }

.adm-nav-link {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .375rem .75rem;
  border-radius: .375rem;
  font-size: .8125rem;
  color: var(--bs-body-color);
  text-decoration: none;
  transition: background .1s, color .1s;
}

.adm-nav-link:hover {
  background: var(--bs-secondary-bg);
  color: var(--bs-body-color);
}

.adm-nav-link.active {
  background: var(--bs-primary-bg-subtle);
  color: var(--bs-primary);
  font-weight: 600;
}

.adm-nav-icon {
  width: .875rem;
  text-align: center;
  opacity: .7;
  flex-shrink: 0;
  font-size: .8rem;
}

.adm-nav-link.active .adm-nav-icon { opacity: 1; }

.adm-sidebar-footer {
  padding: .625rem .75rem .875rem;
  border-top: 1px solid var(--bs-border-color);
}

/* ── Main body ──────────────────────────────────────────────── */

.adm-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: var(--bs-secondary-bg);
}

.adm-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: var(--adm-header-h);
  padding: 0 1.5rem;
  background: var(--bs-body-bg);
  border-bottom: 1px solid var(--bs-border-color);
  position: sticky;
  top: 0;
  z-index: 100;
  flex-shrink: 0;
}

.adm-topbar-title {
  font-size: 1rem;
  font-weight: 600;
  margin: 0;
  color: var(--bs-body-color);
}

.adm-content { flex: 1; padding: 1.5rem; }

.adm-footer {
  font-size: .73rem;
  color: var(--bs-tertiary-color);
  flex-shrink: 0;
}

/* ── Utilities ──────────────────────────────────────────────── */

.hover-bg:hover { background: var(--bs-secondary-bg); }

/* ── Table: thead typographic style ────────────────────────── */

.adm-content thead.table-active {
  --bs-table-active-bg: transparent;
  --bs-table-bg-state: transparent;
  background-color: transparent !important;
}

.adm-content thead.table-active > tr > th {
  font-size: .6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--bs-secondary-color);
  border-bottom: 2px solid var(--bs-border-color);
  white-space: nowrap;
}

.adm-content .table > * > tr > *:first-child { padding-left: 1rem; }

.adm-content .table > * > tr > *:last-child  { padding-right: 1rem; }

.adm-content .table > tbody > tr > td {
  padding-top: .5625rem;
  padding-bottom: .5625rem;
}

/* ── Theme card preview area ────────────────────────────────── */

.adm-theme-preview {
  height: 140px;
  overflow: hidden;
  border-bottom: 1px solid var(--bs-border-color);
}

.adm-theme-preview img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ── Theme card actions ─────────────────────────────────────── */

/* Every control in the footer is a 44px tap target (NF-UX-02). */

.adm-theme-actions .btn,
.adm-theme-actions .adm-theme-status {
  min-height: 44px;
}

.adm-theme-actions .adm-icon-btn {
  min-width: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* The active theme states its status as text, never as a button: the footer
   of an active card has no action to offer, and a control shaped like a button
   that cannot be pressed reads as a broken button. */

.adm-theme-status {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  font-size: .875rem;
  font-weight: 600;
  color: var(--bs-success-text-emphasis);
}

/* Inert but still focusable. `disabled` would drop the control out of the tab
   order, so the tooltip explaining *why* it is unavailable would never reach
   keyboard or screen-reader users — the only ones who cannot hover to see it. */

.adm-theme-actions .adm-icon-btn[aria-disabled="true"] {
  opacity: .45;
  cursor: not-allowed;
}

/* The file input is visually hidden but focusable, so the label styled as a
   button needs to render the focus ring on the input's behalf. */

.adm-theme-actions input:focus-visible + .btn {
  box-shadow: 0 0 0 .25rem rgba(var(--bs-primary-rgb), .25);
}

/* ── Dashboard stat icon ────────────────────────────────────── */

.adm-stat-icon {
  width: 40px;
  height: 40px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  font-size: 1.1rem;
}

/* ── Dashboard stat card link ───────────────────────────────── */

.adm-stat-link { transition: box-shadow .15s; }

.adm-stat-link:hover { box-shadow: 0 0 0 2px var(--bs-primary); }

/* ── Dashboard today table ──────────────────────────────────── */

.adm-today-table td:first-child {
  padding-left: 1rem;
  padding-right: .5rem;
  max-width: 0;
  width: 70%;
  white-space: normal;
  word-break: break-word;
}

.adm-today-table td:last-child { padding-right: 1rem; white-space: nowrap; }

/* ── Settings section sub-label ────────────────────────────── */

.adm-section-label {
  font-size: .7rem;
  letter-spacing: .06em;
}

/* ── Modal form sections ───────────────────────────────────── */

.adm-form-section {
  border: 0;
  padding: 0;
  margin: 0 0 1.5rem;
  min-width: 0; /* fieldset defaults to min-content, which breaks flex/grid children */
}

.adm-form-section:last-child { margin-bottom: 0; }

/* Deliberately not `.text-muted`: at this size on a dark surface the muted
   token lands under the 4.5:1 contrast floor. Body colour at 85% keeps the
   caption subordinate to the labels while staying legible. */

.adm-form-legend {
  float: none;      /* UA stylesheets float <legend>, which collapses the rule below */
  width: auto;
  margin-bottom: .75rem;
  padding-bottom: .35rem;
  border-bottom: 1px solid var(--bs-border-color);
  font-size: .8125rem;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--bs-body-color);
  opacity: .85;
}

/* ── Audit log ──────────────────────────────────────────────── */

.audit-group {
  border-left: 2px solid var(--audit-group-color, var(--bs-border-color));
  padding-left: .75rem;
}

.audit-chip {
  display: inline-block;
  cursor: pointer;
  font-family: ui-monospace, 'Cascadia Code', 'Fira Mono', monospace;
  font-size: .8125rem;
  line-height: 1;
  padding: .35rem .65rem;
  border-radius: .3rem;
  border: 1px solid var(--audit-chip-border, var(--bs-border-color));
  background: transparent;
  color: var(--audit-chip-color, var(--bs-secondary-color));
  transition: background .12s, color .12s;
  white-space: nowrap;
}

.audit-chip:hover,
.audit-chip:focus-visible {
  background: var(--audit-chip-border, var(--bs-border-color));
  color: var(--bs-body-color);
  outline: none;
}

.audit-group--content { --audit-group-color: var(--bs-secondary-color); --audit-chip-border: var(--bs-secondary-color); --audit-chip-color: var(--bs-secondary-color); }

.audit-group--user    { --audit-group-color: var(--bs-danger);           --audit-chip-border: var(--bs-danger);           --audit-chip-color: var(--bs-danger); }

.audit-group--mod     { --audit-group-color: var(--bs-warning);          --audit-chip-border: var(--bs-warning);          --audit-chip-color: var(--bs-warning); }

.audit-group--admin   { --audit-group-color: var(--bs-primary);          --audit-chip-border: var(--bs-primary);          --audit-chip-color: var(--bs-primary); }

.audit-group-label {
  font-size: .6rem;
  text-transform: uppercase;
  letter-spacing: .08em;
}

.audit-ref-hint { font-size: .75rem; }

.audit-log-popover { --bs-popover-max-width: 500px; }

.audit-log-popover .popover-body { padding: 1rem; min-width: 440px; }

/* ── Sidebar drawer (below lg) ──────────────────────────────────
   `position: fixed` applies across this whole range, not just to the open
   state: the base rule is display:flex, so a merely-hidden sidebar still in
   the flow would reserve 220px of the .adm-shell row. Hidden state is
   visibility + transform rather than `display`, so the panel can slide out as
   well as in; visibility is a discrete property, hence the asymmetric 0s-delay
   transitions — see the same pattern and the same warning in _layout.css. */

@media (max-width: 991.98px) {
  .adm-sidebar {
    position: fixed;
    top: 0;
    left: 0;
    height: 100dvh;
    z-index: 1050;
    box-shadow: 0 0 24px rgba(0,0,0,.2);
    visibility: hidden;
    transform: translateX(-100%);
    transition: transform .26s cubic-bezier(.4, 0, .2, 1), visibility 0s linear .26s;
  }
  .adm-sidebar.is-open {
    visibility: visible;
    transform: none;
    transition: transform .26s cubic-bezier(.4, 0, .2, 1), visibility 0s linear 0s;
  }

  .adm-sidebar-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1049;
    background: rgba(0, 0, 0, .45);
    opacity: 0;
    visibility: hidden;
    transition: opacity .26s ease, visibility 0s linear .26s;
  }
  .adm-sidebar.is-open ~ .adm-sidebar-backdrop {
    opacity: 1;
    visibility: visible;
    transition: opacity .26s ease, visibility 0s linear 0s;
  }

  /* Keep the brand text clear of the close button. */
  .adm-brand { padding-right: 3.25rem; }
}

/* Close button — sits in the brand row. .adm-nav owns the scroll (flex:1;
   overflow-y:auto), so the brand row does not move and this stays put. Outside
   the media query above because `.d-lg-none` in the markup handles hiding it on
   desktop. */

.adm-sidebar-close {
  position: absolute;
  top: calc((var(--adm-header-h) - var(--fr-tap-target)) / 2);
  right: .5rem;
  z-index: 1;
  width: var(--fr-tap-target);
  height: var(--fr-tap-target);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: var(--bs-border-radius);
  background: transparent;
  color: var(--bs-secondary-color);
  cursor: pointer;
}

.adm-sidebar-close:hover { background: var(--bs-secondary-bg); color: var(--bs-body-color); }

.adm-sidebar-close:focus-visible { outline: 2px solid var(--ferum-primary); outline-offset: 2px; }

/* NF-UX-02: .btn-sm caps the toggle at 36px (_bootstrap-overrides.css), and the
   coarse-pointer bump below only covers table buttons. _admin.css is imported
   after _bootstrap-overrides.css, so equal specificity resolves here. */

.adm-sidebar-toggle {
  min-width: var(--fr-tap-target);
  min-height: var(--fr-tap-target);
}

@media (prefers-reduced-motion: reduce) {
  .adm-sidebar, .adm-sidebar-backdrop { transition: none; }
}

/* ── Touch target — WCAG 2.1 AA min 44px on touch devices ──── */

@media (pointer: coarse) {
  .table .btn-sm,
  .table .btn {
    min-height: 44px;
    min-width: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
}

/* ── Field group label ──────────────────────────────────────────
   A quiet subheading that splits one card into named groups of fields, so a
   form is read as "where to connect" then "who to connect as" rather than as a
   flat list of inputs. Deliberately lighter than .card-header: it must not
   compete with the card's own title, only separate what sits under it. */

.fr-field-group-label {
  font-size: 0.6875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--bs-tertiary-color);
  margin-bottom: 0.5rem;
}

/* ── Inert field group ──────────────────────────────────────────
   A group whose controls are switched off. Only the CONTROLS dim: the group
   heading and the help text stay at full contrast, because "off" is a state an
   operator reads *before* deciding whether to turn it on. An `opacity` on the
   container dimmed the explanation along with the inputs, and at 50% over an
   already-tertiary colour it was barely legible — the panel read as broken.

   `[readonly]` is listed beside `:disabled` because Bootstrap 5 gives the
   disabled look to `:disabled` only, and one input here has to stay readonly
   rather than disabled so it keeps being submitted (see ferum-admin.js). Without
   this it would be the one control in a switched-off group still looking live. */

.fr-fields-inert .form-control:disabled,
.fr-fields-inert .form-control[readonly],
.fr-fields-inert .input-group-text {
  opacity: 0.75;
}

.fr-fields-inert .form-control[readonly] {
  background-color: var(--bs-secondary-bg);
}

/* ── Site Copy language sub-tabs ────────────────────────────
   Pills inside a card header, sized down so they read as a scope switch for the
   fields below rather than as navigation competing with the page's own tab strip.
   `--bs-nav-pills-*` rather than element selectors so Bootstrap's own active,
   hover and focus states still apply untouched. */

.adm-copy-langs {
  --bs-nav-link-font-size: .8125rem;
  --bs-nav-link-padding-y: .3rem;
  --bs-nav-link-padding-x: .7rem;
  --bs-nav-pills-border-radius: 2rem;
  gap: .25rem;
  flex-wrap: wrap;
}

/* 44px tap target on touch without inflating the pills on a mouse-driven
   desktop, where they sit in a dense header. */

@media (pointer: coarse) {
  .adm-copy-langs { --bs-nav-link-padding-y: .6rem; }
}

/* The "fallback" badge, styled here rather than with utility classes so the active
   state can win on specificity instead of `!important`: on a filled pill the muted
   token would land under the contrast floor against the accent. */

.adm-copy-langs .nav-link .badge {
  background-color: var(--bs-secondary-bg);
  color: var(--bs-secondary-color);
}

.adm-copy-langs .nav-link.active .badge {
  background-color: rgba(255, 255, 255, .22);
  color: #fff;
}

/* ── Subcategory pill link ─────────────────────────────────── */

.fr-subcat-pill { color: var(--bs-secondary-color); }

.fr-subcat-pill:hover { color: var(--bs-body-color); }

/* ── Collapse chevron — category expander in forum index ─────── */

.fr-collapse-icon {
  font-size: 0.75rem;
  transition: transform 0.2s;
}

.fr-collapse-icon--closed { transform: rotate(-90deg); }

/* ── Avatar sizes ─────────────────────────────────────────────── */

.fr-avatar {
  object-fit: cover;
  flex-shrink: 0;
  display: block;
}

.fr-avatar--placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  color: #fff;
  background: var(--ferum-primary);
  flex-shrink: 0;
}

.fr-avatar--24 { width: 24px; height: 24px; font-size: 10px;    border-radius: 50%; }

.fr-avatar--32 { width: 32px; height: 32px; font-size: 12px;    border-radius: 50%; }

.fr-avatar--80 { width: 80px; height: 80px; font-size: 28px;    border-radius: 50%; }

/* ── Extra avatar sizes ───────────────────────────────────────── */

.fr-avatar--22 { width: 22px; height: 22px; font-size:  9px; border-radius: 50%; }

.fr-avatar--28 { width: 28px; height: 28px; font-size: 11px; border-radius: 50%; }

.fr-avatar--36 { width: 36px; height: 36px; font-size: 14px; border-radius: 50%; }

.fr-avatar--38 { width: 38px; height: 38px; font-size: 15px; border-radius: 50%; }

/* ── Trust badge ──────────────────────────────────────────────── */

.fr-trust-badge {
  background: rgba(var(--bs-primary-rgb), 0.1);
  color: var(--ferum-primary);
  font-size: 0.65rem;
  font-weight: 600;
}

/* ── Page layout helpers ──────────────────────────────────────── */

.fr-page-reading { max-width: 860px; margin-inline: auto; }

/* ── Footer ───────────────────────────────────────────────────── */

.fr-footer {
  background: var(--bs-tertiary-bg);
  border-top: 1px solid var(--bs-border-color);
  color: var(--bs-secondary-color);
  font-size: 0.8125rem;
  flex-shrink: 0;
}

.fr-footer-link {
  color: var(--bs-secondary-color);
  text-decoration: none;
  transition: color 0.1s;
}

.fr-footer-link:hover { color: var(--bs-body-color); }

.fr-footer-copy       { color: var(--bs-tertiary-color); }

/* Footer brand — mirrors .fr-topbar-brand at a smaller scale so the mark/logo
   never appears without the site name (unlike the old logo-only footer). */

.fr-footer-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  text-decoration: none;
  min-width: 0;
}

.fr-footer-brand .fr-brand-mark,
.fr-footer-brand .fr-brand-img {
  width: 22px;
  height: 22px;
  font-size: 0.75rem;
  border-radius: 6px;
}

.fr-footer-brand-name {
  font-weight: 600;
  color: var(--bs-body-color);
  transition: color 0.1s;
}

.fr-footer-brand:hover .fr-footer-brand-name { color: var(--ferum-primary); }

/* ── Bootstrap primary overrides ─────────────────────────────── */

.text-primary   { color: var(--ferum-primary) !important; }

.border-primary { border-color: var(--ferum-primary) !important; }

.bg-primary     { background-color: var(--ferum-primary) !important; }

/* Sits above stretched-link ::after (z-index 1) so inner links stay clickable */

.fr-link-elevated {
  position: relative;
  z-index: 2;
}

/* ── Multi-line panel list item (e.g. Popular sidebar) ────────── */

.fr-panel-item {
  display: flex;
  flex-direction: column;
  gap: 0.1875rem;
  padding: 0.5rem 0.875rem;
  border-bottom: 1px solid var(--bs-border-color);
  text-decoration: none;
  color: var(--bs-body-color);
  font-size: 0.875rem;
  transition: background 0.1s, color 0.1s;
}

.fr-panel-item:last-child { border-bottom: none; }

.fr-panel-item:hover { background: var(--fr-nav-hover-bg); text-decoration: none; color: var(--bs-body-color); }

.fr-panel-item-title {
  font-weight: 600;
  font-size: 0.8125rem;
  line-height: 1.4;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  color: var(--bs-body-color);
}

.fr-panel-item-meta {
  font-size: 0.75rem;
  color: var(--bs-tertiary-color);
}

/* ── Color dot sizes ──────────────────────────────────────────── */

.fr-color-dot {
  display: inline-block;
  border-radius: 50%;
  flex-shrink: 0;
}

.fr-color-dot--8  { width:  8px; height:  8px; }

.fr-color-dot--10 { width: 10px; height: 10px; }

/* ── Best answer card header ──────────────────────────────────── */

.fr-best-answer-header {
  background: rgba(16, 185, 129, 0.08) !important;
  color: #059669 !important;
  border-bottom-color: rgba(16, 185, 129, 0.2) !important;
}

/* ── Removable tag chip (Alpine tag input) ────────────────────── */

.fr-tag-chip--removable { gap: 0.25rem; cursor: default; }

.fr-tag-chip-remove {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  line-height: 1;
  color: inherit;
  opacity: 0.7;
  font-size: 0.875em;
}

.fr-tag-chip-remove:hover { opacity: 1; }

/* ── Tag chip input field (Alpine new-thread form) ────────────── */

.fr-tag-input-wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem;
  min-height: 38px;
  cursor: text;
  padding: 0.25rem 0.5rem;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-sm);
  background: var(--bs-body-bg);
}

.fr-tag-input-wrap:focus-within {
  border-color: var(--ferum-primary);
  box-shadow: 0 0 0 3px rgba(var(--bs-primary-rgb), 0.12);
}

.fr-tag-input-field {
  border: 0;
  background: transparent;
  outline: none;
  font-size: 0.875rem;
  color: var(--bs-body-color);
  min-width: 80px;
  flex: 1;
}

/* ── Post # permalink ─────────────────────────────────────────── */

.fr-post-num {
  font-size: 0.75rem;
  color: var(--bs-tertiary-color);
  font-weight: 500;
  text-decoration: none;
}

.fr-post-num:hover { color: var(--ferum-primary); text-decoration: none; }

/* ── Icon-only button (post ellipsis menu) ─────────────────────────
   NOTE: this is a second, later-imported .fr-icon-btn definition. Same
   specificity as _navbar.css's 44px tap-target version, so per-property
   cascade applies: width/height/border-radius/color here win (28px),
   while font-size/flex-shrink/:focus-visible from the navbar block survive
   untouched. Pre-existing duplication in the shipped CSS — reproduced
   verbatim rather than silently merged into one rule. */

.fr-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--bs-border-radius-sm);
  border: none;
  background: transparent;
  padding: 0;
  color: var(--bs-tertiary-color);
  cursor: pointer;
  text-decoration: none !important;
  transition: background 0.1s, color 0.1s;
}

.fr-icon-btn:hover { background: var(--bs-secondary-bg); color: var(--bs-body-color); }

/* Bookmark button stretches to grid width in right-rail Actions panel */

ferum-bookmark-btn.w-100 .bookmark-btn { width: 100%; }

/* ── Floating reply FAB (mobile) ──────────────────────────────── */

.fr-fab {
  position: fixed;
  bottom: calc(var(--fr-bottom-nav-height) + 1rem);
  right: 1rem;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: var(--ferum-primary);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.125rem;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
  z-index: 1025;
  text-decoration: none;
  transition: background 0.15s, transform 0.15s;
}

.fr-fab:hover { background: var(--ferum-primary-dark); color: #fff; transform: scale(1.05); text-decoration: none; }

@media (min-width: 992px) {
  .fr-fab { display: none !important; }
}

/* Smooth theme transitions on key elements only */

body,
.navbar,
.card,
.dropdown-menu,
.modal-content,
.list-group-item,
.fr-feed,
.fr-thread-row,
.fr-notif-feed,
.fr-notif-row,
.fr-footer,
.fr-bottom-nav {
  transition: background-color 0.2s ease, border-color 0.2s ease;
}

/* Last: the catalog block was authored directly into the built asset and had
   accumulated at the end of the file. Keeping it last preserves that cascade
   order exactly, so extracting it here changes nothing about what ships. */

/* ══════════════════════════════════════════════════════════════════════
   Interior-review / catalog UI
   Product catalog cards, product-detail gallery, review rating inputs,
   star histogram, and the home review widgets (stat band + top-rated shelf).
   ══════════════════════════════════════════════════════════════════════ */

/* ── Catalog product card ─────────────────────────────────────────── */

.fr-product-card {
  display: block;
  height: 100%;
  background: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  transition: border-color 0.12s ease, box-shadow 0.12s ease, transform 0.12s ease;
}

.fr-product-card:hover {
  border-color: var(--ferum-primary);
  box-shadow: var(--bs-box-shadow-sm);
  transform: translateY(-2px);
  text-decoration: none;
  color: inherit;
}

.fr-product-thumb {
  aspect-ratio: 4 / 3;
  background: var(--bs-tertiary-bg);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--bs-tertiary-color);
}

.fr-product-thumb img { width: 100%; height: 100%; object-fit: cover; }

/* No-photo state: drop the tall 4:3 box so it reads as a modest placeholder. */

.fr-product-thumb--empty {
  aspect-ratio: auto;
  min-height: 160px;
  flex-direction: column;
  gap: 0.25rem;
}

.fr-product-card-body { padding: 0.5rem 0.625rem; }

.fr-product-name {
  font-weight: 600;
  font-size: 0.85rem;
  color: var(--bs-body-color);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.fr-product-meta { font-size: 0.75rem; color: var(--bs-secondary-color); }

.fr-product-price { font-size: 0.78rem; color: var(--bs-body-color); font-weight: 500; }

.fr-rating-line { font-size: 0.72rem; }

.fr-rating { white-space: nowrap; }

/* ── Review chip (pill) ───────────────────────────────────────────── */

.fr-review-chip {
  display: inline-block;
  font-size: 0.62rem;
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
  background: var(--bs-tertiary-bg);
  border: 1px solid var(--bs-border-color);
  color: var(--bs-secondary-color);
}

/* Most `.fr-review-chip` instances are inert (the product-type badge, a
   review card's per-dimension scores) — this modifier is for the few that are
   genuinely links (a product's material chips, into /catalog?material_id=),
   so a clickable chip gives the same hover feedback as `.fr-category-pill`
   elsewhere instead of looking identical to its non-clickable neighbours. */

.fr-review-chip--link {
  transition: background 0.1s, color 0.1s;
}

.fr-review-chip--link:hover {
  background: var(--fr-nav-hover-bg);
  color: var(--bs-body-color);
}

/* ── Filter bar inside a panel (catalog) ──────────────────────────── */

.fr-filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}

/* Match the 36px min-height the theme gives .btn-sm so inputs, selects and the
   submit button line up as one even row. */

.fr-filter-bar .form-control,
.fr-filter-bar .form-select { min-height: 36px; }

/* Added by ferum-page-product.js once it confirms it can auto-submit the
   sort/filter form on change — keeps the button (and full no-JS fallback)
   in the markup while removing the redundant extra click for JS users. */

.fr-filter-bar--auto button[type="submit"] { display: none; }

/* Product-page management menu trigger (Edit/Delete) — a quiet secondary
   control, not a competing button next to the H1. "Quiet" still needs a
   visible resting affordance, though: fully transparent-until-hover reads as
   invisible rather than subtle, especially for a 3-dot glyph with almost no
   ink of its own. A faint permanent circle gives it a discoverable shape at
   rest; hover/focus only deepens the same fill. */

.btn-icon-ghost {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--bs-border-radius);
  color: var(--bs-secondary-color);
  background: var(--bs-tertiary-bg);
}

.btn-icon-ghost:hover,
.btn-icon-ghost:focus-visible {
  color: var(--bs-body-color);
  background: var(--bs-secondary-bg);
  border-color: var(--bs-border-color);
}

/* ── Star distribution histogram ──────────────────────────────────── */

.fr-histo-row { display: flex; align-items: center; gap: 0.5rem; font-size: 0.72rem; }

.fr-histo-label { width: 2.6rem; color: var(--bs-secondary-color); white-space: nowrap; }

.fr-histo-count { width: 2.2rem; text-align: end; color: var(--bs-secondary-color); }

/* ── Rating panel (product page) ──────────────────────────────────── */

/* Headline score. The stars and the number carry the same value, so the
   number is what gets the weight — the stars are the at-a-glance shape. */

.fr-rating-headline {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.5rem;
  font-size: 1.05rem;
  line-height: 1.2;
}

.fr-rating-score {
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--bs-body-color);
}

/* "/5" stays subordinate: it is the scale, not the reading. */

.fr-rating-max {
  font-size: 0.875rem;
  font-weight: 400;
  color: var(--bs-secondary-color);
}

/* Section label inside a panel body. Separates the two charts in the rating
   panel, which use different scales (% of reviewers vs score out of 5) and
   would otherwise read as one continuous series. */

.fr-subhead {
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--bs-tertiary-color);
  margin-bottom: 0.375rem;
}

/* ── Star rating input (new-thread review block) ──────────────────── */

.fr-star-input { display: inline-flex; align-items: center; gap: 0; }

.fr-star-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;            /* 44px — meets the mobile tap-target minimum */
  height: 2.75rem;
  background: none;
  border: 0;
  padding: 0;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  color: inherit;
  transition: transform 0.1s ease;
}

/* Muted outline until picked, warm gold once painted (JS swaps fa-regular↔fa-solid) */

.fr-star-btn .fa-regular { color: var(--bs-border-color); }

.fr-star-btn .fa-solid   { color: #f5a623; }

.fr-star-btn:hover { transform: scale(1.12); }

.fr-star-btn:focus-visible {
  outline: 2px solid var(--ferum-primary);
  outline-offset: -3px;
  border-radius: 8px;
}

.fr-star-input--lg .fr-star-btn { width: 3rem; height: 3rem; font-size: 1.7rem; }

.fr-rate-label { font-size: 0.875rem; font-weight: 500; color: var(--bs-body-color); }

.fr-rate-overall .fr-rate-label { min-width: 5.5rem; font-weight: 600; }

.fr-rate-row { flex-wrap: wrap; }

.fr-rate-row .fr-rate-label { flex-shrink: 0; }

.fr-rate-hint { min-width: 6.5rem; }

/* ── Staged photo thumbnails (product forms) ──────────────────────── */

/* Used by the public add-product dialog and the admin catalogue modal for
   photos picked but not yet uploaded, and for a saved product's gallery. */

.fr-thumb {
  position: relative;
  width: 72px;
  height: 72px;
  flex: 0 0 auto;
}

.fr-thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  background: var(--bs-tertiary-bg);
}

/* Deliberately not a `.btn`: the theme floors every button at a 36–44px
   min-height, which an inline `height` cannot override — that is what turned
   this control into a tall red pill hanging off the corner of the image. As
   its own element it can be the small round badge it should be. */

.fr-thumb-remove {
  position: absolute;
  top: 3px;
  right: 3px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  font-size: 0.6875rem;
  line-height: 1;
  color: #fff;
  background: rgba(0, 0, 0, 0.55);
  cursor: pointer;
  transition: background-color 0.12s ease, transform 0.12s ease;
}

.fr-thumb-remove:hover { background: var(--bs-danger); transform: scale(1.1); }

.fr-thumb-remove:focus-visible {
  background: var(--bs-danger);
  outline: 2px solid var(--ferum-primary);
  outline-offset: 1px;
}

/* A 44px tap target without a 44px dot: the visible badge stays small and an
   invisible pseudo-element carries the touch area. */

@media (pointer: coarse) {
  .fr-thumb-remove::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: var(--fr-tap-target);
    height: var(--fr-tap-target);
    transform: translate(-50%, -50%);
  }
}

/* ── Home: product shelf ("top rated") ────────────────────────────────
   A responsive grid at every width — it wraps, it never scrolls sideways.

   At rest the shelf is clipped to one row plus a slice of the next, by height —
   NOT by hiding overflow cards with `:nth-child`. Two reasons the height clip
   won:

   1. The visible slice IS the affordance. A row cut mid-card tells the reader
      there is more far better than any button can, because they see the product
      images continue past the fold. Cards removed with `display:none` leave a
      clean edge that looks like the end of the content.
   2. `display:none` made the count state-dependent. JS had to measure which
      cards were hidden, which only works while collapsed — and measuring at the
      wrong moment during a collapse zeroed the count and made the control
      vanish. With a height clip every card is always in flow, so the count is
      just `cards - columns`: true in either state, impossible to race.

   The clip height is set inline by productShelf() (ferum-page-home.js) from
   measured geometry, since row height depends on the card's content and images.
   Without JS no clip is applied and the whole grid shows — content is never
   locked away behind a script that failed to load. */

.fr-shelf {
  display: grid;
  gap: 0.6rem;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.fr-shelf-card { flex: initial; }

@media (min-width: 768px) {
  .fr-shelf { gap: 0.75rem; grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (min-width: 992px) {
  .fr-shelf { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

@media (min-width: 1400px) {
  .fr-shelf { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}

/* Collapsed — overflow cards are removed from flow, not clipped.
   productShelf() puts this class on every card from index `columns` onward, so
   the shelf resolves to exactly one row with no arithmetic involved. Clipping
   the container to a measured height was tried first and leaked a few pixels of
   the next row above the control: that cut is a sum of row height, paddings and
   gap, all of which shift per breakpoint and carry subpixel error, so it was
   only ever approximately right. This is exact everywhere.

   Deliberately no fade/peek either. The button below already says there is more
   AND says how many, so a fading slice was a second, weaker voice for the same
   message — and it made the shelf look broken whenever the next row held a
   single card beside empty columns. */

/* Two classes on purpose. The card also carries Bootstrap's `.card`, which sets
   `display:flex` at the same specificity — so a single-class rule here would win
   only because this sheet happens to load after bootstrap.min.css. That is a
   coincidence of link order, not a guarantee, and a theme that loads its own CSS
   differently would silently un-hide every overflow card. */

.fr-shelf .fr-shelf-card--hidden {
  display: none;
}

/* ── Show-more control ────────────────────────────────────────────────
   Sits at the bottom edge of the shelf, on the seam where the grid is cut off,
   so the arrow points at the rows it reveals. It deliberately does NOT live in
   the section header: a chevron up there is already spoken for — /forum uses it
   to collapse a section — and the same icon meaning two different things in one
   app is worse than no icon at all.

   The control removes itself when the catalogue is smaller than one row (see
   productShelf() in ferum-page-home.js), so it never offers to reveal nothing. */

.fr-shelf-more { display: block; }

/* A full-width strip, not a bordered chip. The chip version carried the same
   `btn-outline-secondary` chrome as "view all" in the header — two identical
   buttons in one panel meaning different things (expand in place vs. leave the
   page), which costs the reader a beat to tell apart every time.
   Dropping to a ghost strip settles it: the header keeps the only bordered
   button, and this reads as "the bottom edge of this panel is actionable".
   It also lands the biggest possible target on the shortest path — directly
   under the fade, spanning the panel — without putting the click handler ON the
   faded row, whose cards are real product links that a handler there would
   hijack. Carries no `btn` class, so every visual comes from here. */

.fr-shelf-more-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  width: 100%;
  min-height: var(--fr-tap-target, 44px);
  padding: 0.75rem 1rem;
  border: 0;
  border-top: 1px solid var(--bs-border-color);
  border-radius: 0;
  background: transparent;
  color: var(--bs-secondary-color);
  font-family: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}

.fr-shelf-more-btn:hover {
  background: var(--bs-tertiary-bg);
  color: var(--bs-body-color);
}

/* Inset so the ring is not clipped by .fr-feed's overflow:hidden. */

.fr-shelf-more-btn:focus-visible {
  outline: 2px solid var(--ferum-primary);
  outline-offset: -2px;
}

.fr-shelf-more-icon { transition: transform 0.2s; }

.fr-shelf-more-icon--up { transform: rotate(180deg); }

/* The product name is the differentiator and its tail carries the variant
   ("… bọc nỉ Scandinavian 3 chỗ"), so it gets two lines rather than one
   truncated one. Two is a cap, not a reservation: a short name stays short. */

.fr-shelf-card-name {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ── Home: market-data stat band ──────────────────────────────────── */

.fr-stat-band { display: flex; flex-wrap: wrap; align-items: center; }

.fr-stat-band-item {
  flex: 1 1 0;
  min-width: 5rem;
  text-align: center;
  padding: 0 0.75rem;
}

.fr-stat-band-item + .fr-stat-band-item { border-left: 1px solid var(--bs-border-color); }

/* ── Product detail: thumbnail gallery ────────────────────────────── */

.pdp-thumbs { display: flex; gap: 0.5rem; flex-wrap: wrap; }

.pdp-thumb {
  width: 64px;
  height: 64px;
  padding: 0;
  border: 2px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-sm);
  overflow: hidden;
  background: var(--bs-tertiary-bg);
  cursor: pointer;
  transition: border-color 0.12s ease;
}

.pdp-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

.pdp-thumb:hover { border-color: var(--bs-secondary-color); }

.pdp-thumb.is-active { border-color: var(--ferum-primary); }

.pdp-thumb:focus-visible { outline: 2px solid var(--ferum-primary); outline-offset: 2px; }

/* ── Product detail: review highlights summary ────────────────────── */

.fr-highlight-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.4rem 0.75rem;
  border-radius: 999px;
  font-size: 0.85rem;
  line-height: 1.2;
}

.fr-highlight-pill i { font-size: 0.8rem; }

.fr-highlight-pill .fr-highlight-val { font-weight: 700; }
