/* =========================================================================
   BRIVOY — Design System v2
   Minimalist, mobile-first. Chrome is quiet; content and typography carry
   the page. Dark "mission control" palette kept, but panels/borders are
   used sparingly instead of boxing everything.
   ========================================================================= */

/* Site-wide "min-width-0" utility used throughout the markup (product
   rows, nav dropdowns, aside cards) to let a flex child shrink/truncate
   its own text instead of forcing the row to overflow or wrap. Bootstrap
   5 does NOT actually ship a min-width-0 utility class (only min-vw-100 /
   min-vh-100), so every use of class="min-width-0" was previously a
   silent no-op — this is the real, load-bearing definition. */
.min-width-0 { min-width: 0; }

/* =========================================================================
   THEME — light mode is the default (:root). Dark mode is the original
   "mission control" navy palette, applied via [data-theme="dark"] on
   <html> (set server-side from the user's saved/cookie preference, and
   flipped instantly client-side by the navbar toggle — see main.js).
   Every rule below reads these variables, so components don't need a
   separate light/dark copy.
   ========================================================================= */
:root {
  --lb-navy-950: #FFFFFF;
  --lb-navy-900: #F5F6FA;
  --lb-navy-800: #E7E9F2;
  --lb-navy-700: #D6D9E8;
  --lb-navy-600: #C7CBDF;
  --lb-mist: #5C6284;
  --lb-white: #12162E;
  --lb-lime: #3E8914;
  --lb-lime-dark: #2F6B0F;
  --lb-coral: #E14F2A;
  --lb-coral-dark: #C23D1C;

  /* RGB triplets (no #, no rgb()) for translucent surfaces like the
     sticky top bar / bottom nav, which need rgba(var(--x), alpha). */
  --lb-navy-950-rgb: 255, 255, 255;
  --lb-navy-900-rgb: 245, 246, 250;

  /* Constant dark ink for text/icons drawn ON TOP of the lime accent
     (buttons, the vote-button "voted" state, the FAB, etc). This does
     NOT flip with the theme — it needs to stay dark in both modes for
     contrast against the (always-bright) lime background. */
  --lb-ink: #0B0E1F;

  --lb-font-display: 'Space Grotesk', 'Segoe UI', sans-serif;
  --lb-font-body: 'Inter', 'Segoe UI', sans-serif;
  --lb-font-mono: 'JetBrains Mono', 'Courier New', monospace;

  --lb-space-1: .25rem;
  --lb-space-2: .5rem;
  --lb-space-3: .75rem;
  --lb-space-4: 1rem;
  --lb-space-5: 1.5rem;
  --lb-space-6: 2rem;

  --lb-radius-sm: .5rem;
  --lb-radius-md: .75rem;
  --lb-radius-lg: 1.1rem;

  --lb-topbar-h: 56px;
  --lb-bottomnav-h: 60px;

  --bs-body-bg: var(--lb-navy-950);
  --bs-body-color: var(--lb-white);
  --bs-body-font-family: var(--lb-font-body);
  --bs-link-color: var(--lb-lime);
  --bs-link-hover-color: var(--lb-lime-dark);
  --bs-border-color: var(--lb-navy-800);

  /* Award medal palette (gold/silver/bronze) — kept as its own set of
     theme-aware variables rather than the translucent-on-navy rgba()
     values used elsewhere, because those were tuned to sit on a dark
     panel and turn washed-out/illegible once light mode (the default
     theme) puts them on a white background instead. Light mode gets
     deeper, more saturated tones for real contrast; dark mode keeps
     the original soft glow look. */
  --lb-gold: #92660A;
  --lb-gold-bg: #FFF4D6;
  --lb-gold-border: #E8B930;
  --lb-silver: #4B5170;
  --lb-silver-bg: #EEF0F7;
  --lb-silver-border: #C3C9DE;
  --lb-bronze: #8A4B1E;
  --lb-bronze-bg: #FBE7D6;
  --lb-bronze-border: #D98C4A;

  /* Editor's Choice award — curatorial pick, kept visually distinct from
     the gold/silver/bronze votes-ranked medals (purple, matching the
     homepage "FEATURED" showcase these picks come from). Same
     light/dark theme-aware treatment as the medal palette above. */
  --lb-curated: #6B3FA0;
  --lb-curated-bg: #F1E9FB;
  --lb-curated-border: #C9A6EC;
}

/* ---- Dark mode ("mission control") — original palette ---- */
[data-theme="dark"] {
  --lb-navy-950: #0B0E1F;
  --lb-navy-900: #10142B;
  --lb-navy-800: #171C3A;
  --lb-navy-700: #232A52;
  --lb-navy-600: #333c6b;
  --lb-mist: #9BA3C7;
  --lb-white: #F6F7FB;
  --lb-lime: #C7F464;
  --lb-lime-dark: #9FD53A;
  --lb-coral: #FF6B4A;
  --lb-coral-dark: #E5502F;

  --lb-navy-950-rgb: 11, 14, 31;
  --lb-navy-900-rgb: 16, 20, 43;

  --lb-gold: #FFD166;
  --lb-gold-bg: rgba(255,209,102,0.16);
  --lb-gold-border: rgba(255,209,102,0.45);
  --lb-silver: #D8DEE9;
  --lb-silver-bg: rgba(216,222,233,0.18);
  --lb-silver-border: rgba(216,222,233,0.4);
  --lb-bronze: #D98C4A;
  --lb-bronze-bg: rgba(217,140,74,0.18);
  --lb-bronze-border: rgba(217,140,74,0.4);

  --lb-curated: #B98CFF;
  --lb-curated-bg: rgba(185,140,255,0.18);
  --lb-curated-border: rgba(185,140,255,0.45);
}

/* Bootstrap's own utility hardcodes #fff; redirect it to follow the theme
   instead — every use of .text-white in this app is "primary foreground
   text on a themed surface", never literal white-on-color. */
.text-white { color: var(--lb-white) !important; }

/* overflow-x: clip (not hidden) so full-bleed elements like the mobile
   hero video can't ever cause a horizontal scrollbar, without creating a
   new scroll container that would break position:sticky (topbar, aside
   promo card) the way overflow-x: hidden here would. */
html { overflow-x: clip; }
html, body { background: var(--lb-navy-950); color: var(--lb-white); }

/* =========================================================================
   SKELETON SHIMMER — a lightweight loading placeholder for every image on
   the site (product logos, thumbnails, galleries, avatars, blog covers,
   the video poster, etc). Applied generically by initImageSkeletons() in
   main.js, which runs on every page load and again on any content it
   injects dynamically afterwards (mobile promo cards, new comments) — see
   the calls to window.lbInitSkeletons() there. No markup changes needed
   per-page: the class rides on the <img> element itself, using its own
   box (already sized by whatever component CSS it belongs to — .lb-logo,
   .lb-avatar-sm, etc.) so nothing shifts layout when the real image
   finishes loading and quietly replaces the shimmer sitting behind it. */
@keyframes lb-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
img.lb-skeleton {
  background-color: var(--lb-navy-800);
  background-image: linear-gradient(100deg,
    transparent 30%,
    rgba(255,255,255,.06) 45%,
    rgba(255,255,255,.11) 50%,
    rgba(255,255,255,.06) 55%,
    transparent 70%);
  background-size: 200% 100%;
  animation: lb-shimmer 1.6s ease-in-out infinite;
  color: transparent; /* hide alt text / broken-image icon while it loads */
}
img.lb-skeleton-done {
  animation: none;
  background: none;
}

@media (prefers-reduced-motion: reduce) {
  img.lb-skeleton { animation: none; background-position: 0 0; }
}

/* Generic non-image placeholder block, for the odd spot that needs a
   shimmer before it has real content but isn't an <img> itself — e.g. the
   dashboard chart while Chart.js is still drawing it. Opt in by hand with
   class="lb-skeleton-block" where relevant. */
.lb-skeleton-block {
  position: relative;
  overflow: hidden;
  background-color: var(--lb-navy-800);
  border-radius: var(--lb-radius-sm, 8px);
}
.lb-skeleton-block::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: linear-gradient(100deg,
    transparent 30%,
    rgba(255,255,255,.06) 45%,
    rgba(255,255,255,.11) 50%,
    rgba(255,255,255,.06) 55%,
    transparent 70%);
  background-size: 200% 100%;
  animation: lb-shimmer 1.6s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .lb-skeleton-block::after { animation: none; }
}

body {
  font-family: var(--lb-font-body);
  min-height: 100vh;
  font-size: .95rem;
  /* room for the fixed mobile bottom nav */
  padding-bottom: calc(var(--lb-bottomnav-h) + env(safe-area-inset-bottom));
  /* Same containing-block guard as .lb-docked-outer/-wrap/-main above —
     <body> is the direct parent of every position:fixed element on the
     site (offcanvas backdrops, the mobile buttons, the search overlay),
     so it's the single most important place to guarantee this. */
  transform: none !important;
  filter: none !important;
  will-change: auto !important;
}
@media (min-width: 992px) { body { padding-bottom: 0; } }

h1, h2, h3, h4, h5, h6, .display-font {
  font-family: var(--lb-font-display);
  font-weight: 600;
  letter-spacing: -0.02em;
}

.text-mist { color: var(--lb-mist) !important; }
.text-lime { color: var(--lb-lime) !important; }
.text-coral { color: var(--lb-coral) !important; }
.mono { font-family: var(--lb-font-mono); }
a { text-decoration: none; }

.lb-container {
  max-width: 1120px;
  /* Force the SAME horizontal gutter everywhere this class is used —
     whether it's on a Bootstrap .container (default .75rem gutter) or a
     .container-fluid with a manual .px-3 (1rem). Equal specificity to both,
     but declared after bootstrap.min.css in the cascade, so this wins and
     every section's left/right edge lines up with the top bar's. */
  padding-left: 1rem;
  padding-right: 1rem;
}

/* ---- Product detail hero section gap ----------------------------------
   Sits right under the sticky navbar (.lb-topbar is `position: sticky`,
   so it takes up its own space in flow — no fixed-nav offset needed
   here, just this section's own top padding).
   Desktop: always a 20px gap under the navbar.
   Mobile: when the product has a hero video right at the top of this
   section, that video should butt up flush against the navbar with no
   gap at all; otherwise (no video, so a heading/content starts the
   section instead) keep the same 20px breathing room as desktop. */
.lb-hero-gap {
  padding-top: 19px;
}
@media (max-width: 991.98px) {
  .lb-hero-gap.lb-hero-gap-video {
    padding-top: 0;
  }
}
@media (min-width: 992px) {
  /* Extra breathing room under the last bit of content so the floating
     .lb-product-actionbar (fixed, ~64px tall + its own bottom offset)
     never permanently sits over real content — it only overlaps the very
     tail end while scrolling, then hides itself once .lb-footer scrolls
     into view (see initProductActionbar() in main.js). */
  .lb-hero-gap {
    padding-bottom: 6.5rem !important;
  }
}

/* =========================================================================
   TOP BAR — mobile: [hamburger][logo] .......... [search][user]
   desktop: [logo][nav links][search]...........[submit][user]
   ========================================================================= */
.lb-topbar {
  height: var(--lb-topbar-h);
  /* Glassy: mostly-transparent tinted panel + heavy blur/saturation of
     whatever's scrolling underneath, rather than an almost-opaque bar.
     A soft inset top highlight and translucent hairline border sell the
     "pane of glass" edge; the bottom border stays translucent too so it
     doesn't read as a hard opaque strip against the blur. */
  background: rgba(var(--lb-navy-950-rgb), 0.55);
  backdrop-filter: blur(20px) saturate(1.8);
  -webkit-backdrop-filter: blur(20px) saturate(1.8);
  box-shadow: inset 0 -1px 0 rgba(255,255,255,.05);
  /* Solid, theme-adaptive — same var(--lb-navy-800) used for the left/right
     panel seams below, so the navbar's bottom edge reads as one continuous
     border system with them rather than its own fainter translucent line. */
  border-bottom: 1px solid var(--lb-navy-800);
  position: sticky;
  top: 0;
  z-index: 1030;
}
/* Browsers without backdrop-filter support can't fake translucency over
   moving content, so fall back to the old near-opaque bar there instead
   of leaving nav text floating over whatever's underneath. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .lb-topbar {
    background: rgba(var(--lb-navy-950-rgb), 0.97);
    box-shadow: none;
    border-bottom: 1px solid var(--lb-navy-800);
  }
}
.lb-topbar-inner {
  min-height: var(--lb-topbar-h);
  display: flex;
  align-items: center;
  gap: .5rem;
}
.lb-icon-btn {
  width: 38px;
  height: 38px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--lb-radius-sm);
  color: var(--lb-white);
  background: transparent;
  border: none;
  font-size: 1.15rem;
  flex-shrink: 0;
}
.lb-icon-btn:hover, .lb-icon-btn:active { background: var(--lb-navy-800); color: var(--lb-lime); }

.lb-brand {
  font-family: var(--lb-font-display);
  font-weight: 700;
  font-size: 1.05rem;
  color: var(--lb-white);
  display: flex;
  align-items: center;
  gap: .4rem;
  white-space: nowrap;
}
.lb-brand .bi { color: var(--lb-lime); font-size: 1.1rem; }

.lb-topbar-spacer { flex: 1 1 auto; }

.lb-avatar-btn img {
  width: 30px; height: 30px; border-radius: 50%;
  object-fit: cover; background: var(--lb-navy-700);
}

.lb-notif-badge {
  position: absolute;
  top: 2px; right: 2px;
  min-width: 16px; height: 16px;
  padding: 0 3px;
  border-radius: 999px;
  background: var(--lb-coral);
  color: #fff;
  font-size: .62rem;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
  font-family: var(--lb-font-mono);
}

/* Desktop-only inline nav, hidden on mobile */
.lb-desktop-nav { display: none; }
@media (min-width: 992px) {
  .lb-desktop-nav { display: flex; align-items: center; gap: .25rem; margin-left: .75rem; }
  .lb-mobile-only { display: none !important; }
}
@media (max-width: 991.98px) {
  .lb-desktop-only { display: none !important; }
}

.lb-nav-link {
  color: var(--lb-mist);
  font-weight: 500;
  font-size: .9rem;
  padding: .4rem .7rem;
  border-radius: var(--lb-radius-sm);
}
.lb-nav-link:hover, .lb-nav-link.active { color: var(--lb-white); background: var(--lb-navy-800); }

/* =========================================================================
   FULL-SCREEN SEARCH OVERLAY — opened by the single search icon in the top
   bar (desktop and mobile alike). Covers the whole viewport, dimmed
   backdrop, with the search field + submit button centered both axes.
   Context-aware: header.php points the form at either blog.php (blog
   pages) or search.php (everywhere else) — see $__searchInBlog there.
   ========================================================================= */
.lb-search-overlay {
  position: fixed;
  inset: 0;
  z-index: 1080;
  width: 100%;
  height: 100%;
  height: 100dvh;
  background: rgba(var(--lb-navy-950-rgb), 0.97);
  backdrop-filter: blur(6px);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-8px);
  transition: opacity .18s ease, transform .18s ease, visibility 0s linear .18s;
}
.lb-search-overlay.is-open {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition: opacity .18s ease, transform .18s ease, visibility 0s linear 0s;
}
body.lb-search-open { overflow: hidden; }
.lb-search-overlay-close {
  position: absolute;
  top: max(16px, env(safe-area-inset-top));
  right: 16px;
  width: 44px; height: 44px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--lb-navy-700);
  background: var(--lb-navy-900);
  color: var(--lb-white);
  font-size: 1.1rem;
}
.lb-search-overlay-close:hover { border-color: var(--lb-lime); color: var(--lb-lime); }
.lb-search-overlay-inner {
  width: 100%;
  max-width: 640px;
  padding: 0 1.25rem;
}
.lb-search-overlay-form { width: 100%; }
.lb-search-overlay-row {
  display: flex;
  align-items: center;
  gap: .5rem;
  background: var(--lb-navy-900);
  border: 1px solid var(--lb-navy-700);
  border-radius: var(--lb-radius-lg, 14px);
  padding: .4rem .4rem .4rem 1.1rem;
}
.lb-search-overlay-row:focus-within { border-color: var(--lb-lime); }
.lb-search-overlay-icon { color: var(--lb-mist); font-size: 1.2rem; flex-shrink: 0; }
.lb-search-overlay-input {
  flex: 1 1 auto;
  min-width: 0;
  background: transparent;
  border: none;
  outline: none;
  color: var(--lb-white);
  font-size: 1.1rem;
  padding: .6rem 0;
}
.lb-search-overlay-input::placeholder { color: var(--lb-mist); }
.lb-search-overlay-submit { flex-shrink: 0; padding: .6rem 1.4rem; border-radius: calc(var(--lb-radius-lg, 14px) - 4px); }
@media (max-width: 575.98px) {
  .lb-search-overlay-row { flex-wrap: wrap; padding: .4rem 1rem; }
  .lb-search-overlay-submit { width: 100%; margin-top: .5rem; }
}

/* Offcanvas menu (hamburger) */
.offcanvas.lb-offcanvas {
  background: var(--lb-navy-900);
  color: var(--lb-white);
  border-right: 1px solid var(--lb-navy-800);
  width: 280px;
  /* Explicit, above the shared .offcanvas-backdrop rule further down
     (z-index: 1045 !important — bumped up from Bootstrap's own default of
     1040 so opening the Other-Links bottom sheet also dims this nav's own
     backdrop correctly, see that rule's comment). Bootstrap's un-overridden
     .offcanvas default is ALSO 1045, so that backdrop bump left this menu
     tied with its own backdrop; with equal z-index, paint order falls back
     to DOM order, and Bootstrap appends the backdrop as the last child of
     <body> (after this nav, which lives up in the header) — so the
     backdrop was winning the tie and rendering on top of the menu it's
     supposed to sit behind. One step above the backdrop's 1045 guarantees
     this nav always wins that stack, same as every other offcanvas/modal
     panel on the site relative to its own backdrop. */
  z-index: 1046;
}
.lb-offcanvas .offcanvas-header { border-bottom: 1px solid var(--lb-navy-800); }
.lb-offcanvas .btn-close { opacity: .7; }
[data-theme="dark"] .lb-offcanvas .btn-close { filter: invert(1) grayscale(1); }
.lb-offcanvas-link {
  display: flex; align-items: center; gap: .65rem;
  padding: .6rem .5rem;
  color: var(--lb-white);
  font-weight: 500;
  border-radius: var(--lb-radius-sm);
  border-left: 2px solid transparent;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.lb-offcanvas-link:hover { background: var(--lb-navy-800); color: var(--lb-lime); }
.lb-offcanvas-link .bi { width: 20px; color: var(--lb-mist); font-size: 1.05rem; }
.lb-offcanvas-link:hover .bi { color: var(--lb-lime); }
.lb-offcanvas-link.lb-active-link { background: rgba(199,244,100,.1); color: var(--lb-lime); border-left-color: var(--lb-lime); }
.lb-offcanvas-link.lb-active-link .bi { color: var(--lb-lime); }
.lb-offcanvas-section-title {
  font-size: .72rem; text-transform: uppercase; letter-spacing: .06em;
  color: var(--lb-mist); padding: .25rem .5rem; margin-top: .5rem;
}

/* ---- Docked nav (account/admin areas): the SAME offcanvas element becomes a
   permanent left sidebar at desktop widths instead of a hidden slide-in drawer,
   so there's exactly one copy of these links, not a duplicate static sidebar. ----

   Alignment: .lb-docked-outer is the SAME container-fluid + lb-container +
   px-3 combo the top bar uses, so the sidebar's left edge lines up under the
   logo and the content's right edge lines up under the avatar — the whole
   sidebar+content block sits inside the identical bounding box as the top
   bar. Every page's own inner "container lb-container" is then neutralized
   (full width, no re-centering, no extra padding) so it doesn't create a
   second, misaligned gutter inside that box. */
/* No bottom padding here — it lives on .lb-docked-main instead (below),
   so the row's true height (and therefore the left/right panels' full
   height, which track the row via `position: sticky`) includes that
   breathing room and their borders run all the way down to the very
   bottom of the shell, connecting with .lb-footer's border-top with zero
   gap between them, instead of stopping short by that padding. */
.lb-docked-outer { padding-top: 0; padding-bottom: 0; }
/* The shell needs more room than the normal 1120px page container once
   there are up to 3 columns side by side — otherwise the center column
   gets crushed. Shared by the top bar's own inner container too, so the
   bar's [logo]...[user] content starts flush above the left panel's edge
   and ends flush above the right panel's edge — see .lb-shell-bounds. */
.lb-shell-bounds.lb-container { max-width: 1360px; }
/* Defensive reset: position:fixed descendants (the mobile visit/more/promo
   buttons, the search overlay, offcanvas backdrops, etc.) are only truly
   viewport-fixed if NO ancestor between them and <body> has a transform,
   filter, perspective, will-change, or contain property set — any one of
   those silently turns "fixed" into "fixed relative to that ancestor"
   instead, which looks exactly like the element scrolling with the page.
   Nothing in this stylesheet sets any of those on these containers, but a
   third-party stylesheet (Bootstrap/Swiper/GLightbox) reaching them via a
   utility class could in theory — this guards against that possibility
   explicitly rather than relying on it never happening. */
.lb-docked-outer, .lb-docked-wrap, .lb-docked-main {
  transform: none !important;
  filter: none !important;
  will-change: auto !important;
}
.lb-docked-main .container.lb-container,
.lb-docked-main > section.lb-container {
  max-width: none;
  width: 100%;
  padding-left: 0 !important;
  padding-right: 0 !important;
}
/* Pages that set their own inline max-width on this container (e.g. the
   centered ~440px login/register card) are deliberately left alone here —
   no margin override — so Bootstrap's own `.container` margin:auto still
   centers them inside the column instead of pinning them to its left edge. */

@media (min-width: 992px) {
  /* No `gap` here on purpose — the offcanvas-to-main and main-to-aside
     spacing are each created explicitly below (offcanvas's margin-right,
     main's padding-right) instead of a uniform flex `gap`, specifically
     so the right-hand seam can be redrawn as a real border on
     .lb-docked-main without adding a `position: relative` anywhere
     upstream of it. That matters because .lb-actionbar-fixed (the
     floating vote/rate/visit bar on product pages) is a DESCENDANT of
     .lb-docked-main, and its footer-proximity JS temporarily switches it
     to `position: absolute` with a document-relative `top` — introducing
     any positioned ancestor between it and <body> would silently change
     its containing block and throw that math off (this happened once
     already; don't reintroduce it). Padding/margin/border never affect
     containing blocks, so this whole approach is safe for that bar.

     align-items: stretch (not the flex default `flex-start`) is required
     for .lb-docked-main's border-right (below) to actually reach
     .lb-footer's border-top on short pages. The left sidebar is
     `position: sticky` with an explicit `height: calc(100vh - topbar)`
     (see #lbOffcanvas.lb-offcanvas-docked below), so it's always at
     least a full viewport tall — taller than .lb-docked-main's own
     content whenever a page's content is shorter than the viewport.
     Under flex-start, a flex item's height comes only from its own
     content, so .lb-docked-main (and its border-right) would stop
     exactly where its content ends — short of the sidebar's bottom edge
     and short of the footer — leaving a gap. `stretch` makes every flex
     item without its own explicit height (.lb-docked-main here) grow to
     match the row's tallest item instead, so the border always runs the
     full height of the row, flush into the footer, regardless of how
     little content the page has. .lb-shell-right opts back out of this
     via its own `align-self: flex-start` (see below) since it's sticky
     content with no border of its own — stretching it would just add
     invisible empty space under its widgets, so it isn't needed there. */
  .lb-docked-wrap { display: flex; align-items: stretch; width: 100%; box-sizing: border-box; }
  .lb-docked-main {
    flex: 1 1 0%;
    min-width: 0;
    padding-bottom: var(--lb-space-6);
  }
  /* Right-hand seam + gap — scoped to body.lb-has-right-rail since
     .lb-docked-main is shared with admin/auth pages that use the docked
     left nav but have no right panel at all (see $__showRightRail in
     header.php); unscoped, this would draw a border with nothing next to
     it on those pages. padding-right stands in for the old flex gap on
     this side, with the border landing at its outer edge — flush against
     the right panel, same spot it sat at when it lived on the panel's own
     border-left. */
  body.lb-has-right-rail .lb-docked-main {
    padding-right: var(--lb-space-6);
    border-right: 1px solid var(--lb-navy-800);
  }
  /* Left-hand gap (offcanvas to main) — recreates what the old uniform
     flex `gap` did for this pair (margin-right, folded into the existing
     #lbOffcanvas.lb-offcanvas-docked rule below). Margin rather than a
     wrap `gap` so it has no effect once the offcanvas switches to
     `position: fixed` and drops out of flow (992-1199.98 tablet band),
     matching the original behaviour of main sitting flush at that point. */

  #lbOffcanvas.lb-offcanvas-docked {
    position: sticky !important;
    top: var(--lb-topbar-h) !important;
    left: auto !important;
    transform: none !important;
    visibility: visible !important;
    flex: 0 0 260px;
    width: 260px !important;
    height: calc(100vh - var(--lb-topbar-h)) !important;
    max-height: calc(100vh - var(--lb-topbar-h));
    margin-right: var(--lb-space-6);
    border-radius: 0;
    border: 0;
    /* Theme-adaptive seam between the left panel and the center column —
       var(--lb-navy-800) is the site's standard border color, redefined
       per theme in [data-theme="dark"], so this flips automatically. */
    border-right: 1px solid var(--lb-navy-800);
    background: transparent;
    box-shadow: none !important;
    z-index: 10;
    display: flex !important;
    flex-direction: column;
  }
  #lbOffcanvas.lb-offcanvas-docked .offcanvas-header { display: none; }
  #lbOffcanvas.lb-offcanvas-docked .offcanvas-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 1.1rem .85rem;
    /* Flush left edge: the sidebar's own left inset (.85rem above) is what
       pushed nav items in from .lb-offcanvas-docked's left border, out of
       line with the topbar's [logo]...[user] row, which starts flush at
       .lb-shell-bounds' own left edge (see the big alignment comment above
       this media query). Zeroing it out lines the nav links' left edge up
       with the logo's left edge instead. */
    padding-left: 0;
    scrollbar-width: thin;
    scrollbar-color: var(--lb-navy-700) transparent;
  }
  #lbOffcanvas.lb-offcanvas-docked .offcanvas-body::-webkit-scrollbar { width: 6px; }
  #lbOffcanvas.lb-offcanvas-docked .offcanvas-body::-webkit-scrollbar-thumb { background: var(--lb-navy-700); border-radius: 999px; }
  /* No backdrop / scroll-lock needed once it's a static sidebar, not a modal drawer. */
  body.lb-docked-nav .offcanvas-backdrop { display: none !important; }
}

/* =========================================================================
   RIGHT RAIL — third column of the app shell (left nav | center feed |
   right rail), same idea as the left sidebar above: pinned in the
   viewport via `position: sticky` at the same top offset. Unlike the left
   nav, it does NOT get its own independent scrollbar/clipped height —
   it has no fixed height or overflow, so once its widgets (including the
   trailing "related products" card) grow taller than the viewport it just
   keeps scrolling along with the page's own scroll (the html/body), the
   same way it scrolls back into place once you scroll up again. It's only
   "sticky" in the sense that it stays pinned near the top of the viewport
   while its own content still fits, then scrolls off naturally with
   everything else. Shown starting at the `lg` breakpoint (992px) — this
   behaves identically whether it's sharing the row with the left nav
   (xl+) or standing in for it (the "lower desktop / tablet" band
   immediately below, where it trades places with the left nav instead of
   both — or neither — being visible). */
.lb-shell-right {
  flex: 0 0 320px;
  width: 320px;
  box-sizing: border-box;
  padding: 1rem .9rem 1.1rem 1rem;
}
@media (min-width: 992px) {
  .lb-shell-right {
    position: sticky;
    top: var(--lb-topbar-h);
    align-self: flex-start;
    /* Seam between the center column and this panel now lives on
       .lb-docked-main as a border-right instead (see .lb-docked-main
       above) — kept off this element on purpose. */
    /* Flush right edge: the base rule's right inset (.9rem, above) pushed
       widget cards in from the panel's true right edge, out of line with
       the topbar's [logo]...[user] row, which ends flush at
       .lb-shell-bounds' own right edge. Zeroing it out lines the widgets'
       right edge up with the avatar's right edge instead. */
    padding-right: 0;
  }
}

/* =========================================================================
   "LOWER DESKTOP" / TABLET BAND (992px–1199.98px) — there's room to dock
   ONE side panel next to the center column here, not both left nav AND
   right rail (that needs the full 3-column xl+ width above). Rather than
   defaulting to the left nav and hiding the right rail (the right rail's
   Featured/Promoted/trending-category widgets are more relevant to what's
   in the center feed at this width), we flip it: the right rail shows,
   and the left nav reverts to its normal slide-in offcanvas drawer,
   reachable via the hamburger button (shown again at this width — it's
   otherwise hidden at >=992px by `.lb-mobile-only`). Only kicks in on
   pages that actually have a right rail to swap in (see body class
   `.lb-has-right-rail`, set by header.php) — admin/auth pages keep the
   plain docked-left-nav behaviour at every width >=992px, unaffected.
   ========================================================================= */
@media (min-width: 992px) and (max-width: 1199.98px) {
  /* Every property here that the base >=992px docked-sidebar rule (above)
     sets as `!important` has to be re-declared `!important` here too, or
     that rule wins regardless of this selector's higher specificity —
     `!important` is compared before specificity, not after. */
  body.lb-has-right-rail #lbOffcanvas.lb-offcanvas-docked {
    /* Undo the docked-sidebar treatment from the block above and go back
       to a normal fixed, off-screen-by-default Bootstrap offcanvas drawer
       — position:fixed (not sticky/in-flow), starts translated fully out
       of view, and only becomes visible/interactive once Bootstrap's own
       `.show` class is toggled by the hamburger button below. */
    position: fixed !important;
    top: 0 !important;
    left: 0 !important;
    transform: translateX(-100%) !important;
    visibility: hidden !important;
    transition: transform .3s ease-in-out, visibility 0s linear .3s;
    flex: 0 0 auto;
    width: 260px !important;
    height: 100vh !important;
    max-height: 100vh;
    border-right: 1px solid var(--lb-navy-800);
    background: var(--lb-navy-900);
    box-shadow: 0 0 40px rgba(0,0,0,.35) !important;
    /* MUST beat .offcanvas-backdrop's z-index:1045 !important (see its own
       comment further down) — matching it exactly, as this rule did before,
       ties the paint order to DOM order instead, and Bootstrap appends the
       backdrop as body's last child (after this drawer, which lives up in
       the header), so the backdrop was winning that tie and rendering on
       top of the ENTIRE drawer — dimming its own opaque background and
       text along with the page behind it, not just the page. One step
       above, `!important` to match, guarantees the drawer always wins —
       same convention `.offcanvas.lb-offcanvas` already uses for exactly
       this reason. */
    z-index: 1046 !important;
  }
  body.lb-has-right-rail #lbOffcanvas.lb-offcanvas-docked.show,
  body.lb-has-right-rail #lbOffcanvas.lb-offcanvas-docked.showing {
    transform: none !important;
    visibility: visible !important;
    transition: transform .3s ease-in-out;
  }
  /* The docked variant hides the offcanvas's own header (close button) —
     as a real slide-in drawer again at this width it needs it back. */
  body.lb-has-right-rail #lbOffcanvas.lb-offcanvas-docked .offcanvas-header {
    display: flex;
    border-bottom: 1px solid var(--lb-navy-800);
  }
  /* A real drawer needs its usual dimmed backdrop + scroll-lock, both of
     which the docked-sidebar block above disables globally — restore
     them just for this band (this rule's extra class beats that one's
     equal-specificity selector by appearing later in the stylesheet). */
  body.lb-has-right-rail .offcanvas-backdrop { display: block !important; }
  /* Bring the hamburger back — hidden at >=992px by default since the
     left nav is normally already visible as a docked sidebar there. */
  body.lb-has-right-rail .lb-hamburger-btn.lb-mobile-only { display: inline-flex !important; }
}
/* =========================================================================
   BOTTOM NAV — mobile only, fixed, 5 quick actions
   ========================================================================= */
.lb-bottom-nav {
  display: flex;
  position: fixed;
  left: 0; right: 0; bottom: 0;
  height: var(--lb-bottomnav-h);
  padding-bottom: env(safe-area-inset-bottom);
  background: rgba(var(--lb-navy-900-rgb), 0.97);
  backdrop-filter: blur(10px);
  border-top: 1px solid var(--lb-navy-800);
  /* Above the mobile visit/more/promo fixed cluster (1032–1034) — the
     bottom nav is permanent, always-visible chrome and should never be
     covered by any of those, even though they sit higher up the screen
     and don't normally overlap it spatially. */
  z-index: 1040;
}
@media (min-width: 992px) { .lb-bottom-nav { display: none; } }

.lb-bottom-nav-item {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  color: var(--lb-mist);
  font-size: .65rem;
  font-weight: 500;
}
.lb-bottom-nav-item .bi { font-size: 1.25rem; line-height: 1; }
.lb-bottom-nav-item.active { color: var(--lb-lime); }

button.lb-bottom-nav-item { background: transparent; border: none; }
.lb-bottom-nav-item.lb-fab-item { position: relative; top: -14px; }
.lb-fab {
  box-sizing: border-box;
  width: 46px; height: 46px;
  flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--lb-lime);
  color: var(--lb-ink);
  border-radius: 50%;
  aspect-ratio: 1 / 1;
  font-size: 1.4rem;
  line-height: 1;
  overflow: hidden;
  box-shadow: 0 4px 14px rgba(199,244,100,.35);
}
.lb-fab .bi { line-height: 1; }
.lb-bottom-nav-item.lb-fab-item span:last-child { margin-top: 2px; color: var(--lb-mist); }

/* =========================================================================
   DROPDOWN MENUS (avatar menu, etc.)
   ========================================================================= */
.dropdown-menu {
  background: var(--lb-navy-800);
  border: 1px solid var(--lb-navy-700);
  box-shadow: 0 8px 24px rgba(0,0,0,.35);
}
.dropdown-item { color: var(--lb-white); background: transparent; }
.dropdown-item:hover,
.dropdown-item:focus,
.dropdown-item:active {
  background: var(--lb-navy-700) !important;
  color: var(--lb-lime) !important;
}
.dropdown-divider { border-color: var(--lb-navy-700); }

/* =========================================================================
   BUTTONS
   ========================================================================= */
.btn-lime { background: var(--lb-lime); border: 1px solid var(--lb-lime); color: var(--lb-ink); font-weight: 600; }
.btn-lime:hover { background: var(--lb-lime-dark); border-color: var(--lb-lime-dark); color: var(--lb-ink); }
.btn-outline-lime { border: 1px solid var(--lb-navy-600); color: var(--lb-white); background: var(--lb-navy-800); }
.btn-outline-lime:hover { border-color: var(--lb-lime); color: var(--lb-lime); background: var(--lb-navy-800); }
.btn-coral { background: var(--lb-coral); border: 1px solid var(--lb-coral); color: #fff; font-weight: 600; }
.btn-coral:hover { background: var(--lb-coral-dark); border-color: var(--lb-coral-dark); color: #fff; }

/* ---- Bootstrap tooltips (site-wide) ----------------------------------------
   Every [data-bs-toggle="tooltip"] trigger (see initTooltips() in main.js)
   gets Bootstrap's real tooltip component instead of the native browser one —
   but without its default triangular arrow/pointer, which is hidden here by
   zeroing the arrow's size rather than display:none (Popper still reserves
   space for an arrow of that size when positioning the tooltip, so a hard
   display:none can leave a gap; 0×0 keeps positioning correct with nothing
   visible to draw). */
.tooltip .tooltip-arrow { display: none; }
.tooltip {
  --bs-tooltip-bg: var(--lb-navy-800);
  --bs-tooltip-color: var(--lb-white);
}
.tooltip .tooltip-inner {
  font-size: .8rem;
  font-weight: 500;
  padding: .4rem .65rem;
  box-shadow: 0 4px 14px rgba(0,0,0,.18);
}

/* =========================================================================
   HOMEPAGE SHOWCASE — "Featured items" + "Editor's Choice" cards.
   Replaces the old full-width "lb-hero" banner. Two short, compact cards
   in one row: a scrollable Featured list (2 items visible at once —
   vertical swiper on desktop, horizontal 2-up swiper on mobile) and an
   Editor's Choice image slide (always horizontal). Deliberately kept
   short — small fixed heights below — so the pair covers very little
   vertical space on the page. Mobile-first: base rules below are the
   mobile layout; the lg breakpoint re-arranges for desktop.
   ========================================================================= */
.lb-showcase { padding-top: var(--lb-space-3); padding-bottom: var(--lb-space-3); }
.lb-showcase-row { align-items: stretch; }

/* Mobile order: Editor's Choice first, Featured just below it. */
.lb-show-col-editor  { order: -1; }
.lb-show-col-featured { order: 1; }
@media (min-width: 992px) {
  /* Desktop: back to natural reading order — Featured (left), Editor's
     Choice (right) — matching the two cards side by side. */
  .lb-show-col-featured { order: 1; }
  .lb-show-col-editor   { order: 2; }
}

.lb-show-card {
  height: 100%;
  border-radius: var(--lb-radius-lg);
  overflow: hidden;
  position: relative;
}
/* Editor's Choice card: every part rounds consistently — the outer card
   itself (already covered by .lb-show-card above) and the inner shelf
   (.lb-editor-slide-card), which defines its own bottom radius
   independently of the outer clip since it's a backdrop-filter element
   (see the long comment on .lb-editor-slide-card below for why that
   can't just rely on the ancestor's overflow:hidden). */
.lb-show-card-editor .lb-editor-slide-card {
  border-radius: 0 0 var(--lb-radius-lg) var(--lb-radius-lg);
}
/* Small "See all" link to the full Editor's Choice archive
   (editor-choice.php) — every pick ever made, not just whichever slides
   are currently active in the swiper below it. Positioned above the
   swiper (z-index) in the card's top-right corner, over the image, same
   treatment as a chip/pill overlay rather than living in a header row
   (this card has no .lb-show-card-head like the Featured card next to
   it — the slide artwork fills the whole card). */
.lb-editor-see-all {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  background: rgba(11, 14, 31, .55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  /* Literal white, not var(--lb-white) — that variable flips to a dark
     navy in the default light theme, but this chip always sits on the
     same fixed dark translucent backdrop above, in both themes. */
  color: #FFFFFF;
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .01em;
  padding: .3rem .6rem;
  border-radius: 1rem;
  text-decoration: none;
  border: 1px solid rgba(255,255,255,.14);
  transition: background-color .15s ease, border-color .15s ease;
}
.lb-editor-see-all:hover {
  background: rgba(11, 14, 31, .8);
  border-color: rgba(255,255,255,.28);
  color: var(--lb-lime);
}
.lb-editor-see-all i { font-size: .68rem; }

/* Full-width variant of the exact same card (editor-choice.php's hero
   slider) — everything about the slide itself (.lb-editor-slide and
   friends) is untouched/identical to the homepage version; this class
   only says "don't inherit the homepage grid column's height:100% (which
   assumes a sibling Featured-card row to match), just size naturally"
   since this page has no Featured card next to it to stretch to. */
.lb-show-card-editor-full { height: auto; }

/* ---- Featured items card ------------------------------------------- */
.lb-show-card-featured { padding: var(--lb-space-3) var(--lb-space-3) var(--lb-space-2); }
.lb-show-card-head { display: flex; flex-direction: column; gap: .1rem; margin-bottom: .5rem; }
.lb-show-card-title { font-size: .95rem; font-weight: 700; margin: 0; }

/* Each product row inside the Featured swiper — small and compact by
   design (short fixed height) so 2 rows + gap stay a small, quiet block
   instead of a tall banner. */
.lb-feat-row {
  display: flex;
  align-items: center;
  gap: .65rem;
  height: 100%;
  padding: .35rem .1rem;
  text-decoration: none;
  color: inherit;
}
.lb-feat-row-logo {
  width: 38px; height: 38px; flex-shrink: 0;
  border-radius: 50%;
  /* Same "contain" fix as .lb-logo above — avoids cropping wide/non-square
     logos. */
  object-fit: contain;
  background: #fff;
  padding: 2px;
  box-sizing: border-box;
}
.lb-feat-row-body { flex: 1 1 auto; min-width: 0; }
.lb-feat-row-name { font-size: .82rem; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.lb-feat-row-tagline {
  font-size: .72rem;
  color: var(--lb-mist);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.lb-feat-row-btn { flex-shrink: 0; padding: .28rem .75rem; font-size: .72rem; font-weight: 600; }

/* Desktop: vertical swiper, exactly 2 rows visible — fixed height keeps
   the card short regardless of how many Featured products exist. */
.lb-feat-swiper-v {
  height: 148px;
}
.lb-feat-swiper-v .swiper-slide { height: calc(50% - 4px); }

/* Right-aside Featured/Promoted cards: PROMOTED and FEATURED render as
   two separate .lb-panel cards (not merged into one), but every panel in
   this rail uses the slimmer .lb-aside-panel padding (see below) and a
   noticeably tighter gap between the 2 visible product rows in each
   swiper (spaceBetween: 2 in main.js, vs. 8 for the hero's own Featured
   card) plus slimmer row padding — all so the whole rail's stack of
   cards fits the viewport without needing its own vertical scrollbar. */
.lb-aside-feat-swiper.lb-feat-swiper-v {
  height: 104px;
}
.lb-aside-feat-swiper.lb-feat-swiper-v .swiper-slide { height: calc(50% - 1px); }
.lb-aside-feat-swiper .lb-feat-row { padding: .18rem .1rem; }

/* Slim, shared padding for every card in the right rail (swaps out
   Bootstrap's .p-3 utility, which can't be overridden without
   !important) — noticeably less top/bottom room than the default 1rem
   so the stack of PROMOTED / FEATURED / Trending / This Week cards is
   short enough to fit without the rail needing its own scrollbar. */
.lb-aside-panel { padding: .75rem .9rem .6rem; }


/* Mobile: horizontal swiper, exactly 2 rows visible side by side. */
.lb-feat-swiper-h .swiper-slide {
  width: 50%;
  border-right: 1px solid var(--lb-navy-800);
}
.lb-feat-swiper-h .swiper-slide:last-child { border-right: none; }

/* ---- Editor's Choice card -------------------------------------------
   A single, contained rounded card (not full-bleed) — landscape shape,
   deliberately short at every breakpoint (much shorter than the old
   full-width hero banner), full-bleed background image with a dark
   gradient for legibility, headline + subtext, and a compact
   logo/name/tagline/View "app card" shelf along the bottom. Always
   slides horizontally (direction:'horizontal' in the JS init — never
   vertical), one slide at a time.
   ========================================================================= */
.lb-editor-swiper { width: 100%; height: 100%; }
.lb-editor-swiper .swiper-wrapper { align-items: stretch; }
.lb-editor-swiper .swiper-slide { width: 100%; height: 100%; }

.lb-editor-slide {
  /* Adaptive tint fallback (navy) — overridden per-slide via JS once each
     slide's background image has loaded (see applyAdaptiveEditorTint() in
     main.js), which samples the image's dominant color and writes it here
     as an "R, G, B" triplet. Every tint-based rule below reads this same
     variable, so the scrim gradient AND the bottom shelf card recolor
     together, per image, automatically. */
  --lb-editor-tint: 8, 10, 24;
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: flex-end;
  height: 100%;
  min-height: 170px;
  max-height: 190px;
  text-decoration: none;
  color: inherit;
  cursor: pointer;
}
@media (min-width: 992px) {
  .lb-editor-slide { min-height: 148px; max-height: 190px; }
}

.lb-editor-slide-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
  pointer-events: none;
  z-index: 0;
  transition: transform .6s ease;
}
.lb-editor-slide:hover .lb-editor-slide-bg { transform: scale(1.03); }

/* Per-image adaptive overlay: reads --lb-editor-tint (see .lb-editor-slide
   above). Rather than a hard cutoff where the tint starts (which reads as
   a static rectangle sitting on top of the artwork), the tint fades in
   gradually across a wide band — near-invisible through the top ~22%,
   easing in through the middle, then fully covering the bottom of the
   slide — so the edge itself looks soft/blurred rather than like a flat
   shape with a visible seam. It still bleeds smoothly from there down to
   a fully opaque wash at the very bottom, reading as one continuous
   tinted surface behind the text (.lb-editor-slide-inner) and the shelf
   below it. This element (not text-shadow) is what makes the text
   legible. */
.lb-editor-slide-scrim {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(180deg,
    rgba(var(--lb-editor-tint), 0)    0%,
    rgba(var(--lb-editor-tint), 0)   22%,
    rgba(var(--lb-editor-tint), .08) 30%,
    rgba(var(--lb-editor-tint), .28) 38%,
    rgba(var(--lb-editor-tint), .55) 46%,
    rgba(var(--lb-editor-tint), .8)  58%,
    rgba(var(--lb-editor-tint), 1)  100%);
}

.lb-editor-slide-inner {
  position: relative;
  z-index: 1;
  width: 100%;
  /* No bottom padding — the app shelf below sits flush against the very
     bottom edge of the card (its own inner padding supplies the visual
     breathing room instead), matching the reference design exactly. */
  padding: .7rem .85rem 0;
  display: flex;
  flex-direction: column;
  gap: .15rem;
}
.lb-editor-slide-inner .lb-eyebrow {
  font-size: .62rem;
  font-weight: 700;
  color: #fff;
  opacity: .92;
}
.lb-editor-slide-headline {
  font-size: clamp(.9rem, 2.7vw, 1.1rem);
  font-weight: 800;
  color: #fff;
  line-height: 1.15;
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.lb-editor-slide-subtext {
  font-size: .74rem;
  color: rgba(255,255,255,.85);
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Bottom "app shelf" — a full-bleed bar tinted with the same adaptive
   color as the overlay above it. It breaks out of .lb-editor-slide-inner's
   horizontal padding (negative margins matching that padding exactly)
   so the tinted/blurred background runs edge-to-edge across the whole
   slide, while its own matching horizontal padding keeps the icon/name
   row visually aligned with the headline above it. It gets generous
   bottom padding so the row breathes above the rounded edge.
   Its bottom corners are rounded here directly (matching --lb-radius-lg)
   rather than relying solely on the ancestor .lb-show-card's
   overflow:hidden to clip them — backdrop-filter elements are notorious
   for ignoring an ancestor's border-radius clip in some browsers, which
   is what was leaving the bottom corners visibly square. */
.lb-editor-slide-card {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: .6rem;
  width: calc(100% + 1.7rem);
  margin-left: -.85rem;
  margin-right: -.85rem;
  box-sizing: border-box;
  margin-top: .55rem;
  padding: .6rem .85rem .95rem;
  background: rgba(var(--lb-editor-tint), .9);
  backdrop-filter: blur(14px) saturate(160%);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  border-radius: 0 0 var(--lb-radius-lg) var(--lb-radius-lg);
}
/* .lb-editor-slide-card sits INSIDE .lb-editor-slide-inner (right after the
   headline/subtext, same padded column) — so right above the card there
   was a small dead gap (inner's own flex gap + the card's margin-top)
   where only the plain .lb-editor-slide-scrim gradient showed, with
   nothing bridging it to the card's tint+blur below. That made the
   blur/tint switch on abruptly right at the card's own top edge instead
   of reading as one continuous surface. This pseudo-element extends the
   SAME tint+blur upward to fill exactly that gap, fading in via
   mask-image (transparent/no-blur where it meets the subtext, full
   strength where it meets the card) so it bleeds smoothly up into
   .lb-editor-slide-inner rather than starting with a hard edge. Purely
   decorative/absolutely positioned, so it doesn't affect layout or clip
   the headline/subtext text above it. */
.lb-editor-slide-card::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: .9rem;
  background: rgba(var(--lb-editor-tint), .9);
  backdrop-filter: blur(14px) saturate(160%);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  -webkit-mask-image: linear-gradient(180deg, rgba(0,0,0,0) 0%, #000 100%);
  mask-image: linear-gradient(180deg, rgba(0,0,0,0) 0%, #000 100%);
  pointer-events: none;
}
.lb-editor-slide-card .lb-logo { width: 30px; height: 30px; flex-shrink: 0; border-radius: 50%; background: transparent; }
.lb-editor-slide-card,
.lb-editor-slide-card * { color: #fff !important; }
.lb-editor-slide-card .fw-semibold { font-size: .8rem; }
.lb-editor-slide-card .btn.btn-lime {
  padding: .28rem .75rem;
  font-size: .7rem;
  font-weight: 700;
  border-radius: 999px;
  flex-shrink: 0;
  color: var(--lb-ink) !important;
}

/* Compact badge modifier — used to fit a text badge (e.g. "FEATURED")
   inline in tight single-line rows like .lb-feat-row without wrapping. */
.lb-badge-xs {
  font-size: .55rem !important;
  padding: .08rem .4rem !important;
  letter-spacing: .03em !important;
  gap: .15rem !important;
}
.lb-badge-xs i { font-size: .55rem; }

.lb-eyebrow {
  font-family: var(--lb-font-mono); font-size: .7rem; letter-spacing: .08em;
  text-transform: uppercase; color: var(--lb-lime);
}

/* =========================================================================
   VOTE BUTTON — compact pill, not a bulky box
   ========================================================================= */
.lb-vote-btn {
  display: flex; align-items: center; justify-content: center; gap: .3rem;
  min-width: 52px; height: 40px; padding: 0 .6rem;
  border: 1px solid var(--lb-navy-700);
  border-radius: 999px;
  background: transparent;
  color: var(--lb-white);
  cursor: pointer;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
  user-select: none;
}
.lb-vote-btn:hover { border-color: var(--lb-lime); }
.lb-vote-btn.voted { background: var(--lb-lime); border-color: var(--lb-lime); color: var(--lb-ink); }
.lb-vote-btn .bi { font-size: .95rem; }
.lb-vote-btn .count { font-family: var(--lb-font-mono); font-weight: 700; font-size: .85rem; }
.lb-vote-btn:disabled { opacity: .5; cursor: not-allowed; }

/* Larger vote button used solo on product detail sidebar */
.lb-vote-btn.lb-vote-lg { height: 64px; border-radius: var(--lb-radius-md); flex-direction: column; gap: 0; }
.lb-vote-btn.lb-vote-lg .count { font-size: 1.05rem; }

/* Product-Hunt-style stat pair on the right of a row: comment count (static)
   + vote button, both stacked icon-over-number, top-aligned with the row. */
.lb-row-stats { display: flex; align-items: flex-start; gap: .4rem; flex-shrink: 0; }
.lb-comment-stat {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  min-width: 52px; height: 40px; padding: 0 .6rem;
  border: 1px solid var(--lb-navy-700);
  border-radius: 999px;
  color: var(--lb-mist);
}
.lb-comment-stat .bi { font-size: .9rem; line-height: 1; }
.lb-comment-stat .count { font-family: var(--lb-font-mono); font-weight: 700; font-size: .85rem; line-height: 1.3; }

/* =========================================================================
   PRODUCT ROW — minimal list item, border-bottom instead of boxed card.
   Top-aligned (rank/logo sit at the top of the row, like Product Hunt),
   not vertically centered — rows vary in height with tagline/meta length.
   ========================================================================= */
.lb-product-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: .85rem;
  padding: .85rem .25rem;
  border-bottom: 1px solid var(--lb-navy-800);
  position: relative;
  border-radius: var(--lb-radius-sm);
  transition: background .15s ease;
}
.lb-product-row:last-child { border-bottom: none; }
/* The name+badges block can outgrow the row (e.g. Verified + Featured +
   Hero + an award medal chip all stacked next to a long product name) —
   with the default flex-basis:auto, that content's own preferred width is
   what the *parent* row uses to decide whether it fits on one line, so on
   narrower viewports the whole row would wrap and knock .lb-row-stats
   (comment/vote) down below instead of leaving it pinned top-right like
   the rest of the site. Forcing flex-basis:0 here (on top of the global
   .min-width-0 fix above) removes this column's content width from that
   fit calculation entirely — it still grows to fill the remaining space
   (flex-grow-1) and can still shrink/wrap its own contents, it just never
   forces the row itself to wrap. */
.lb-product-row > .flex-grow-1.min-width-0 { flex-basis: 0; }
/* Gentle hover feedback on every product row across dashboard, my-products,
   profile, and aside lists — was previously fully static, giving no
   affordance that the row (or its stretched-link) is interactive. */
.lb-product-row:hover { background: rgba(199,244,100,.045); }
[data-theme="dark"] .lb-product-row:hover { background: rgba(199,244,100,.06); }

/* "Yesterday / Last Week / Last Month" round-ups on the homepage — sit
   directly under the main product list inside the same column, separated
   by a divider instead of relying on outer section spacing (which used to
   leave a large empty gap when the sidebar was taller than the list). */
.lb-period-section { border-top: 1px solid var(--lb-navy-800); padding-top: var(--lb-space-5, 2rem); }
/* Whole row is clickable via a stretched-link anchor; interactive
   elements inside (vote button, action buttons) sit above it.
   .lb-admin-row-actions (admin/products.php) has to be listed here too —
   without it, every Promote/Hero/Verify/"..." click on that page was
   being swallowed by the stretched-link overlay instead of reaching the
   button underneath, since stretched-link's ::after otherwise sits above
   any content that isn't explicitly raised with position+z-index. */
.lb-product-row .lb-vote-btn,
.lb-product-row .lb-row-actions,
.lb-product-row .lb-admin-row-actions,
.lb-product-row .lb-row-stats,
.lb-product-row .lb-maker-stack {
  position: relative;
  z-index: 2;
}

/* Status badges (Verified / Featured / Editor's Choice / award medal) sit
   right next to the stretched-link product-name anchor inside every
   .lb-product-row (homepage, editor-choice, search, category, profile, …
   — see product_badges_html() in functions.php). Without their own
   position+z-index they were being covered by the stretched-link's
   ::after overlay just like .lb-vote-btn/.lb-row-actions above, so
   hovering directly over an icon never reached it — the Bootstrap
   tooltip's mouseenter listener sat on an element the pointer could
   never actually land on. Raising them the same way lets the
   data-bs-toggle="tooltip" title on each badge show on hover/focus. */
.lb-product-row .lb-badge-icon,
.lb-product-row .lb-badge-verified,
.lb-product-row .lb-badge-hero,
.lb-product-row .lb-badge-editor-choice,
.lb-product-row .lb-badge-award {
  position: relative;
  z-index: 2;
}

/* Per-product action toolbar (my-products.php) — a wrapped horizontal
   row of compact buttons instead of a tall single-file column. Sits below
   the product's own info on every width so the row's height tracks its
   content instead of whichever is taller of (info column) vs. (5-button
   stack), and so the primary action (Promote) doesn't get visually buried
   under three secondary ones above it. */
.lb-row-actions {
  display: flex;
  flex: 1 1 100%;
  flex-wrap: wrap;
  gap: .45rem;
  margin-top: .65rem;
  /* Indent to line up under the product info text, not the logo. */
  margin-left: calc(44px + .85rem);
}
@media (max-width: 575.98px) {
  .lb-row-actions { margin-left: 0; }
}
.lb-row-actions .btn {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  white-space: nowrap;
}
@media (min-width: 576px) {
  /* Above phone width, right-align the toolbar under the row so it reads
     as "actions for this item" rather than a full-bleed button bar. */
  .lb-row-actions { justify-content: flex-end; }
}

/* =========================================================================
   ADMIN PRODUCT ROW ACTIONS (admin/products.php) — one horizontal,
   wrapping toolbar instead of a tall stack of full-width buttons. Frequent
   status actions stay as plain small buttons; Promote/Hero collapse to a
   single-line combo (select + button) and become a quiet status chip once
   active; everything else (Editor's Choice, Verify, Delete) lives behind
   one "more" menu so the row stays scannable at a glance.
   ========================================================================= */
.lb-admin-row-actions {
  display: flex;
  flex: 1 1 100%;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  margin-top: .65rem;
  margin-left: calc(44px + .85rem);
}
@media (max-width: 575.98px) {
  .lb-admin-row-actions { margin-left: 0; }
}
.lb-action-group { display: flex; flex-wrap: wrap; gap: .45rem; }
.lb-admin-row-actions .btn-sm { display: inline-flex; align-items: center; gap: .35rem; white-space: nowrap; }

/* Select + submit joined into a single flat control, no gap between the
   two pieces so it reads as one compact unit rather than two controls. */
.lb-combo {
  display: flex;
  align-items: stretch;
  height: 31px;
  border: 1px solid var(--lb-navy-700);
  border-radius: var(--lb-radius-sm);
  overflow: hidden;
  background: transparent;
}
.lb-combo-select {
  border: none;
  border-right: 1px solid var(--lb-navy-700);
  background: transparent;
  color: var(--lb-white);
  font-size: .8rem;
  padding: 0 .4rem;
  outline: none;
}
.lb-combo-select option { background: var(--lb-navy-800); color: var(--lb-white); }
.lb-combo-btn {
  border: none;
  background: transparent;
  color: var(--lb-white);
  font-size: .8rem;
  font-weight: 600;
  padding: 0 .65rem;
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  white-space: nowrap;
  transition: background .15s ease, color .15s ease;
}
.lb-combo-btn:hover { background: var(--lb-lime); color: var(--lb-ink); }

/* Active-state pill for Promote/Hero — a quiet way to show "this is on"
   with a single tap to turn it back off, instead of a loud coral button. */
.lb-status-chip {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  height: 31px;
  padding: 0 .35rem 0 .7rem;
  border: 1px solid var(--lb-navy-700);
  border-radius: var(--lb-radius-sm);
  background: rgba(199,244,100,.08);
  color: var(--lb-lime);
  font-size: .8rem;
  font-weight: 600;
}
.lb-chip-x {
  border: none;
  background: transparent;
  color: var(--lb-mist);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  transition: background .15s ease, color .15s ease;
}
.lb-chip-x:hover { background: var(--lb-coral); color: #fff; }

.lb-kebab-btn { padding-left: .55rem; padding-right: .55rem; }
.dropdown-item.text-coral:hover,
.dropdown-item.text-coral:focus {
  background: var(--lb-coral) !important;
  color: #fff !important;
}

.lb-rank {
  font-family: var(--lb-font-mono);
  color: var(--lb-mist);
  font-size: .85rem;
  width: 22px;
  text-align: center;
  flex-shrink: 0;
  padding-top: .5rem;
}
/* Top-3 rank slot (see rank_badge_html() in functions.php) — the medal
   emoji replaces the plain "1"/"2"/"3" mono digit; ranks 4+ keep the
   plain number as before. */
.lb-rank-medal {
  font-size: 1.35rem; padding-top: .1rem; position: relative;
  display: flex; flex-direction: column; align-items: center; gap: .2rem;
}
.lb-rank-medal-icon { line-height: 1; }

/* Still-running cycle: widen the narrow rank column just enough to fit
   a tiny "Provisional" tag stacked directly under the medal, so the
   "this rank isn't final yet" status reads as part of the medal itself
   rather than as a separate pill elsewhere in the row. Cyan/sky blue is
   deliberately unused by any other badge on the site (VERIFIED is blue,
   PROMOTED/FEATURED are amber/purple, medals are gold/silver/bronze) so
   "provisional" always reads as its own distinct status at a glance. */
.lb-rank-live { width: 64px; }
.lb-rank-live-tag,
.lb-badge-provisional {
  font-size: .5rem; font-weight: 700; letter-spacing: .01em; text-transform: uppercase;
  color: #fff;
  background: #000;
  border-radius: .6rem;
  padding: .05rem .35rem;
  line-height: 1.4;
  white-space: nowrap;
  display: inline-flex; align-items: center; gap: .15rem;
}
.lb-rank-live-tag i { font-size: .9em; }

/* #1 spot only: a little "pop" on the gold medal itself each time the
   looping confetti burst fires (triggered from main.js), so the medal
   feels like part of the celebration rather than just a backdrop for
   confetti pieces flying past it. The confetti pieces themselves are
   rendered via the canvas-confetti library (see footer.php) positioned
   over this element — no extra DOM needed here for them. */
.lb-rank-1.lb-confetti-pop {
  animation: lb-medal-pop .6s ease;
}
@keyframes lb-medal-pop {
  0%   { transform: scale(1); }
  30%  { transform: scale(1.35); }
  60%  { transform: scale(0.92); }
  100% { transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .lb-rank-1.lb-confetti-pop { animation: none; }
}

.lb-logo {
  width: 44px; height: 44px;
  border-radius: var(--lb-radius-sm);
  /* "contain" (not "cover") so a wide/rectangular logo is never cropped —
     it letterboxes inside the box instead of having its edges cut off.
     The white backdrop is what makes that letterboxing look intentional
     (like an app-store icon tile) for logos with transparent PNG/SVG
     backgrounds, and matches regardless of the page's own dark theme. */
  object-fit: contain;
  background: #fff;
  padding: 2px;
  box-sizing: border-box;
  flex-shrink: 0;
}

/* Product logos/icons are always real uploaded images (never the empty-
   state placeholder look), so give the <img> itself the circular
   treatment rather than the box — see .lb-logo above for sizing. */
img.lb-logo {
  border-radius: 50%;
  background: #fff;
}

.lb-badge-featured {
  background: rgba(199,244,100,0.14);
  color: var(--lb-lime);
  font-size: .65rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  padding: .15rem .45rem;
  border-radius: 1rem;
}

.lb-badge-verified {
  background: rgba(88,166,255,0.16);
  color: #58A6FF;
  font-size: .65rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  padding: .15rem .5rem;
  border-radius: 1rem;
  display: inline-flex;
  align-items: center;
  gap: .2rem;
}

.lb-badge-promoted {
  background: rgba(255,183,77,0.16);
  color: #FFB74D;
  font-size: .65rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  padding: .15rem .5rem;
  border-radius: 1rem;
}

.lb-badge-hero {
  background: rgba(185,140,255,0.18);
  color: #B98CFF;
  font-size: .65rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  padding: .15rem .5rem;
  border-radius: 1rem;
  display: inline-flex;
  align-items: center;
  gap: .2rem;
}

/* Small inline pill for a permanent Editor's Choice pick — same family as
   .lb-badge-hero/.lb-badge-verified above, used anywhere a product shows
   up in a compact list row (see product_badges_html() in functions.php),
   as opposed to the bigger standalone .lb-award-card-editor-choice used
   on the product page's AWARDS panel / profile Trophy Case / the
   dedicated /editor-choice listing page. */
.lb-badge-editor-choice {
  background: rgba(255, 214, 92, 0.18);
  color: #FFD65C;
  font-size: .65rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  padding: .15rem .5rem;
  border-radius: 1rem;
  display: inline-flex;
  align-items: center;
  gap: .2rem;
}

/* Icon-only variant of the Verified / Featured / Editor's Choice badges —
   combined with the color classes above (e.g. class="lb-badge-verified
   lb-badge-icon"). Strips the pill chrome (background/padding/uppercase
   label) down to just the glyph; the label moves into a native Bootstrap
   tooltip (data-bs-toggle="tooltip" title="...") shown on hover/focus
   instead of being written out inline. */
.lb-badge-icon {
  background: transparent;
  padding: 0;
  border-radius: 0;
  font-size: 1.2rem;
  line-height: 1;
  cursor: default;
}
.lb-badge-icon i { line-height: 1; }

/* Gradient fills for the Verified / Featured / Editor's Choice glyphs —
   applied straight to the <i> icon via background-clip:text so it renders
   as a gradient-colored icon no matter which variant it's nested inside
   (the compact icon-only .lb-badge-icon rows used across product cards
   site-wide, or the labeled pill used in the admin panel). Text labels
   next to the icon (e.g. admin's "VERIFIED" pill text) are unaffected —
   only the glyph itself picks up the gradient. */
.lb-badge-verified i,
.lb-badge-hero i,
.lb-badge-editor-choice i {
  display: inline-block;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}
.lb-badge-verified i {
  background-image: linear-gradient(135deg, #7DD3FC 0%, #3B82F6 50%, #1D4ED8 100%);
}
.lb-badge-hero i {
  background-image: linear-gradient(135deg, #86EFAC 0%, #22C55E 50%, #15803D 100%);
}
.lb-badge-editor-choice i {
  background-image: linear-gradient(135deg, #1A1A1A 0%, #FFD700 45%, #8A6D00 75%, #1A1A1A 100%);
}

/* Standalone gradient icon utility classes — same three gradients as the
   badges above, but usable directly on any <i> glyph that isn't wrapped in
   a .lb-badge-* pill (page headers, empty states, pricing card headings —
   see editor-choice.php, verify.php, feature.php). */
.lb-icon-gradient-verified,
.lb-icon-gradient-hero,
.lb-icon-gradient-editor-choice {
  display: inline-block;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}
.lb-icon-gradient-verified {
  background-image: linear-gradient(135deg, #7DD3FC 0%, #3B82F6 50%, #1D4ED8 100%);
}
.lb-icon-gradient-hero {
  background-image: linear-gradient(135deg, #86EFAC 0%, #22C55E 50%, #15803D 100%);
}
.lb-icon-gradient-editor-choice {
  background-image: linear-gradient(135deg, #1A1A1A 0%, #FFD700 45%, #8A6D00 75%, #1A1A1A 100%);
}

/* Same black-and-gold treatment for the bigger standalone Editor's Choice
   trophy icon used on the product page AWARDS panel / profile Trophy Case
   (.lb-award-card-editor-choice) — scoped so it never touches the plain
   gold/silver/bronze rank medals, which share the same .lb-award-card-icon
   container class but aren't Editor's Choice. */
.lb-award-card-editor-choice .lb-award-card-icon i {
  display: inline-block;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  background-image: linear-gradient(135deg, #1A1A1A 0%, #FFD700 45%, #8A6D00 75%, #1A1A1A 100%);
}

/* Award medals: #1/#2/#3 Product of the Day/Week/Month recognition.
   Colored by RANK (universal gold/silver/bronze), not by period type, so
   a #1 badge looks the same whether it's a daily, weekly, or monthly win —
   consistent across Today's launches / Yesterday / Last Week / Last Month. */
.lb-badge-award {
  font-size: .65rem; font-weight: 700; letter-spacing: .02em; text-transform: none;
  padding: .15rem .55rem;
  border-radius: 1rem;
  display: inline-flex; align-items: center; gap: .25rem;
  white-space: nowrap;
}
.lb-badge-award-rank-1 { background: var(--lb-gold-bg); color: var(--lb-gold); } /* Gold */
.lb-badge-award-rank-2 { background: var(--lb-silver-bg); color: var(--lb-silver); } /* Silver */
.lb-badge-award-rank-3 { background: var(--lb-bronze-bg); color: var(--lb-bronze); } /* Bronze */

/* Live/in-progress medal (today's standings, not locked in yet) — same
   pill, just a dashed outline and a small broadcast icon so it visibly
   reads as provisional rather than an official award. */
.lb-badge-award-live { border: 1px dashed currentColor; }
.lb-badge-award-live i { font-size: .85em; }

/* Larger medal chip used on trophy cases / awards leaderboard */
.lb-medal-chip {
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .5rem .85rem;
  border-radius: var(--lb-radius-md);
  border: 1px solid var(--lb-navy-800);
  background: var(--lb-navy-900);
  font-size: .8rem; font-weight: 600;
}
.lb-medal-chip .lb-medal-emoji { font-size: 1.1rem; line-height: 1; }
.lb-medal-rank-1 { border-color: var(--lb-gold-border); }  /* Gold */
.lb-medal-rank-2 { border-color: var(--lb-silver-border); } /* Silver */
.lb-medal-rank-3 { border-color: var(--lb-bronze-border); }  /* Bronze */

/* Product-detail-page AWARDS panel: full cards (icon, title, date) rather
   than compact chips, so every award a product has ever won — or is
   currently on track to win, see get_all_product_awards_with_live() — is
   clearly laid out one per card. Colored by rank, same gold/silver/bronze
   palette as everywhere else. Card base is a plain white panel (same idea
   as the FAQ accordion's card) with the medal color kept only as a soft
   gradient wash at the top — rather than the tinted color fading into the
   navy panel background — so the award itself stays the focal point in
   both themes; dark mode swaps the white base back to a navy one further
   down. */
.lb-award-card {
  border: 1px solid var(--lb-navy-600);
  border-radius: var(--lb-radius-md);
  background: #FFFFFF;
  padding: 1.25rem 1rem;
  text-align: center;
  height: 100%;
  transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease;
}
.lb-award-card:hover { transform: translateY(-2px); }
.lb-award-card-icon { font-size: 2.1rem; line-height: 1; margin-bottom: .35rem; filter: drop-shadow(0 2px 6px rgba(0,0,0,.15)); }
/* Small rank pill ("#1"/"#2"/"#3") sits between the medal and the award-type
   recognition text below it, colored to match the medal (gold/silver/bronze).
   */
.lb-award-card-rank {
  display: inline-block;
  font-family: var(--lb-font-mono);
  font-weight: 700; font-size: .7rem; letter-spacing: .03em;
  padding: .1rem .5rem;
  border-radius: 1rem;
  margin-bottom: .45rem;
}
/* Award-type recognition text — "Launch of the Day/Week/Month" — the main,
   most prominent line on the card so the period a product won is legible
   at a glance, not buried in a compact combined title. */
.lb-award-card-title { font-weight: 700; font-size: .92rem; color: var(--lb-white); margin-bottom: .3rem; line-height: 1.25; }
.lb-award-card-date { font-size: .75rem; color: var(--lb-mist); }
.lb-award-card-live-tag {
  display: inline-block; margin-left: .35rem;
  font-size: .65rem; font-weight: 700; letter-spacing: .02em;
  padding: .05rem .4rem; border-radius: 1rem;
  border: 1px dashed currentColor;
}
.lb-award-card-rank-1 {
  border-color: var(--lb-gold-border);
  background: linear-gradient(180deg, var(--lb-gold-bg) 0%, #FFFFFF 70%);
}
.lb-award-card-rank-1:hover { box-shadow: 0 8px 22px -10px var(--lb-gold-border); }
.lb-award-card-rank-1 .lb-award-card-rank { background: var(--lb-gold-bg); color: var(--lb-gold); }
.lb-award-card-rank-1 .lb-award-card-live-tag { color: var(--lb-gold); }
.lb-award-card-rank-2 {
  border-color: var(--lb-silver-border);
  background: linear-gradient(180deg, var(--lb-silver-bg) 0%, #FFFFFF 70%);
}
.lb-award-card-rank-2:hover { box-shadow: 0 8px 22px -10px var(--lb-silver-border); }
.lb-award-card-rank-2 .lb-award-card-rank { background: var(--lb-silver-bg); color: var(--lb-silver); }
.lb-award-card-rank-2 .lb-award-card-live-tag { color: var(--lb-silver); }
.lb-award-card-rank-3 {
  border-color: var(--lb-bronze-border);
  background: linear-gradient(180deg, var(--lb-bronze-bg) 0%, #FFFFFF 70%);
}
.lb-award-card-rank-3:hover { box-shadow: 0 8px 22px -10px var(--lb-bronze-border); }
.lb-award-card-rank-3 .lb-award-card-rank { background: var(--lb-bronze-bg); color: var(--lb-bronze); }
.lb-award-card-rank-3 .lb-award-card-live-tag { color: var(--lb-bronze); }

/* Editor's Choice — a permanent, curatorial award (see
   record_editor_choice_award() in functions.php) rather than a
   votes-ranked #1/#2/#3 medal, so it gets its own purple treatment and a
   "CURATED" pill instead of a rank number. */
.lb-award-card-editor-choice {
  border-color: var(--lb-curated-border);
  background: linear-gradient(180deg, var(--lb-curated-bg) 0%, #FFFFFF 70%);
}
.lb-award-card-editor-choice:hover { box-shadow: 0 8px 22px -10px var(--lb-curated-border); }
.lb-award-card-editor-choice .lb-award-card-rank { background: var(--lb-curated-bg); color: var(--lb-curated); }
.lb-award-card-editor-choice .lb-award-card-icon { color: var(--lb-curated); }
/* Dark mode: swap the white card base back to the navy panel tone (same
   swap the FAQ accordion doesn't need, since it never had a white base to
   begin with) — the gradient wash at the top is unchanged. */
[data-theme="dark"] .lb-award-card { background: var(--lb-navy-800); }
[data-theme="dark"] .lb-award-card-rank-1 { background: linear-gradient(180deg, var(--lb-gold-bg) 0%, var(--lb-navy-800) 70%); }
[data-theme="dark"] .lb-award-card-rank-2 { background: linear-gradient(180deg, var(--lb-silver-bg) 0%, var(--lb-navy-800) 70%); }
[data-theme="dark"] .lb-award-card-rank-3 { background: linear-gradient(180deg, var(--lb-bronze-bg) 0%, var(--lb-navy-800) 70%); }
[data-theme="dark"] .lb-award-card-editor-choice { background: linear-gradient(180deg, var(--lb-curated-bg) 0%, var(--lb-navy-800) 70%); }
/* Product name/link under the date — only used on the profile Trophy Case
   tab, where (unlike the single-product AWARDS panel this markup is
   otherwise shared with) cards can belong to several different products. */
.lb-award-card-product {
  margin-top: .35rem;
  padding-top: .35rem;
  border-top: 1px dashed var(--lb-navy-800);
  font-size: .72rem;
  font-weight: 600;
  color: var(--lb-mist);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
a:hover .lb-award-card-product { color: var(--lb-lime); }

/* Awards leaderboard page rows */
.lb-award-row { transition: border-color .15s ease, transform .15s ease; }
.lb-award-row:hover { transform: translateY(-1px); }
.lb-award-rank-emoji { font-size: 1.8rem; line-height: 1; flex-shrink: 0; width: 2.2rem; text-align: center; }
.lb-award-rank-1 { border-color: var(--lb-gold-border); }  /* Gold */

/* Rating summary card at the top of the product page's RATING section —
   mobile-first: the big-number/stars block and the 5→1 star distribution
   bars stack full-width by default (col-12) and only sit side by side
   from the sm breakpoint up (see the col-sm-4 / col-sm-8 split in
   product/view.php), so it reads cleanly on a phone before anything else. */
.lb-rating-summary-score { font-size: 2.75rem; font-weight: 800; color: var(--lb-white); line-height: 1; }
.lb-rating-bar-row { display: flex; align-items: center; gap: .6rem; }
.lb-rating-bar-row + .lb-rating-bar-row { margin-top: .4rem; }
.lb-rating-bar-label { flex-shrink: 0; width: 1.1rem; font-size: .78rem; font-weight: 700; color: var(--lb-mist); text-align: right; }
/* "Thin" is the point — a slim 6px track, not a bulky default progress bar. */
.lb-rating-bar-track {
  flex-grow: 1;
  height: 6px;
  border-radius: 999px;
  background: var(--lb-navy-800);
  overflow: hidden;
}
.lb-rating-bar-fill {
  height: 100%;
  border-radius: 999px;
  background: var(--lb-lime);
}
.lb-rating-bar-count { flex-shrink: 0; width: 2.4rem; font-size: .75rem; color: var(--lb-mist); text-align: right; }

.lb-award-rank-2 { border-color: var(--lb-silver-border); } /* Silver */
.lb-award-rank-3 { border-color: var(--lb-bronze-border); }  /* Bronze */

/* Dashed border marks it as a paid placement — deliberately no extra
   horizontal padding beyond the base .lb-product-row's, so the logo,
   name, and stats all line up in exactly the same columns as every
   organic row above/below it instead of looking indented/offset.
   A very light amber gradient (same hue as the PROMOTED badge) gives it
   a subtle "sponsored" tint without looking like a jarring ad block. */
.lb-promoted-row {
  border: 1px dashed rgba(255,183,77,0.35);
  border-radius: var(--lb-radius-sm);
  background: linear-gradient(135deg, rgba(255,183,77,0.08), rgba(255,183,77,0.02) 60%);
}
.lb-promoted-row:hover {
  background: linear-gradient(135deg, rgba(255,183,77,0.12), rgba(255,183,77,0.04) 60%);
}
/* Empty leading spacer (no rank number — promoted rows aren't organically
   ranked) so the logo/name/stats columns still line up with the organic
   rows around it instead of the logo sitting further left than everyone
   else's. */
.lb-promoted-row .lb-rank {
  width: 22px;
}

/* Standalone variant used when a Promoted card is shown on its own inside
   its own panel (the product detail page's two Promoted placements)
   rather than woven into a list of other rows. Carries the same flex
   layout/padding/interactive-layering .lb-product-row normally provides,
   without the list-row border-bottom divider or grey hover tint that
   only make sense stacked between other rows in a feed — the dashed
   border + amber tint above already give it plenty of presence on its own. */
.lb-promoted-row.lb-promoted-standalone {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: .85rem;
  padding: .85rem;
  position: relative;
}
.lb-promoted-row.lb-promoted-standalone > .flex-grow-1.min-width-0 { flex-basis: 0; }
.lb-promoted-row.lb-promoted-standalone .lb-vote-btn,
.lb-promoted-row.lb-promoted-standalone .lb-row-stats,
.lb-promoted-row.lb-promoted-standalone .lb-maker-stack {
  position: relative;
  z-index: 2;
}

/* Minimal variant (product detail page's own Promoted placement — see
   render_promoted_row()'s $minimal param): centers the logo/name/tagline
   against the "Visit {Name}" button on the cross axis, so the button sits
   at the vertical middle of the card on the right rather than pinned to
   the top like the stats column in the full card. */
.lb-promoted-row-minimal {
  align-items: center;
}
.lb-promoted-visit-btn {
  white-space: nowrap;
  pointer-events: none; /* clicks pass through to the row's data-promo-go handler instead of the button intercepting them */
  background: var(--lb-navy-950); /* true white in light mode, true dark in dark mode */
}

.lb-tag {
  background: transparent;
  border: 1px solid var(--lb-navy-700);
  color: var(--lb-mist);
  font-size: .72rem;
  padding: .12rem .5rem;
  border-radius: 1rem;
  display: inline-block;
}

/* Clickable tags (product page "about" tags -> /tag?slug=…) */
a.lb-tag-link { text-decoration: none; transition: border-color .15s, color .15s; }
a.lb-tag-link:hover, a.lb-tag-link:focus-visible { border-color: var(--lb-lime); color: var(--lb-lime); }

/* Landing page/section anchor so the sticky topbar never covers the top of
   the list when jumped/scrolled to (see #today-list + initSortScroll() in main.js). */
#today-list {
  /* Was topbar-height-only, but the category pill bar (.lb-cat-wrap) also
     sticks just below the topbar on this page — without accounting for
     it too, the Today/Top/Upcoming buttons below would scroll the list up
     underneath the category bar instead of stopping right below it.
     --lb-cat-h is the bar's real measured height (see main.js), falling
     back to a reasonable estimate before JS has run/measured it. */
  scroll-margin-top: calc(var(--lb-topbar-h) + var(--lb-cat-h, 52px) + .75rem);
}

/* ---- Next-launch countdown (homepage "Upcoming" button + list panel) ---- */
.lb-countdown-badge {
  display: inline-block;
  margin-left: .3rem;
  padding: .05rem .4rem;
  border-radius: 1rem;
  font-family: var(--lb-font-mono);
  font-size: .68rem;
  background: rgba(0,0,0,.15);
}
.btn-outline-lime .lb-countdown-badge { border: 1px solid currentColor; }
.btn-lime .lb-countdown-badge { background: rgba(11,14,31,.15); }
.lb-countdown-panel { border: 1px dashed var(--lb-navy-700); }
.lb-countdown-big { font-size: 1.05rem; font-weight: 700; letter-spacing: .02em; }
.lb-countdown-live { color: var(--lb-coral) !important; }

/* Category pill nav — quiet, horizontal scroll on mobile */
.lb-category-pill {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .4rem .85rem;
  border-radius: 999px;
  border: 1px solid var(--lb-navy-800);
  color: var(--lb-mist);
  font-size: .82rem;
  white-space: nowrap;
}
.lb-category-pill.active, .lb-category-pill:hover { border-color: var(--lb-lime); color: var(--lb-lime); }

/* Panels used sparingly now — forms, stat boxes, key surfaces only */
.lb-panel { background: var(--lb-navy-900); border: 1px solid var(--lb-navy-800); border-radius: var(--lb-radius-lg); }

/* Makers directory card (see makers.php) — whole card is a stretched-link
   to that maker's profile, so give it the same subtle hover lift as other
   clickable panels on the site. */
.lb-maker-card { position: relative; z-index: 1; transition: border-color .15s ease, transform .15s ease; }
.lb-maker-card:hover { border-color: var(--lb-lime); transform: translateY(-2px); }
/* Every .lb-maker-card sits at the same z-index (1), so with the ":hover"
   transform above forming its own stacking context, cards still paint in
   plain DOM order — meaning a card's open "..." dropdown menu (which drops
   DOWN and overlaps the next card below it) was getting visually covered
   by that next card, since it comes later in the DOM and therefore paints
   on top by default. Raising z-index while hovered/focused (the dropdown
   toggle keeps focus while its menu is open) — or while its menu is
   actually open, for keyboard/no-hover cases — lifts the whole card above
   its neighbors so items like ".lb-block-btn" stay visible on hover. */
.lb-maker-card:hover,
.lb-maker-card:focus-within,
.lb-maker-card:has(.dropdown-menu.show) {
  z-index: 20;
}


/* =========================================================================
   PRODUCT PAGE QUICK-JUMP SUB-NAV — TOP / LINKS / BUILT WITH / MAKER /
   RATING / COMMENTS, sticks flush against the bottom of the main topbar
   (top: 0 relative to it, no gap) once scrolled to (see product/view.php).

   The sticky positioning lives on the OUTER wrap (.lb-subnav-wrap), not on
   the scrollable nav pill row itself — putting position:sticky on an
   element that also has overflow-x:auto is the classic way to end up with
   a sub-nav that silently refuses to stick in some browsers. The wrap also
   explicitly gets its own z-index so it forms a stacking context: that's
   what guarantees the prev/next arrow buttons (z-index 5) always paint
   above the nav row (z-index: auto) instead of getting tucked underneath
   its opaque background.
   ========================================================================= */
.lb-subnav-wrap {
  position: sticky;
  /* Stick just below the main navbar, like the original script.
     The hero section's 20px gap sits BELOW this, not affected by sticky
     positioning — they're independent. */
  top: var(--lb-topbar-h);
  z-index: 1020;
}
@media (min-width: 992px) {
  /* Contained/rounded pill-bar look (below) is the DEFAULT, in-flow
     appearance. Once actually pinned under the topbar — .is-stuck,
     toggled by the same watchStickyState() IntersectionObserver helper
     used for the homepage category bar, watching #lbSubnavSentinel — it
     switches to the connected, edge-to-edge look: breaks out of
     `.lb-docked-main` by one shell gap on each side to land flush against
     the left panel's border-right and (xl+) the right panel's
     border-left, no radius, bottom border only. Same technique as
     .lb-cat-wrap.is-stuck in the CATEGORY PILL BAR block below — see the
     comment there for why this is a fixed-gap breakout and not the
     mobile block's 100vw viewport trick. */
  .lb-subnav-wrap.is-stuck {
    width: calc(100% + var(--lb-space-6) * 2);
    margin-left: calc(-1 * var(--lb-space-6));
    margin-right: calc(-1 * var(--lb-space-6));
    padding-left: var(--lb-space-6);
    padding-right: var(--lb-space-6);
  }
  .lb-subnav-wrap.is-stuck .lb-product-subnav {
    border-radius: 0;
    border: 0;
    border-bottom: 1px solid var(--lb-navy-800);
    background: rgba(var(--lb-navy-950-rgb), .92);
  }
}
@media (max-width: 991.98px) {
  /* On mobile with a hero video, same behavior — stick at topbar height. */
  .lb-hero-gap-video .lb-subnav-wrap {
    top: var(--lb-topbar-h);
  }
  /* Full-bleed on mobile only: break out of the column/container padding so
     the bar spans the entire viewport edge-to-edge starting at the device's
     left edge, no matter how deeply nested it is inside centered/padded
     parents. Desktop keeps the original contained/rounded look below. */
  .lb-subnav-wrap {
    width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
  }
  .lb-product-subnav {
    border-left: none;
    border-right: none;
    border-radius: 0px!important;
  }
}
.lb-product-subnav {
  display: block;
  width: 100%;
  background: rgba(var(--lb-navy-950-rgb), .92);
  backdrop-filter: blur(10px);
  border: 1px solid var(--lb-navy-800);
  border-radius: var(--lb-radius-lg);
  padding: .7rem 1.5rem;
  overflow: hidden;
}
/* Extra side padding while the prev/next arrows are actually showing (see
   updateSubnavArrows() in product/view.php) — the 28px arrow button plus
   its 6px offset needs more clearance than the row's normal 1.5rem side
   padding, or it sits on top of the first/last pill link on any screen
   size, not just mobile. */
.lb-product-subnav.lb-subnav-has-arrows {
  padding-left: 2.5rem;
  padding-right: 2.5rem;
}
.lb-product-subnav .swiper-wrapper {
  align-items: center;
  /* No CSS `gap` here on purpose — same as the homepage category pill bar's
     .lb-category-swiper (see main.js), which has no matching CSS gap rule
     either. Swiper's own `spaceBetween: 8` JS param (product/view.php) is
     the ONLY source of spacing between slides. A CSS flex `gap` here
     "worked" visually but silently desynced Swiper's internal width math
     from the real rendered row: Swiper computed each slide's total size
     using ITS OWN spaceBetween value, with no idea the browser was also
     inserting a wider `gap` between them, so its overflow detection
     (isLocked/isBeginning/isEnd, driving the prev/next chevrons) measured
     a virtualSize that was too small to ever equal the real, wider,
     actually-overflowing row. Net effect: the row visibly overflowed and
     cut content off the right edge, but the chevrons never appeared to
     reveal it. Letting spaceBetween be the single source of truth (exactly
     like lb-cat-inner) keeps Swiper's math and the real layout in sync. */
}
.lb-product-subnav .swiper-slide {
  flex-shrink: 0;
}
.lb-subnav-link {
  flex-shrink: 0;
  color: var(--lb-mist);
  font-family: var(--lb-font-mono);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .04em;
  text-decoration: none;
  padding: .3rem .1rem;
  border-bottom: 2px solid transparent;
  transition: color .15s ease, border-color .15s ease;
}
.lb-subnav-link:hover { color: var(--lb-white); }
.lb-subnav-link.active { color: var(--lb-white); border-color: var(--lb-lime); }

.lb-subnav-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: var(--lb-navy-800);
  border: 1px solid var(--lb-navy-700);
  color: var(--lb-white);
  font-size: .7rem;
  cursor: pointer;
  z-index: 5;
  box-shadow: 0 2px 8px rgba(0,0,0,.35);
}
.lb-subnav-arrow:hover { background: var(--lb-lime); color: var(--lb-ink); border-color: var(--lb-lime); }
.lb-subnav-arrow-prev { left: 6px; }
.lb-subnav-arrow-next { right: 6px; }

/* Offsets so anchor jumps land below the sticky topbar + sub-nav instead of
   right underneath them. --lb-subnav-h is the sub-nav's real measured
   height (see main.js), falling back to the old ~2.5rem estimate before
   JS has run/measured it. */
#lb-top, #lb-about, #lb-links, #lb-built-with, #lb-pricing, #lb-awards, #lb-maker, #lb-rating, #lb-comments {
  scroll-margin-top: calc(var(--lb-topbar-h) + var(--lb-subnav-h, 2.5rem) + .5rem);
}
.lb-stat { font-family: var(--lb-font-mono); font-size: 1.4rem; font-weight: 700; color: var(--lb-white); }

/* =========================================================================
   HOMEPAGE CATEGORY PILL BAR — sticks flush under the topbar once scrolled
   to (top: var(--lb-topbar-h), same offset as the product sub-nav above),
   but only SWITCHES to the connected, edge-to-edge look once it's actually
   pinned there — the .is-stuck class toggled by an IntersectionObserver
   watching #lbCatSentinel, the zero-height marker placed immediately
   before this section in index.php (same "sentinel above a sticky
   element" technique, just detecting the stuck state instead of scroll
   position — see the shared watchStickyState() helper in main.js).
   Before it's stuck, this is just the normal contained/padded section from
   the reference layout. Once stuck, it breaks out of `.lb-docked-main`'s
   own bounds by exactly one shell gap (var(--lb-space-6)) on each side —
   NOT a viewport-relative 100vw trick like the mobile sub-nav uses, since
   here the two "edges" to reach are the fixed left/right panels a fixed
   px gap away, not the browser window — landing flush against the left
   panel's border-right and (at xl+) the right panel's border-left with no
   radius, just a bottom border, exactly like a single continuous seam. */
.lb-cat-wrap {
  position: sticky;
  top: var(--lb-topbar-h);
  z-index: 1015;
  transition: background-color .15s ease;
}
.lb-cat-inner { position: relative; }
/* Base (mobile-first) stuck look: a solid/blurred backdrop + hairline
   once pinned, so page content scrolling underneath doesn't show through
   it — previously this background was defined ONLY inside the
   min-width:992px query below, so on phones/tablets the pinned bar
   stayed fully transparent while stuck. The desktop query now only adds
   the extra edge-to-edge breakout mechanics on top of this. */
section.lb-cat-wrap.is-stuck {
  background: rgba(var(--lb-navy-950-rgb), .92);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--lb-navy-800);
}
@media (min-width: 992px) {
  section.lb-cat-wrap.is-stuck {
    width: calc(100% + var(--lb-space-6) * 2) !important;
    margin-left: calc(-1 * var(--lb-space-6)) !important;
    margin-right: calc(-1 * var(--lb-space-6)) !important;
    padding-left: var(--lb-space-6) !important;
    padding-right: var(--lb-space-6) !important;
    border-radius: 0 !important;
  }
}


/* Uniform icon + label + number stat card (admin overview, dashboard, etc.)
   — flex layout keeps the icon, label and number vertically aligned across
   cards of different content lengths. */
.lb-stat-card {
  display: flex; align-items: center; gap: .9rem;
  transition: border-color .15s ease, transform .15s ease;
}
.lb-stat-card-icon {
  flex-shrink: 0;
  width: 44px; height: 44px;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--lb-radius-md);
  background: rgba(199,244,100,.1);
  color: var(--lb-lime);
  font-size: 1.2rem;
}
.lb-stat-card-body { min-width: 0; }
.lb-stat-card-body .lb-stat { line-height: 1.15; }
a.lb-stat-card-link { text-decoration: none; display: block; }
a.lb-stat-card-link:hover .lb-panel { border-color: var(--lb-lime); transform: translateY(-2px); }
a.lb-stat-card-link:hover .lb-stat-card-icon { background: rgba(199,244,100,.18); }

.form-label { color: var(--lb-white); opacity: .75; font-weight: 500; font-size: .85rem; }

/* ---- Inputs, textareas, selects & buttons — site-wide, theme-aware --------
   Applies everywhere (not just the product forms), and reads the same
   --lb-* variables everything else does, so it automatically matches
   whichever theme (light/dark — see [data-theme="dark"] above) is active
   instead of a fixed color. The field itself sits one step lighter than
   its surrounding panel in both themes so it always reads as an input
   well rather than blending into the page. */
.form-control, .form-select, textarea.form-control, .lb-tag-input {
  background-color: var(--lb-navy-800);
  border: 1px solid var(--lb-navy-600);
  color: var(--lb-white);
}
.form-control:focus, .form-select:focus, .lb-tag-input:focus-within {
  background-color: var(--lb-navy-800);
  border-color: var(--lb-lime);
  color: var(--lb-white);
  box-shadow: 0 0 0 .15rem rgba(199,244,100,.15);
}
.form-control::placeholder { color: var(--lb-mist); opacity: .7; }
.form-control:disabled, .form-control[readonly], .form-select:disabled {
  background-color: var(--lb-navy-900);
  color: var(--lb-mist);
  opacity: .8;
}

/* Bootstrap's own gray outline/secondary button colors don't come from
   our theme variables, so left alone they stay a fixed mid-gray that's
   low-contrast in dark mode and slightly flat in light mode — this
   re-points them at --lb-mist / --lb-navy so they match the rest of the
   UI in both themes. */
.btn-outline-secondary {
  --bs-btn-color: var(--lb-mist);
  --bs-btn-border-color: var(--lb-navy-600);
  --bs-btn-hover-bg: var(--lb-navy-800);
  --bs-btn-hover-border-color: var(--lb-navy-600);
  --bs-btn-hover-color: var(--lb-white);
  --bs-btn-active-bg: var(--lb-navy-800);
  --bs-btn-active-border-color: var(--lb-navy-600);
  --bs-btn-active-color: var(--lb-white);
  --bs-btn-disabled-color: var(--lb-mist);
  --bs-btn-disabled-border-color: var(--lb-navy-700);
}
.btn-secondary {
  --bs-btn-bg: var(--lb-navy-800);
  --bs-btn-border-color: var(--lb-navy-600);
  --bs-btn-color: var(--lb-white);
  --bs-btn-hover-bg: var(--lb-navy-700);
  --bs-btn-hover-border-color: var(--lb-navy-600);
  --bs-btn-hover-color: var(--lb-white);
  --bs-btn-active-bg: var(--lb-navy-700);
  --bs-btn-active-color: var(--lb-white);
}
/* Bootstrap's .btn-close is a dark glyph baked into the SVG — invisible
   against a dark background, so it needs inverting in dark mode
   everywhere (previously only handled for the offcanvas). */
[data-theme="dark"] .btn-close { filter: invert(1) grayscale(1); }

/* ---- Image upload dropzones & cards (product submit/edit) -------------------
   Same --lb-navy-800/--lb-navy-600 tokens as .form-control, so an empty
   upload slot reads as the same kind of "field" a text input is, in both
   themes. */
.lb-upload-dropzone {
  position: relative;
  border: 2px dashed var(--lb-navy-600);
  border-radius: var(--lb-radius-md);
  background: var(--lb-navy-800);
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}
.lb-upload-dropzone:hover, .lb-upload-dropzone:focus-visible { border-color: var(--lb-lime); }
.lb-upload-dropzone.lb-dragover { border-color: var(--lb-lime); background: rgba(199,244,100,.06); }
.lb-upload-dropzone-logo { max-width: 220px; }
.lb-upload-dropzone-thumbnail { max-width: 400px; }

.lb-upload-card { position: relative; aspect-ratio: 1 / 1; border-radius: var(--lb-radius-md); overflow: hidden; background: var(--lb-navy-800); border: 1px solid var(--lb-navy-600); }
.lb-upload-card-wide { aspect-ratio: 16 / 9; }
.lb-upload-card img { width: 100%; height: 100%; object-fit: cover; display: block; }

.lb-upload-cards-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: .65rem;
}
.lb-upload-mini-card {
  position: relative;
  aspect-ratio: 1 / 1;
  border-radius: var(--lb-radius-sm);
  overflow: hidden;
  border: 1px solid var(--lb-navy-600);
  background: var(--lb-navy-800);
}
.lb-upload-mini-card img { width: 100%; height: 100%; object-fit: cover; display: block; }

.lb-upload-remove {
  position: absolute;
  top: 6px; right: 6px;
  width: 24px; height: 24px;
  border-radius: 50%;
  background: rgba(11,14,31,.85);
  color: #fff;
  border: none;
  display: flex; align-items: center; justify-content: center;
  font-size: .7rem;
  z-index: 2;
}
.lb-upload-remove:hover { background: var(--lb-coral); }

/* ---- Inline field validation feedback ---------------------------------------- */
.lb-field-feedback {
  font-size: .78rem;
  margin-top: .3rem;
  min-height: 0;
}
.lb-field-feedback.lb-field-ok { color: var(--lb-lime); }
.lb-field-feedback.lb-field-error { color: var(--lb-coral); }
.form-control.lb-input-invalid, .form-select.lb-input-invalid {
  border-color: var(--lb-coral);
}
.form-check-input.lb-input-invalid { border-color: var(--lb-coral); box-shadow: 0 0 0 1px var(--lb-coral); }
.lb-tag-input.lb-input-invalid { border-color: var(--lb-coral); }

/* ---- Shareable stats card preview (Fabric.js) — product/view.php ---------
   This <canvas> is a plain 2D canvas that share-card.js draws into
   directly (contain-fit, ~300px) — it is NOT the canvas Fabric renders
   the 1080x1080 card onto (that happens on a detached offscreen canvas
   used only for export). Its width/height pixel buffer + CSS size are
   both set from JS on every render, so this rule is just cosmetic
   styling; .modal-dialog-scrollable on the modal (see product/view.php)
   is the fallback for anything still too tall to fit. */
/* ---- Share-card modal layout — product/view.php #shareCardModal --------
   Left: scrollable template (layout) rail. Right: color-theme swatches on
   top, canvas + actions below. Stacks to rail-on-top on narrow screens
   since there's no room for a side rail once the canvas needs ~320px. */
.lb-share-layout {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.lb-share-rail {
  display: flex;
  flex-direction: row;
  gap: .6rem;
  overflow-x: auto;
  overflow-y: hidden;
  padding-bottom: .35rem;
  flex-shrink: 0;
}
.lb-share-main {
  flex: 1;
  min-width: 0;
}
@media (min-width: 992px) {
  .lb-share-layout { flex-direction: row; align-items: flex-start; }
  .lb-share-rail {
    flex-direction: column;
    width: 132px;
    flex: 0 0 132px;
    overflow-x: hidden;
    overflow-y: auto;
    max-height: 560px;
    padding-right: .35rem;
    padding-bottom: 0;
  }
}

/* Template rail buttons + their little CSS-drawn preview mockups */
.lb-tpl-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .4rem;
  flex-shrink: 0;
  width: 104px;
  padding: .55rem .5rem;
  border-radius: 12px;
  border: 1.5px solid var(--lb-navy-700);
  background: var(--lb-navy-800);
  color: var(--lb-mist);
  cursor: pointer;
  transition: border-color .12s ease, background .12s ease, color .12s ease;
}
.lb-tpl-btn:hover { border-color: var(--lb-mist); color: var(--lb-white); }
.lb-tpl-btn.active { border-color: var(--lb-lime); background: rgba(199,244,100,0.08); color: var(--lb-white); }
.lb-tpl-name { font-size: .72rem; font-weight: 600; letter-spacing: .01em; }

.lb-tpl-thumb {
  position: relative;
  width: 76px;
  height: 62px;
  border-radius: 8px;
  background: linear-gradient(135deg, #0B0E1F, #171C3A);
  border: 1px solid rgba(255,255,255,.08);
  overflow: hidden;
  flex-shrink: 0;
}
.lb-tpl-thumb i { position: absolute; display: block; border-radius: 3px; background: var(--lb-lime); font-style: normal; }

/* Card: 3 small tiles + 1 wide tile underneath */
.lb-tpl-card i:nth-child(1) { left: 6px; top: 8px; width: 18px; height: 16px; opacity: .9; }
.lb-tpl-card i:nth-child(2) { left: 29px; top: 8px; width: 18px; height: 16px; opacity: .7; }
.lb-tpl-card i:nth-child(3) { left: 52px; top: 8px; width: 18px; height: 16px; opacity: .5; }
.lb-tpl-card i:nth-child(4) { left: 6px; top: 30px; width: 64px; height: 24px; opacity: .25; }

/* Bento: one big tile + two stacked small tiles */
.lb-tpl-bento i:nth-child(1) { left: 6px; top: 8px; width: 38px; height: 46px; opacity: .9; }
.lb-tpl-bento i:nth-child(2) { left: 48px; top: 8px; width: 22px; height: 20px; opacity: .7; }
.lb-tpl-bento i:nth-child(3) { left: 48px; top: 34px; width: 22px; height: 20px; opacity: .5; }

/* Minimal: plain centered lines, no boxes/fills */
.lb-tpl-minimal { background: linear-gradient(135deg, #0B0E1F, #171C3A); }
.lb-tpl-minimal i { background: transparent; border-radius: 50%; }
.lb-tpl-minimal i:nth-child(1) { left: 32px; top: 8px; width: 12px; height: 12px; background: var(--lb-lime); border-radius: 50%; }
.lb-tpl-minimal i:nth-child(2) { left: 18px; top: 28px; width: 40px; height: 3px; background: rgba(255,255,255,.55); border-radius: 2px; }
.lb-tpl-minimal i:nth-child(3) { left: 24px; top: 38px; width: 28px; height: 3px; background: rgba(255,255,255,.3); border-radius: 2px; }

/* Poster: huge type block + a solid colored stat bar */
.lb-tpl-poster i:nth-child(1) { left: 6px; top: 8px; width: 64px; height: 20px; opacity: .9; border-radius: 4px; }
.lb-tpl-poster i:nth-child(2) { left: 6px; top: 34px; width: 64px; height: 18px; background: var(--lb-lime); opacity: 1; }

/* Photo BG: a "photo" gradient fill with a text bar across the bottom */
.lb-tpl-photo { background: linear-gradient(135deg, #3a2a52, #1a3a4a, #17301f); }
.lb-tpl-photo i:nth-child(1) { left: 0; bottom: 0; top: auto; width: 100%; height: 20px; background: rgba(11,14,31,.65); border-radius: 0; }

.lb-share-card-canvas {
  display: block;
  margin: 0 auto;
  max-width: 100%;
  border-radius: 12px;
  box-shadow: 0 12px 40px rgba(0,0,0,.35);
}

/* ---- Share-card theme swatches — product/view.php #shareCardThemePicker --
   Each button carries its own --sw-a/--sw-b/--sw-accent custom properties
   (set inline per swatch) so this one rule paints all four presets: a
   diagonal two-tone disc for the theme's background gradient, plus a
   small accent dot bottom-right. The active swatch gets a lime ring +
   check mark; share-card.js toggles .active and aria-pressed on click. */
.lb-share-theme-swatch {
  position: relative;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  padding: 0;
  border: 2px solid var(--lb-navy-700);
  background: linear-gradient(135deg, var(--sw-a) 0%, var(--sw-a) 50%, var(--sw-b) 50%, var(--sw-b) 100%);
  cursor: pointer;
  flex-shrink: 0;
  transition: transform .12s ease, border-color .12s ease, box-shadow .12s ease;
}
.lb-share-theme-swatch::after {
  content: '';
  position: absolute;
  right: -2px;
  bottom: -2px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--sw-accent);
  border: 2px solid var(--lb-navy-900);
}
.lb-share-theme-swatch:hover {
  transform: translateY(-1px);
  border-color: var(--lb-mist);
}
.lb-share-theme-swatch.active {
  border-color: var(--lb-lime);
  box-shadow: 0 0 0 3px rgba(199,244,100,0.22);
}
.lb-share-theme-swatch.active::before {
  content: '\f26e'; /* bootstrap-icons check-lg, rendered via its own font so no extra markup/JS is needed */
  font-family: 'bootstrap-icons' !important;
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  color: #fff;
  text-shadow: 0 1px 3px rgba(0,0,0,.6);
}

/* ---- FAQ repeater drag handle (SortableJS) — product submit/edit ---------- */
.faq-drag-handle {
  flex-shrink: 0;
  width: 28px;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  color: var(--lb-mist);
  cursor: grab;
  touch-action: none;
}
.faq-drag-handle:active { cursor: grabbing; }
.faq-drag-handle:hover { color: var(--lb-lime); }
.faq-row-ghost { opacity: .4; }
.faq-row.sortable-chosen { box-shadow: 0 0 0 1px var(--lb-lime); }

/* "Add another FAQ" uses the site's solid lime accent (same as every other
   primary action button) rather than the plain navy outline style, so it
   reads as the one "do this" affordance in the FAQ repeater — see the
   btn-lime class on the button itself in product/submit.php + edit.php. */

/* ---- Promo code UI (boost/hero/verification/subscribe checkout cards) ----- */
.lb-promo-banner {
  background: color-mix(in srgb, var(--lb-lime) 12%, transparent);
  border: 1px dashed var(--lb-lime);
  border-radius: 8px;
  padding: .5rem .75rem;
  font-size: .82rem;
  color: var(--lb-ink-soft, var(--lb-mist));
  margin: .75rem 0;
}
.lb-promo-banner strong { color: var(--lb-lime-dark); letter-spacing: .03em; }
.lb-promo-field .lb-promo-feedback.text-lime { color: var(--lb-lime-dark) !important; }
.lb-promo-field .lb-promo-feedback.text-coral { color: var(--lb-coral) !important; }

/* ---- Submit form: sectioned layout ---------------------------------------- */
.lb-form-section {
  padding-bottom: var(--lb-space-5, 1.75rem);
  margin-bottom: var(--lb-space-5, 1.75rem);
  border-bottom: 1px solid var(--lb-navy-800);
}
.lb-form-section-last { border-bottom: none; margin-bottom: 0; padding-bottom: 0; }
.lb-form-section-header {
  display: flex;
  align-items: center;
  gap: .75rem;
  margin-bottom: 1.25rem;
}
.lb-form-section-num {
  flex-shrink: 0;
  width: 30px; height: 30px;
  border-radius: 50%;
  background: rgba(199,244,100,.12);
  color: var(--lb-lime);
  font-weight: 700;
  font-size: .9rem;
  display: flex; align-items: center; justify-content: center;
}

/* ---- Tag chip input ------------------------------------------------------------ */
.lb-tag-input {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .4rem;
  padding: .5rem .6rem;
  background: var(--lb-navy-800);
  border: 1px solid var(--lb-navy-600);
  border-radius: var(--lb-radius-sm);
}
.lb-tag-input:focus-within { border-color: var(--lb-lime); }
.lb-tag-chip {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  background: var(--lb-navy-800);
  border: 1px solid var(--lb-navy-700);
  color: var(--lb-white);
  font-size: .8rem;
  padding: .2rem .3rem .2rem .6rem;
  border-radius: 999px;
}
.lb-tag-chip-remove {
  display: flex; align-items: center; justify-content: center;
  width: 16px; height: 16px;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: var(--lb-mist);
  font-size: .7rem;
  padding: 0;
}
.lb-tag-chip-remove:hover { background: var(--lb-coral); color: #fff; }
#tagTextInput {
  flex: 1 1 120px;
  min-width: 120px;
  background: transparent;
  border: none;
  outline: none;
  color: var(--lb-white);
  font-size: .9rem;
  padding: .25rem 0;
}
#tagTextInput::placeholder { color: var(--lb-mist); opacity: .6; }

/* Desktop footer (hidden on mobile in favor of bottom nav) */
/* margin-top removed — .lb-docked-outer/.lb-docked-main above now handle
   the breathing room before the footer, so its border-top sits flush
   against the bottom of the shell (and the left/right panels' borders)
   with zero gap, instead of floating var(--lb-space-6) below it. */
.lb-footer { border-top: 1px solid var(--lb-navy-800); color: var(--lb-mist); padding: var(--lb-space-6) 0; font-size: .85rem; }
/* Previously hidden on mobile entirely — now shown, with the bottom nav's
   own height already accounted for via body's padding-bottom above, and
   extra clearance for the fixed promo/scroll-top buttons that float over
   the bottom of the page on phones. */
@media (max-width: 991.98px) { .lb-footer { padding-bottom: calc(var(--lb-space-6) + 90px); } }

/* Sidebar list items (Featured Products / Top Launches asides, etc.): name
   must be able to shrink and ellipsis instead of overflowing past badges —
   flex items default to min-width:auto which blocks shrinking and causes
   the name to overlap the badges/vote count in narrow columns. */
.lb-aside-item-body { flex: 1 1 0%; min-width: 0; }
.lb-aside-item-body > div:first-child { display: flex; align-items: center; gap: .3rem; min-width: 0; row-gap: .2rem; flex-wrap: wrap; }
.lb-aside-item-name {
  flex: 1 1 auto;
  min-width: 30%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Badges are allowed to wrap onto their own line under the name (rather
   than being squeezed to zero width) once a row is carrying more than one
   or two of them — VERIFIED + PROMOTED + FEATURED + an award chip on a
   narrow 320px sidebar column has no room to stay on a single line. */
.lb-aside-item-badges { flex-shrink: 1; display: inline-flex; flex-wrap: wrap; align-items: center; gap: .25rem; row-gap: .2rem; }
.lb-aside-item-badges:empty { display: none; }

/* Trending Products cards inside the main product-details column (2-up grid) */
.lb-trending-card {
  border: 1px solid var(--lb-navy-800);
  border-radius: var(--lb-radius-md);
  transition: border-color .15s ease, background .15s ease;
}
.lb-trending-card:hover { border-color: var(--lb-lime); background: rgba(199,244,100,.04); }

/* Promoted campaigns woven into the TRENDING PRODUCTS grid (see
   product/view.php) still need their dashed amber border/tint to stand
   out from the organic trending cards around them — the two-class
   selector here beats plain .lb-trending-card's solid border despite
   .lb-promoted-row being declared earlier in this file. */
.lb-trending-card.lb-promoted-row {
  border: 1px dashed rgba(255,183,77,0.35);
  background: linear-gradient(135deg, rgba(255,183,77,0.08), rgba(255,183,77,0.02) 60%);
}
.lb-trending-card.lb-promoted-row:hover {
  border-color: rgba(255,183,77,0.5);
  background: linear-gradient(135deg, rgba(255,183,77,0.12), rgba(255,183,77,0.04) 60%);
}

/* Vote-count pill in the TOP LAUNCHES / TRENDING PRODUCTS cards (see
   product/view.php) — was plain text before; a border makes it read as its
   own status chip instead of floating text next to the tagline. */
.lb-trending-card-votes {
  display: inline-flex;
  align-items: center;
  gap: .25rem;
  padding: .2rem .5rem;
  border: 1px solid var(--lb-navy-700);
  border-radius: 999px;
}

/* Full-width hero video — shown above the product name/details when the
   product has one. Starts as a poster image (the product's featured/
   thumbnail image) with a play button; clicking swaps it for a real,
   playing embed right in place (no popup). */
.lb-hero-video {
  border-radius: var(--lb-radius-lg);
  overflow: hidden;
  border: 1px solid var(--lb-navy-800);
  aspect-ratio: 16 / 9;
  position: relative;
}
.lb-hero-video-poster {
  display: block; width: 100%; height: 100%;
  padding: 0; border: none; background: none; cursor: pointer;
  position: relative;
}
.lb-hero-video-poster img { width: 100%; height: 100%; object-fit: cover; display: block; }
.lb-hero-video iframe { width: 100%; height: 100%; border: 0; display: block; }
.lb-hero-video .lb-play-overlay .bi { transition: transform .15s ease; }
.lb-hero-video-poster:hover .lb-play-overlay .bi { transform: scale(1.08); }

/* ---- Ambient mode --------------------------------------------------------
   Whenever the person presses play, ONE extra always-muted copy of the
   very same video plays right behind the real one — oversized, heavily
   blurred, and masked with a rectangular vignette (not a round one) so it
   fades out uniformly with distance from the frame on every side,
   corners included, in the same rectangular shape as the video itself
   rather than an ellipse. It's one continuous ring around the frame, not
   separate per-edge strips, so there's nothing to leave a gap at the
   diagonals. Earlier this used up to eight separate cropped copies, one
   per edge/corner, for a slightly more directional glow — but eight
   extra embedded YouTube players is eight extra sets of network requests
   and eight more players for the browser to decode/paint at once, which
   visibly hurt performance for a purely decorative effect. One oversized,
   blurred copy reads as basically the same soft halo at a fraction of
   the cost. This is what YouTube's own iframe embed API doesn't expose
   (there's no "ambient mode" parameter, and the real player is
   cross-origin so its live pixels can't be sampled anyway), so the glow
   is really the same looping video, just scaled up, blurred and masked
   via CSS.

   Structure once playing:
     .lb-hero-video-ambient          (was .lb-hero-video; overflow now
                                       open so the glow can spill past it;
                                       z-index pinned so that bleed can
                                       never paint above the rest of the
                                       page — see the sidebar/below-hero
                                       rules further down)
       .lb-hero-glow-zones           (the single glow copy's outer
                                       wrapper: oversized, centered, masked
                                       with a top/bottom rectangular fade —
                                       sits behind the frame)
         .lb-hero-glow-inner         (masked with a left/right rectangular
                                       fade; nesting two independently-
                                       masked elements multiplies their
                                       alpha together, so combined with
                                       the parent's top/bottom fade this
                                       becomes one rectangular vignette —
                                       no mask-composite needed, and no
                                       browser support gap for it either)
       .lb-hero-video-frame          (the "TV" itself: opaque, rounded,
                                       bordered, clips its own contents —
                                       this is what keeps the real video
                                       looking like a normal player)
         .lb-hero-video-frame-bg     (portrait only: a blurred cover-fit
                                       copy filling the whole frame, behind
                                       the letterboxed player, so the
                                       gutters either side show blurred
                                       video instead of flat black — a
                                       different job than the outer glow
                                       above, which is why it's still its
                                       own second copy rather than reusing
                                       that one)
         .lb-hero-video-fg           (the real, audible player)

   - Landscape (16:9) videos: the real player fills the frame completely,
     like a TV's screen fills its bezel.
   - Portrait (9:16 / Shorts) videos: the real player keeps its own shape
     inside the frame, letterboxed on a blurred copy of itself either
     side — same as any TV showing a vertical video with its own ambient
     mode active.

   Slow connections: even this one extra embed is skipped entirely when
   navigator.connection reports a slow/metered link — see lbIsSlowNetwork
   in the script below. In that case only the single real, audible player
   loads, with no ambient bleed and no .lb-hero-video-ambient class. */
.lb-hero-video-ambient {
  overflow: visible;
  border: none;
  background: transparent;
  /* Pin this to an explicit stacking context of its own so the glow layer
     inside it (which spills past its own box) is always compared against
     the rest of the page by z-index rather than falling back to "any
     positioned element paints above any non-positioned one regardless of
     DOM order" — which is what previously let the bleed cover the sidebar
     and the sections below the hero video even though they come later in
     the markup. */
  position: relative;
  z-index: 1;
}
.lb-hero-glow-zones {
  position: absolute;
  /* Fixed pixel bleed, not a percentage — a consistent 50px rim around
     the actual video size regardless of how big the player itself
     renders, rather than a bleed that scales up into a much wider halo
     on a large screen. */
  inset: -50px;
  z-index: 0;
  overflow: hidden;
  /* A smaller, subtler corner radius than the frame's own — a big radius
     here reads as a soft round blob rather than a glow hugging a mostly
     square-cornered screen. */
  border-radius: 10px;
  pointer-events: none;
  /* Rectangular fade, matching the box's own (video-shaped) rectangle,
     rather than a round/elliptical falloff — opaque near the frame
     (irrelevant in practice, since the opaque frame sits on top and
     covers that area anyway), fading to transparent by the outer edge on
     each side. This is only the top/bottom half of the fade; combined
     with .lb-hero-glow-inner's left/right fade just below, since a masked
     child's already-masked result gets masked again by its parent — the
     two fades multiply together into one rectangular vignette, corners
     included, instead of needing a single radial shape. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 30%, #000 70%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 30%, #000 70%, transparent 100%);
}
.lb-hero-glow-inner {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: inherit;
  pointer-events: none;
  -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 30%, #000 70%, transparent 100%);
  mask-image: linear-gradient(to right, transparent 0%, #000 30%, #000 70%, transparent 100%);
}
.lb-hero-glow-inner iframe {
  position: absolute;
  top: 50%; left: 50%;
  /* Just enough oversize that the crop doesn't catch the source video's
     own hard edge before the blur/mask has faded it out — much less than
     before (150%), which was zooming in far past what's actually needed
     and made the glow's colors drift noticeably from what the real video
     was showing at that moment. */
  width: 112%;
  height: 112%;
  transform: translate(-50%, -50%);
  pointer-events: none;
  /* A bit more blur than the previous 20px rim — the box is now 50px
     past the frame on each side, so it can take more softening before
     the color washes flat, while still reading as a distinct band of
     light right behind the screen rather than a smeared cloud. */
  filter: blur(34px) saturate(2.2) brightness(1.18);
}

.lb-hero-video-frame {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  border-radius: var(--lb-radius-lg);
  overflow: hidden;
  /* box-sizing:border-box so, if a border is ever reintroduced, it eats
     into the frame's own box rather than growing it — the video children
     below are absolutely positioned with inset:0, which places them
     against the padding edge (i.e. inside the border), so they
     automatically shrink to sit inside a border instead of underneath
     it. */
  box-sizing: border-box;
  /* No border/backdrop-blur/glass frame here — the frame sits flush with
     the video underneath it, so the real player (and the portrait
     letterbox-fill layer) fills this box edge to edge with nothing eating
     into its width or height on any side. */
  border: none;
  /* No solid black fill here anymore — with the glow now hugging this
     close (50px), a flat black background behind the frame's own rounded
     corners created a hard black ring between the ambient glow and the
     actual video, breaking the seamless bleed. Landscape video and
     (when ambient mode is on) the portrait letterbox-fill layer both
     already cover the frame completely on their own; the only case that
     ever shows through now is a portrait video on a slow connection with
     ambient mode skipped entirely, where it falls back to the page's own
     dark background (--lb-navy-950 via html,body) instead of pure black —
     close enough in a dark theme to not read as a bug. */
  background: transparent;
}
.lb-hero-video-fg { position: absolute; inset: 0; z-index: 1; }
.lb-hero-video-fg iframe { width: 100%; height: 100%; border: 0; display: block; }

/* Portrait: letterboxed within the frame at its own aspect ratio instead
   of stretched to fill it, centered on the frame's own black background. */
.lb-hero-video-portrait .lb-hero-video-fg {
  left: 50%;
  right: auto;
  width: auto;
  aspect-ratio: 9 / 16;
  transform: translateX(-50%);
}

/* Portrait letterbox fill: a fifth always-muted copy of the same video,
   scaled to cover the *entire* frame and blurred, sitting behind the
   letterboxed player. Without this the strip either side of a 9:16 video
   was just the frame's own flat #000 background — solid black, not the
   video's own blurred light — since the outer top/bottom/left/right glow
   zones only bleed past the frame's outer edge and never reach inward to
   fill the letterbox gutters *inside* it. */
.lb-hero-video-frame-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}
.lb-hero-video-frame-bg iframe {
  position: absolute;
  top: 50%; left: 50%;
  width: 100%; height: 100%;
  min-width: 140%; min-height: 140%;
  transform: translate(-50%, -50%);
  pointer-events: none;
  filter: blur(45px) saturate(1.8) brightness(0.85);
}

/* ---- Keep the bleed behind the rest of the layout, always -------------
   The glow zones intentionally spill outside the hero video's own box (up
   to ~70% of its size past each edge), which means they can visually reach
   the right-hand sidebar and the sections below the hero video. On their
   own, .lb-hero-video-ambient's positioned children would actually paint
   *above* those later, non-positioned siblings regardless of DOM order —
   that's the CSS rule that any positioned element paints after (i.e. on
   top of) non-positioned ones in the same stacking context. Giving the
   sidebar and every section below the hero their own explicit z-index,
   higher than the ambient wrapper's z-index:1 above, pins them on top
   for good instead of leaving it to that default ordering. Scoped with
   :has() to this product page's hero layout specifically, so it doesn't
   touch col-lg-8/col-lg-4 usage elsewhere on the site.

   .lb-subnav-wrap is excluded from the `position: relative` here on
   purpose: it's a direct child of .col-lg-8, and this selector's
   specificity (two classes via :has() + .col-lg-8, plus one more via
   :not()) is higher than .lb-subnav-wrap's own single-class sticky rule
   elsewhere in this file. Left unexcluded, this rule would silently win
   the cascade on any product with a hero video and flip the sub-nav from
   `position: sticky` back to `relative` — it would then just sit offset
   by `top: var(--lb-topbar-h)` in normal flow instead of sticking, which
   is exactly the "sub-nav won't stick on hero-video products" bug. Its
   own z-index (1020, set alongside its sticky rule) is already far
   higher than the z-index: 2 given here, so once `position: sticky` is
   left alone it paints above the ambient glow on its own — no need to
   fold it into this override at all.

   .modal and .offcanvas are excluded for the same class of reason, just
   with a worse symptom: both the Rate-product modal (#lbRateModal) and
   the Flag/report modal (#lbFlagModal) are deliberately placed as direct
   children of this section (see the comment above #lbRateModal) rather
   than inside the sticky right rail. Bootstrap's own .modal rule relies
   on `position: fixed` + z-index:1055 to render as a centered overlay
   above its z-index:1050 backdrop — but this selector's specificity beats
   Bootstrap's single-class .modal rule, so without the :not(.modal)
   exclusion it was silently downgrading the modal to `position: relative;
   z-index: 2`, which both broke its centered-overlay positioning AND
   dropped it below its own backdrop (the exact "rating popup showing
   behind the backdrop" bug). Excluding .offcanvas too, pre-emptively, for
   the same reason — any offcanvas panel added as a direct child of a
   hero-video product section later would hit the identical trap.

   .lb-actionbar-fixed (the floating vote/rate/visit bar) is excluded for
   the identical reason as .lb-subnav-wrap above: it's also a direct child
   of this section, relies on `position: fixed` to float above the
   viewport, and this selector's specificity would otherwise silently
   downgrade it to `position: relative`, which was exactly why it wasn't
   appearing pinned to the bottom of the window on hero-video products. */
section.lb-hero-gap:has(> .lb-hero-video) > *:not(.lb-hero-video):not(.lb-subnav-wrap):not(.modal):not(.offcanvas):not(.lb-actionbar-fixed) {
  position: relative;
  z-index: 2;
}

/* ---- Mobile: full-bleed video, edge to edge, no side gaps ----------------
   Breaks the player out of every ancestor's padding/gutter (container +
   Bootstrap column) regardless of how many are nested, without touching
   the markup — the standard "full-bleed" trick for a centered layout.

   The glow zones don't get room to spill outside the box here — a
   phone-width screen has no spare margin either side of an edge-to-edge
   video for them to spread into — so the whole ambient wrapper clips them
   to the frame instead of letting them bleed past it. */
@media (max-width: 991.98px) {
  .lb-hero-video {
    width: 100vw;
    margin-left: calc(-50vw + 50%);
    margin-right: calc(-50vw + 50%);
    border-left: none;
    border-right: none;
    border-radius: 0;
  }
  .lb-hero-video-ambient { overflow: hidden; }
  .lb-hero-video-frame {
    border: none;
    border-radius: 0;
  }
}

.lb-gallery-desktop > div > .lb-media-panel,
.lb-gallery-desktop > div > .swiper.lb-gallery-mini,
.lb-gallery-mobile {
  border-radius: var(--lb-radius-lg); overflow: hidden; border: 1px solid var(--lb-navy-800); height: 100%;
}
.lb-media-slide img { width: 100%; height: 100%; object-fit: cover; aspect-ratio: 16/9; display: block; }
.lb-gallery-mini .swiper-slide { height: 100%; }
.lb-media-slide { position: relative; cursor: pointer; height: 100%; display: block; color: inherit; text-decoration: none; }

/* On mobile the combined slider sits in normal document flow (not a fixed-height
   grid cell like the desktop panels), so without an explicit bound its height
   was resolving from the slide contents in a way that let it balloon well past
   a sane viewport share. Give it a real aspect-ratio-based height instead. */
@media (max-width: 991.98px) {
  .lb-gallery-mobile {
    height: auto;
    aspect-ratio: 16 / 9;
    max-height: 60vh;
  }
  .lb-gallery-mobile .swiper-wrapper,
  .lb-gallery-mobile .swiper-slide,
  .lb-gallery-mobile .lb-media-slide {
    height: 100%;
  }
  .lb-gallery-mobile .lb-media-slide img {
    height: 100%;
    aspect-ratio: auto;
  }
}

.lb-play-overlay {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  /* No full-panel dark tint anymore — that used to wash out the whole
     thumbnail/video underneath just to make one play button readable.
     The button already carries its own solid lime background below, so
     it stays legible on any thumbnail without darkening the media. */
  background: transparent;
}
.lb-play-overlay .bi {
  width: 64px; height: 64px;
  display: flex; align-items: center; justify-content: center;
  background: var(--lb-lime);
  color: var(--lb-ink);
  border-radius: 50%;
  font-size: 1.8rem;
  padding-left: 4px;
}
.swiper-button-next, .swiper-button-prev { color: var(--lb-lime) !important; }
.swiper-pagination-bullet-active { background: var(--lb-lime) !important; }

.lb-comment-list { margin: .35rem 0; padding-left: 1.35rem; }
.lb-comment-list li { margin-bottom: .15rem; }
.lb-editor-toolbar { display: flex; gap: .25rem; flex-wrap: wrap; }
.lb-editor-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px;
  background: transparent;
  border: 1px solid var(--lb-navy-800);
  border-radius: var(--lb-radius-sm);
  color: var(--lb-mist);
  font-size: .85rem;
  cursor: pointer;
}
.lb-editor-btn:hover { border-color: var(--lb-lime); color: var(--lb-lime); }

/* ---- Modal backdrop: frosted glass instead of a flat dark overlay ------------- */
.modal-backdrop {
  background-color: rgba(11, 14, 31, 0.55);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.modal-backdrop.show { opacity: 1; }

/* GLightbox theme overrides — match the site's dark navy / lime accent
   instead of the library's default black/white chrome. */
.glightbox-clean .gclose,
.glightbox-clean .gnext,
.glightbox-clean .gprev {
  background: rgba(11,14,31,.7) !important;
  border-radius: 50%;
  /* GLightbox's own CSS ships gprev/gnext as a 40x50 (desktop) or 26x44
     (mobile) rectangle — border-radius:50% on a non-square box draws an
     oval/pill, not a circle. gclose is 35x35 (already square) but is
     pinned here too so all three buttons share one consistent circle
     size. Fixed equal width/height, plus flex centering (inherited from
     the library's own .gprev/.gnext/.gclose rule) keeps the arrow/close
     icon centered inside the now-circular button instead of off to one
     side. !important beats the library's own width/height at every one
     of its breakpoints (base, 769px, 992px) without needing to duplicate
     each of those media queries here. */
  width: 44px !important;
  height: 44px !important;
}
.glightbox-clean .gclose:hover,
.glightbox-clean .gnext:hover,
.glightbox-clean .gprev:hover {
  background: var(--lb-lime) !important;
}
.glightbox-clean .gclose:hover svg,
.glightbox-clean .gnext:hover svg,
.glightbox-clean .gprev:hover svg { fill: var(--lb-ink) !important; }
.gslide-image img { border-radius: var(--lb-radius-md); }
.goverlay { background: rgba(11,14,31,.92) !important; }

.lb-featured-swiper .swiper-slide {
  background: var(--lb-navy-900);
  border: 1px solid var(--lb-navy-800);
  border-radius: var(--lb-radius-md);
  padding: 1.1rem;
}

/* =========================================================================
   PRO AVATAR BADGE (avatar overlay, everywhere) — a small lime patch-check
   on the bottom-right of any avatar belonging to a Pro subscriber, with a
   hover/focus popover confirming it plus an optional link to the pricing
   page. Purely additive: drop `pro_badge_html()`'s output as a sibling of
   an existing avatar `<img>` inside a `.lb-avatar-relative` wrapper — no
   changes needed to the avatar's own size/shape/classes.
   Named .lb-pro-avatar-badge (not .lb-pro-badge) on purpose — that name was
   already taken by the small text "PRO" upsell pill used elsewhere
   (settings.php, dashboard.php, product/submit.php); reusing it here broke
   those unrelated badges by forcing them into this widget's absolute
   15×15px circle sizing.
   ========================================================================= */
.lb-avatar-relative { position: relative; display: inline-flex; line-height: 0; }
.lb-pro-avatar-badge {
  position: absolute;
  right: -3px;
  bottom: -3px;
  width: 19px;
  height: 19px;
  border-radius: 50%;
  /* Semi-black plate behind a gold crown — reads clearly on top of any
     avatar photo (dark or light) regardless of theme, so unlike most of
     this badge's neighbors these two colors are fixed rather than tied to
     the light/dark theme variables. */
  background: rgba(20, 20, 20, .82);
  color: #F5C518;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: .62rem;
  border: 2px solid var(--lb-navy-950);
  cursor: default;
}
.lb-pro-avatar-badge.lb-pro-avatar-badge-lg { width: 27px; height: 27px; font-size: .88rem; border-width: 3px; }
/* Crown is an <img> (bi-crown-fill doesn't exist in the bundled Bootstrap
   Icons version), sized as a share of the badge circle rather than via
   font-size so it scales the same way in both the sm and lg variants.
   Selector needs the extra `.lb-pro-avatar-badge` ancestor class (two
   classes = higher specificity than the generic `.lb-avatar-btn img` rule
   above, which otherwise wins and blows this up to a 30px avatar-styled
   square/circle instead of a small crown glyph) — without it the badge's
   own dark plate gets hidden behind the oversized, avatar-colored icon. */
.lb-pro-avatar-badge .lb-pro-avatar-badge-icon {
  width: 52%;
  height: 52%;
  border-radius: 0;
  background: transparent;
  object-fit: contain;
  display: block;
}
.lb-pro-avatar-badge-pop {
  display: none;
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  background: var(--lb-navy-800);
  border: 1px solid var(--lb-navy-700);
  border-radius: var(--lb-radius-sm);
  padding: .5rem .7rem;
  white-space: nowrap;
  box-shadow: 0 8px 20px rgba(0,0,0,.35);
  z-index: 40;
  font-size: .78rem;
  text-align: center;
}
.lb-pro-avatar-badge-pop strong { display: block; color: var(--lb-white); font-weight: 600; margin-bottom: .2rem; }
.lb-pro-avatar-badge-pop a { color: var(--lb-lime); font-weight: 600; }
.lb-pro-avatar-badge:hover .lb-pro-avatar-badge-pop,
.lb-pro-avatar-badge:focus .lb-pro-avatar-badge-pop,
.lb-pro-avatar-badge:focus-within .lb-pro-avatar-badge-pop { display: block; }

/* Standalone version of the same dark-plate + gold-crown badge, for places
   like the Pro plan card heading (subscribe.php) that want the Pro crown
   inline in normal flow rather than pinned to the corner of an avatar. */
.lb-pro-crown-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: rgba(20, 20, 20, .82);
  flex: none;
}
.lb-pro-crown-badge img { width: 52%; height: 52%; display: block; }

/* =========================================================================
   MAKER AVATAR STACK — compact "made by" avatar row used on every product
   listing card (homepage Featured/Today's/Promoted/Yesterday/Last Week/
   Last Month). Primary maker + any accepted co-makers (product_makers),
   each with its own Pro crown badge, overlapping like a typical
   "collaborators" stack. Beyond render_maker_stack()'s $max cap the
   remaining makers collapse into a single "+N" pill instead of piling on
   more avatars — it links straight to the product's own MAKERS section.
   ========================================================================= */
.lb-maker-stack {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
}
.lb-maker-stack-item {
  margin-left: -8px;
  border-radius: 50%;
  transition: transform .12s ease, z-index 0s;
}
.lb-maker-stack-item:first-child { margin-left: 0; }
.lb-maker-stack-item:hover,
.lb-maker-stack-item:focus-visible {
  transform: translateY(-2px);
  z-index: 5;
}
.lb-maker-stack-avatar {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--lb-navy-700);
  border: 2px solid var(--lb-navy-900);
  display: block;
}
[data-theme="light"] .lb-maker-stack-avatar { border-color: var(--lb-white); }
/* The stack's own Pro badge needs to be a touch smaller than the default
   19px "sm" size (pro_badge_html()'s default) to stay proportionate to
   these 22px mini avatars — same dark-plate/crown styling, just scaled. */
.lb-maker-stack-item .lb-pro-avatar-badge {
  width: 13px;
  height: 13px;
  right: -3px;
  bottom: -3px;
  border-width: 1.5px;
}
.lb-maker-stack-more {
  margin-left: -8px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--lb-navy-800);
  border: 2px solid var(--lb-navy-900);
  color: var(--lb-mist);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: .62rem;
  font-weight: 700;
  font-family: var(--lb-font-mono);
  text-decoration: none;
  flex-shrink: 0;
  transition: background .12s ease, color .12s ease;
}
[data-theme="light"] .lb-maker-stack-more { border-color: var(--lb-white); }
.lb-maker-stack-more:hover,
.lb-maker-stack-more:focus-visible {
  background: var(--lb-lime);
  color: var(--lb-navy-950);
}

/* =========================================================================
   PIN TO TOP (comments/reviews, product owner + Pro only) & pinned-product
   chip on profile — a subtle affordance, not a loud call to action.
   ========================================================================= */
.lb-pin-btn {
  border: none; background: transparent; color: var(--lb-mist);
  width: 24px; height: 24px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background .15s ease, color .15s ease;
}
.lb-pin-btn:hover { background: var(--lb-navy-800); color: var(--lb-lime); }
.lb-pin-btn i.bi-pin-angle-fill { color: var(--lb-lime); }
.lb-pinned-chip {
  display: inline-flex; align-items: center; gap: .3rem;
  font-size: .68rem; font-weight: 700; letter-spacing: .02em;
  color: var(--lb-lime); background: rgba(199,244,100,.12);
  padding: .1rem .5rem; border-radius: 1rem;
}
.lb-pinned-item {
  background: rgba(199,244,100,.05);
  border-radius: var(--lb-radius-sm);
  padding: .6rem .7rem;
  margin: -0.6rem -0.7rem 0;
}

/* =========================================================================
   AVAILABILITY CALENDAR (boost.php) — a real month grid instead of a bare
   native date input plus separate progress bars. Month-level availability
   (the only granularity the backend tracks) is folded straight into the
   grid: a fully-booked month's days simply render disabled/coral instead
   of needing a separate legend of progress bars above the picker.
   ========================================================================= */
.lb-calendar {
  border: 1px solid var(--lb-navy-800);
  border-radius: var(--lb-radius-md);
  padding: 1.4rem 1.75rem 1.25rem;
  width: 100%;
  background: var(--lb-navy-950);
}
.lb-calendar-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1.1rem; }
.lb-calendar-title { display: flex; flex-direction: column; align-items: center; gap: .35rem; }
.lb-calendar-title .mono { font-weight: 700; font-size: 1.1rem; }
.lb-calendar-badge {
  font-size: .72rem; font-weight: 600; letter-spacing: .01em;
  padding: .16rem .6rem; border-radius: 1rem;
  color: var(--lb-mist); background: var(--lb-navy-800);
}
.lb-calendar-badge.is-open { color: var(--lb-lime); background: rgba(199,244,100,.12); }
.lb-calendar-badge.is-full { color: var(--lb-coral); background: rgba(255,107,74,.12); }
.lb-calendar-nav {
  width: 34px; height: 34px; border-radius: 50%;
  border: 1px solid var(--lb-navy-700); background: transparent; color: var(--lb-white);
  display: inline-flex; align-items: center; justify-content: center;
  transition: background .15s ease, border-color .15s ease, opacity .15s ease;
}
.lb-calendar-nav:hover:not(:disabled) { background: var(--lb-navy-800); border-color: var(--lb-lime); }
.lb-calendar-nav:disabled { opacity: .3; cursor: not-allowed; }
.lb-calendar-weekdays {
  display: grid; grid-template-columns: repeat(7, 1fr);
  text-align: center; font-size: .78rem; color: var(--lb-mist);
  margin-bottom: .5rem;
}
.lb-calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: .5rem 0; justify-items: center; }
.lb-cal-day {
  width: 46px; height: 46px; border: none; background: transparent; color: var(--lb-white);
  border-radius: 50%; font-size: .92rem; font-family: var(--lb-font-mono);
  display: flex; align-items: center; justify-content: center;
  transition: background .15s ease, color .15s ease;
}
.lb-cal-day.lb-cal-pad { visibility: hidden; }
.lb-cal-day:not(:disabled):hover { background: var(--lb-navy-800); }
.lb-cal-day.is-today { box-shadow: inset 0 0 0 1.5px var(--lb-lime); }
.lb-cal-day.is-selected { background: var(--lb-lime); color: var(--lb-ink); font-weight: 700; box-shadow: none; }
.lb-cal-day.is-disabled { color: var(--lb-mist); opacity: .35; cursor: not-allowed; }
.lb-cal-day.is-full { color: var(--lb-coral); opacity: .55; cursor: not-allowed; text-decoration: line-through; }
.lb-calendar-footer { margin-top: 1.1rem; padding-top: .9rem; border-top: 1px solid var(--lb-navy-800); display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: .5rem; }
.lb-calendar-legend { display: flex; gap: 1.25rem; flex-wrap: wrap; font-size: .78rem; color: var(--lb-mist); margin-bottom: 0; }
.lb-calendar-legend span { display: inline-flex; align-items: center; gap: .4rem; }
.lb-legend-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
.lb-legend-dot.lb-legend-selected { background: var(--lb-lime); }
.lb-legend-dot.lb-legend-today { background: transparent; box-shadow: inset 0 0 0 1.5px var(--lb-lime); }
.lb-legend-dot.lb-legend-full { background: var(--lb-coral); opacity: .6; }
@media (max-width: 575.98px) {
  .lb-cal-day { width: 38px; height: 38px; font-size: .82rem; }
  .lb-calendar { padding: 1.1rem 1rem 1rem; }
}

/* =========================================================================
   PRICING PLAN CARDS (subscribe.php, boost.php) — minimal, flat, one accent.
   Cards are flex columns so the CTA always sits flush at the bottom no
   matter how many feature lines a plan has, and everything at rest reads
   as quiet — a single lime border/ribbon marks the recommended plan
   instead of stacking multiple loud colors.
   ========================================================================= */
.lb-plan {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--lb-navy-800);
  border-radius: var(--lb-radius-lg);
  padding: 1.6rem;
  background: var(--lb-navy-900);
  height: 100%;
  transition: border-color .15s ease, transform .15s ease;
}
.lb-plan.recommended { border-color: var(--lb-lime); position: relative; }
.lb-plan.recommended::after {
  content: "BEST VALUE"; position: absolute; top: -11px; left: 1.4rem;
  background: var(--lb-lime); color: var(--lb-ink);
  font-size: .65rem; font-weight: 700; padding: .18rem .55rem; border-radius: .4rem; letter-spacing: .04em;
}
.lb-plan-head { padding-bottom: 1.1rem; margin-bottom: 1.1rem; border-bottom: 1px solid var(--lb-navy-800); }
.lb-plan .price { font-family: var(--lb-font-mono); font-size: 2.1rem; font-weight: 700; letter-spacing: -.01em; }
.lb-plan-renew { opacity: .85; }
/* Feature list grows to fill remaining space so the CTA below always
   lands on the same baseline across a row of unequal-length cards. */
.lb-plan-body { flex: 1 1 auto; }
.lb-plan-features { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .55rem; }
.lb-plan-features li { display: flex; align-items: flex-start; gap: .5rem; color: var(--lb-mist); line-height: 1.35; min-width: 0; }
.lb-plan-features li .bi { color: var(--lb-lime); margin-top: .15rem; flex-shrink: 0; }
.lb-plan-cta { margin-top: 1.4rem; }

/* Wide, horizontal layout for single/featured-plan pages like
   subscribe.php — one full-width bar (name+price+features+CTA laid out
   side by side) reads far better than a narrow boxed card floating in
   the middle of a wide page. flex-wrap + min-width:0 on every section is
   a safety net: if the row ever runs out of horizontal space the columns
   drop to new lines instead of being force-shrunk into overlapping text. */
.lb-plan-wide {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: stretch;
  gap: 1.75rem 2rem;
  padding: 2rem 2.4rem;
}
.lb-plan-wide .lb-plan-head {
  flex: 1 1 220px;
  min-width: 200px;
  padding-bottom: 0;
  margin-bottom: 0;
  border-bottom: none;
  border-right: 1px solid var(--lb-navy-800);
  padding-right: 2rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.lb-plan-wide h3 { font-size: 1.15rem; }
.lb-plan-price-block {
  flex: 0 0 auto;
  min-width: 150px;
  text-align: center;
  padding: 0 2rem;
  border-right: 1px solid var(--lb-navy-800);
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.lb-plan-wide .price { font-size: 2.6rem; }
.lb-plan-wide .lb-plan-body { flex: 2 1 300px; min-width: 260px; display: flex; align-items: center; }
.lb-plan-wide .lb-plan-features {
  display: flex;
  flex-wrap: wrap;
  gap: .55rem 1.5rem;
  font-size: .92rem;
  width: 100%;
}
.lb-plan-wide .lb-plan-features li { flex: 1 1 calc(50% - .75rem); min-width: 180px; }
.lb-plan-wide .lb-plan-cta { flex: 0 0 220px; min-width: 200px; margin-top: 0; }
.lb-plan-wide.recommended::after { top: -11px; left: 2rem; }

@media (max-width: 991.98px) {
  .lb-plan-wide { align-items: stretch; text-align: left; }
  .lb-plan-wide .lb-plan-head,
  .lb-plan-price-block {
    border-right: none;
    border-bottom: 1px solid var(--lb-navy-800);
    padding: 0 0 1.2rem;
    text-align: left;
    flex: 1 1 100%;
    width: 100%;
  }
  .lb-plan-wide .lb-plan-body { flex: 1 1 100%; width: 100%; }
  .lb-plan-wide .lb-plan-features li { flex: 1 1 100%; min-width: 0; }
  .lb-plan-wide .lb-plan-cta { flex: 1 1 100%; width: 100%; }
}

/* "Payments unavailable" shouldn't look like a clickable primary button —
   dashed border + muted text reads as a status, not a broken CTA. */
.lb-plan-unavailable.btn:disabled {
  border-style: dashed;
  color: var(--lb-mist);
  opacity: 1;
  background: transparent;
}

.lb-comment { border-left: 2px solid var(--lb-navy-800); padding-left: .85rem; }
.lb-avatar-sm { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; background: var(--lb-navy-700); }

/* Product page "MAKERS" section — a responsive grid of cards instead of a
   single-column stacked list, so several makers on one product don't turn
   into a tall scroll of one row each. 2-up by default (mobile-friendly —
   this is the width most visitors hit it at), 3-up from sm, 4-up from lg. */
.lb-maker-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: .75rem;
}
@media (min-width: 576px) {
  .lb-maker-grid { grid-template-columns: repeat(3, 1fr); }
}
@media (min-width: 992px) {
  .lb-maker-grid { grid-template-columns: repeat(4, 1fr); }
}
.lb-maker-card {
  display: flex; align-items: center; gap: .6rem;
  padding: .65rem .8rem;
  border: 1px solid var(--lb-navy-600);
  border-radius: var(--lb-radius-md);
  background: var(--lb-navy-800);
  color: var(--lb-white);
  text-decoration: none;
  min-width: 0; /* lets the grid cell shrink below the name's natural width */
}
/* Full maker name, never clipped to an ellipsis — wraps onto a second
   line inside the card instead of cutting a long name short. */
.lb-maker-name { white-space: normal; word-break: break-word; line-height: 1.25; }
.lb-maker-card:hover { border-color: var(--lb-lime); color: var(--lb-white); }
/* Product page's own MAKERS grid only (.lb-maker-grid wraps just this
   section — the makers.php directory card reuses .lb-maker-card together
   with .lb-panel and keeps its filled panel background). Drop the fill so
   it reads the same borderless way the homepage product listing rows do,
   with the same subtle lime-tint hover instead of a filled card. */
.lb-maker-grid .lb-maker-card {
  background: transparent;
  transition: background .15s ease, border-color .15s ease;
}
.lb-maker-grid .lb-maker-card:hover { background: rgba(199,244,100,.045); }
[data-theme="dark"] .lb-maker-grid .lb-maker-card:hover { background: rgba(199,244,100,.06); }
.lb-maker-card .lb-badge-promoted { margin-left: auto; flex-shrink: 0; }

.lb-flag-btn {
  border: none; background: transparent; color: var(--lb-mist);
  font-size: .78rem; padding: 0 .2rem; line-height: 1;
  margin-left: auto;
}
.lb-flag-btn:hover { color: var(--lb-coral); }

/* Star ratings */
.lb-stars { color: var(--lb-lime); font-size: .8rem; letter-spacing: 1px; }
.lb-stars-sm { font-size: .75rem; }
.lb-stars-empty { color: var(--lb-navy-600); }
.lb-stars-lg { font-size: 1.6rem; letter-spacing: 4px; }
.lb-rate-widget .bi { cursor: pointer; transition: color .1s ease, transform .1s ease; }
.lb-rate-widget .bi:hover { transform: scale(1.15); }

.lb-dotline { height: 1px; background-image: linear-gradient(to right, var(--lb-navy-700) 40%, rgba(255,255,255,0) 0%); background-size: 8px 1px; background-repeat: repeat-x; }

::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: var(--lb-navy-950); }
::-webkit-scrollbar-thumb { background: var(--lb-navy-700); border-radius: 5px; }

@media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }

a:focus-visible, button:focus-visible, .form-control:focus-visible {
  outline: 2px solid var(--lb-lime); outline-offset: 2px;
}

/* ---- Sticky Featured + Promoted aside card (desktop only) ----------------
   Rendered by render_sticky_featured_promoted_card() in functions.php,
   which is always the FIRST thing echoed into every right-hand aside
   across the site. Every one of those asides is `<aside class="col-lg-4">`,
   so rather than reordering markup in eight separate templates, every
   aside is made a column flexbox here and this card is pushed to
   `order: 999` — i.e. it always renders LAST, visually, in every aside,
   everywhere, from this one rule. It still sticks just below the top bar
   once scrolled into view, and un-sticks once the aside runs out — plain
   CSS, no JS/scroll listeners needed. Hidden entirely below lg via the
   d-none d-lg-block classes on the element itself; the mobile fixed
   promoted carousel above the bottom nav takes over that role on small
   screens instead (see .lb-mobile-promo-fixed). */
aside.col-lg-4 {
  display: flex;
  flex-direction: column;
}
@media (min-width: 992px) {
  .lb-aside-sticky-promo {
    order: 999;
    position: sticky;
    top: calc(var(--lb-topbar-h, 56px) + var(--lb-space-4, 1rem));
    z-index: 15;
    max-height: calc(100vh - var(--lb-topbar-h, 56px) - var(--lb-space-6, 2rem));
    overflow-y: auto;
  }
}

/* ---- Mobile-only fixed "visit website" shortcut on the product page ------- */
.lb-mobile-fixed-scrim {
  position: fixed !important;
  left: 0; right: 0; bottom: 0;
  /* Generous flat height, not tied precisely to the buttons/promo card's
     real size — it only needs to comfortably cover behind them and fade
     out well before the content above, so exact precision doesn't matter
     the way it does for the buttons' own bottom offset. Covers the bottom
     nav's own height too since the gradient should start from the true
     bottom edge, underneath everything. */
  height: calc(var(--lb-bottomnav-h, 62px) + env(safe-area-inset-bottom) + 170px);
  background: linear-gradient(to top,
    var(--lb-navy-950) 0%,
    var(--lb-navy-950) 38%,
    rgba(var(--lb-navy-950-rgb), .85) 55%,
    rgba(var(--lb-navy-950-rgb), 0) 100%);
  pointer-events: none;
  z-index: 1018;
}
/* ---- Mobile-only fixed cluster on the product page: a single flex
   column at the bottom-left, laid out bottom-to-furthest from the bottom
   nav:
     1. lb-mobile-more-fixed   (44px — "Other links" bottom sheet)
     2. lb-mobile-visit-fixed  (52px — primary CTA)
     3. lb-mobile-rate-fixed   (52px)
     4. lb-mobile-vote-fixed   (52px)
   The wrapper (.lb-mobile-fixed-stack) is the one `position: fixed`
   element and owns the bottom/left offset + gap; the circles themselves
   are laid out by flexbox instead of each carrying its own hardcoded
   `bottom` value. That means a button that isn't rendered (e.g. no rate
   button for your own product) simply isn't in the flex flow, and the
   others close the gap automatically instead of leaving an empty hole
   where it used to be. DOM order is bottom-to-top (more, visit, rate,
   vote); flex-direction: column-reverse places that first child at the
   start of the (reversed) main axis, i.e. the bottom of the stack,
   matching the old visual order. ---- */
.lb-mobile-fixed-stack {
  position: fixed !important;
  left: 16px;
  bottom: calc(var(--lb-bottomnav-h, 62px) + env(safe-area-inset-bottom) + 14px);
  display: flex;
  flex-direction: column-reverse;
  align-items: center;
  gap: 10px;
  /* Below .lb-bottom-nav (1040), same as the rest of this cluster. */
  z-index: 1034;
}
.lb-mobile-more-fixed {
  width: 44px; height: 44px;
  border-radius: 50%;
  background: var(--lb-navy-900);
  border: 1px solid var(--lb-navy-700);
  color: var(--lb-white);
  display: flex; align-items: center; justify-content: center;
  font-size: 1.1rem;
  box-shadow: 0 6px 18px rgba(0,0,0,.25);
  flex-shrink: 0;
  transition: transform .28s cubic-bezier(.22,1,.36,1), opacity .28s ease;
}
.lb-mobile-more-fixed:active { opacity: .85; }

.lb-mobile-visit-fixed {
  width: 52px; height: 52px;
  border-radius: 50%;
  background: var(--lb-coral);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-size: 1.15rem;
  box-shadow: 0 6px 18px rgba(255,107,74,.4);
  flex-shrink: 0;
  transition: transform .28s cubic-bezier(.22,1,.36,1), opacity .28s ease;
}
.lb-mobile-visit-fixed:hover, .lb-mobile-visit-fixed:active { color: #fff; opacity: .92; }

.lb-mobile-rate-fixed {
  width: 52px; height: 52px;
  border-radius: 50%;
  background: var(--lb-navy-900);
  border: 1px solid var(--lb-navy-700);
  color: var(--lb-lime);
  display: flex; align-items: center; justify-content: center;
  font-size: 1.15rem;
  box-shadow: 0 6px 18px rgba(0,0,0,.3);
  flex-shrink: 0;
  transition: transform .28s cubic-bezier(.22,1,.36,1), opacity .28s ease;
}
.lb-mobile-rate-fixed:hover, .lb-mobile-rate-fixed:active { opacity: .9; }
.lb-mobile-rate-fixed:disabled { opacity: .45; cursor: not-allowed; }

.lb-mobile-vote-fixed {
  width: 52px; height: 52px;
  min-width: 0;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--lb-navy-700);
  background: var(--lb-navy-900);
  color: var(--lb-white);
  display: flex; align-items: center; justify-content: center;
  flex-direction: column; gap: 0;
  box-shadow: 0 6px 18px rgba(0,0,0,.3);
  flex-shrink: 0;
  transition: transform .28s cubic-bezier(.22,1,.36,1), opacity .28s ease, background .15s ease, border-color .15s ease;
}
.lb-mobile-vote-fixed .bi { font-size: 1rem; }
.lb-mobile-vote-fixed .count { font-size: .68rem; line-height: 1; margin-top: 1px; }
.lb-mobile-vote-fixed:active { opacity: .9; }
.lb-mobile-vote-fixed:disabled { opacity: .45; cursor: not-allowed; }

/* =========================================================================
   PRODUCT PAGE — desktop floating action bar (upvote / rate / visit)
   Replaces the old separate upvote/rating/visit panels in the right rail.
   .lb-actionbar-fixed is `position: fixed`, full viewport width — but its
   inner row (.lb-actionbar-fixed-row) reuses the SAME container class
   list (container-fluid lb-container px-3 lb-shell-bounds) and the same
   left-nav-width / gap / right-rail-width spacers that .lb-docked-wrap
   itself uses (see that block above), so ordinary CSS flex layout lines
   the actual bar up with the real main content column — no JS
   measurement, so nothing can fail to run and leave it not appearing.
   z-index is intentionally higher than .lb-subnav-wrap's 1020 so it
   always wins if the two ever overlap.
   ========================================================================= */
.lb-actionbar-fixed {
  position: fixed !important;
  left: 0;
  right: 0;
  bottom: 5px;
  z-index: 1021;
  pointer-events: none; /* re-enabled on the bar itself, below */
}
/* Applied by JS (initProductActionbar() in main.js) once the footer is
   getting close. Switches from `fixed` (pinned to the viewport) to
   `absolute` (positioned relative to the document instead) with an
   explicit `top` computed so the bar's bottom edge lands exactly 10px
   above the footer's top edge — matching the bar's normal 10px viewport
   offset above — so instead of vanishing or overlapping the footer, it
   simply stops following the viewport and is left behind, in place, as
   the user keeps scrolling past it. Removed again (see main.js) the
   moment the footer scrolls back out of range, resuming normal fixed
   behaviour. `!important` here for the same reason the base rule needs
   it: without it, a plain inline `style.position` couldn't win against
   that `position: fixed !important` above. */
.lb-actionbar-fixed.lb-actionbar-unstuck {
  position: absolute !important;
  bottom: auto;
}
.lb-actionbar-fixed-row {
  display: flex;
  align-items: stretch;
  gap: var(--lb-space-6);
  width: 100%;
}
/* Mirrors #lbOffcanvas.lb-offcanvas-docked's `flex: 0 0 260px` above —
   only actually true left-docked at xl+; see the override for the
   992-1199.98 tablet band right below, which mirrors that same band's
   left-nav/right-rail swap (.lb-offcanvas-docked / .lb-shell-right) so
   this floating bar keeps lining up with the real content column
   instead of leaving a phantom 260px gap once the left nav is no longer
   actually docked there. */
.lb-actionbar-fixed-spacer-left { flex: 0 0 260px; }
/* Mirrors .lb-shell-right's `flex: 0 0 320px`, present from lg+ (992px)
   now that the right rail itself shows starting at that breakpoint. */
.lb-actionbar-fixed-spacer-right { display: none; }
@media (min-width: 992px) {
  .lb-actionbar-fixed-spacer-right { display: block; flex: 0 0 320px; }
}
@media (min-width: 992px) and (max-width: 1199.98px) {
  body.lb-has-right-rail .lb-actionbar-fixed-spacer-left { flex: 0 0 0; width: 0; }
}

.lb-product-actionbar {
  display: flex;
  flex: 1 1 0%;
  min-width: 0;
  pointer-events: auto;
  align-items: center;
  gap: 1.1rem;
  padding: .9rem 1.35rem;
  border-radius: var(--lb-radius-lg);
  border: 1px solid var(--lb-navy-700);
  background: rgba(var(--lb-navy-900-rgb), .96);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  box-shadow: 0 12px 34px rgba(0,0,0,.4);
  transition: transform .28s cubic-bezier(.22,1,.36,1), opacity .28s ease;
}
.lb-actionbar-divider { width: 1px; align-self: stretch; background: var(--lb-navy-700); }

.lb-actionbar-vote {
  height: 46px;
  padding: 0 1.1rem;
  flex-shrink: 0;
}
.lb-actionbar-vote .bi { font-size: 1.05rem; }
.lb-actionbar-vote-text { font-weight: 600; font-size: .88rem; white-space: nowrap; }
.lb-actionbar-vote-text .count { font-family: var(--lb-font-mono); font-weight: 700; }

.lb-actionbar-rate {
  display: flex;
  align-items: center;
  gap: .65rem;
  flex: 1 1 auto;
  min-width: 0;
}
.lb-actionbar-rate-stars { display: flex; align-items: center; flex-shrink: 0; }
.lb-actionbar-rate-label {
  border: none;
  background: transparent;
  padding: 0;
  font-weight: 600;
  font-size: .85rem;
  white-space: nowrap;
  text-decoration: none;
}
button.lb-actionbar-rate-label { cursor: pointer; }

.lb-actionbar-visit {
  flex-shrink: 0;
  padding: .65rem 1.4rem;
  white-space: nowrap;
}

/* ---- Hide/show behaviour ------------------------------------------------
   Desktop action bar (.lb-product-actionbar): NEVER hidden. It stays
   visible at all times — while scrolling, and even once the footer is
   in view. What changes near the footer is its `position` (fixed -> the
   frozen-in-place kind), handled entirely via inline styles set in
   initProductActionbar() in main.js — see the comment there. No opacity/
   transform hide classes apply to it at all.

   Mobile circles (visit/rate/vote): hidden ONLY while actively scrolling,
   in either direction, and shown again a short beat after scrolling
   stops — including once the footer has scrolled into view; they don't
   get any special "near footer" treatment, they just keep floating. */
.lb-mobile-visit-fixed.lb-scroll-hidden,
.lb-mobile-rate-fixed.lb-scroll-hidden,
.lb-mobile-vote-fixed.lb-scroll-hidden { transform: scale(.35); opacity: 0; pointer-events: none; }

.lb-maker-result-item:hover { background: var(--lb-navy-800); border-radius: 8px; }

/* Profile page tab toggle ("Products launched" vs "From people you follow"
   vs "Trophy case") — same free-scrolling Swiper row + edge chevrons as
   the product page's quick-jump sub-nav (.lb-subnav-wrap / .lb-product-subnav
   in product/view.php), just not sticky: .lb-profile-tabs-wrap is the outer
   positioning context for the prev/next arrows (mirrors .lb-cat-inner's
   role there), .lb-profile-tabs is the actual `.swiper` element. */
.lb-profile-tabs-wrap { position: relative; }
.lb-profile-tabs {
  border-bottom: 1px solid var(--lb-navy-800);
  display: block;
  width: 100%;
  overflow: hidden;
}
/* Extra side padding while the prev/next arrows are actually showing —
   same reasoning/values as .lb-product-subnav.lb-subnav-has-arrows. */
.lb-profile-tabs.lb-subnav-has-arrows { padding-left: 2rem; padding-right: 2rem; }
.lb-profile-tabs .swiper-wrapper {
  align-items: center;
  /* No CSS `gap` here on purpose — spaceBetween (set in JS) is the single
     source of truth for slide spacing, same reasoning as the product
     sub-nav's swiper (see the comment on .lb-product-subnav .swiper-wrapper). */
}
.lb-profile-tabs .swiper-slide { flex-shrink: 0; width: auto; }
.lb-profile-tabs .nav-link { color: var(--lb-mist); border: none; border-bottom: 2px solid transparent; border-radius: 0; padding: .5rem .6rem; font-weight: 600; font-size: .92rem; white-space: nowrap; }
.lb-profile-tabs .nav-link:hover { color: var(--lb-white); }
.lb-profile-tabs .nav-link.active { color: var(--lb-lime); border-bottom-color: var(--lb-lime); background: transparent; }
.lb-profile-tabs-wrap .lb-subnav-arrow-prev { left: -4px; }
.lb-profile-tabs-wrap .lb-subnav-arrow-next { right: -4px; }

/* Raises the visit/more/scroll-top fixed buttons above the mobile promoted
   carousel (below) when it's present on the page, so nothing overlaps.
   Scoped to below-lg only so it never fights the desktop bottom offsets
   (e.g. .lb-scroll-top's 24px position at lg+, where the promo bar never
   shows in the first place). */
@media (max-width: 991.98px) {
  /* Only the wrapper needs raising now — the circles inside it are
     flex-laid-out, so raising the stack as a whole keeps every button's
     spacing correct with or without the promo carousel. .lb-scroll-top is
     a standalone button (not part of that flex stack) so it needs its own
     matching rule — without it, this selector only ever matched
     .lb-mobile-fixed-stack and the scroll-top button stayed at its normal
     offset, hidden behind the promo carousel's opaque background. */
  .lb-mobile-fixed-stack.lb-mobile-fixed-raised,
  .lb-scroll-top.lb-mobile-fixed-raised {
    bottom: calc(var(--lb-bottomnav-h, 62px) + env(safe-area-inset-bottom) + var(--lb-mobile-promo-h, 86px) + 14px) !important;
  }
}

/* Other Links bottom sheet — themed on top of Bootstrap's offcanvas-bottom,
   with a smoother/seamless easing curve than the default linear slide. */
.lb-other-links-sheet {
  /* Explicit, not just inherited from Bootstrap's un-overridden .offcanvas
     default (1045) — pinned above the bottom nav (1030) and the visit/
     more/promo fixed buttons (1033–1035) so the sheet always wins the
     stack, exactly like a native mobile bottom-sheet sitting above every
     other fixed chrome on the screen. */
  z-index: 1050 !important;
  /* Belt-and-suspenders reinforcement of Bootstrap's own .offcanvas-bottom
     defaults (position:fixed, bottom:0, full width) — forced explicitly so
     nothing else on the page can ever cause this to render at anything
     other than a true full-width, viewport-bottom-anchored sheet. */
  position: fixed !important;
  left: 0 !important;
  right: 0 !important;
  bottom: 0 !important;
  width: 100% !important;
  background: var(--lb-navy-950);
  color: var(--lb-white);
  border-top: 1px solid var(--lb-navy-800);
  border-radius: 16px 16px 0 0;
  max-height: 92vh;
  min-height: 30vh;
  /* Native-app default resting height; JS drag on .lb-sheet-drag-handle
     overrides this with an inline height between min-height and max-height. */
  height: 70vh;
  transition: transform .32s cubic-bezier(.22, 1, .36, 1), visibility .32s cubic-bezier(.22, 1, .36, 1), height .28s cubic-bezier(.22, 1, .36, 1);
}
/* While the user is actively dragging the handle, kill the height
   transition so the sheet tracks the finger/pointer 1:1 with zero lag —
   same as a native iOS/Android bottom sheet. */
.lb-other-links-sheet.lb-sheet-dragging { transition: transform .32s cubic-bezier(.22, 1, .36, 1), visibility .32s cubic-bezier(.22, 1, .36, 1); }

/* Drag handle — the little pill grabber at the top of the sheet, same
   affordance as native bottom sheets. Dragging it up/down resizes the
   sheet between min-height and max-height; a flick decides whether it
   snaps back to the default height, expands to full, or dismisses. */
.lb-sheet-drag-handle {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 10px 0 6px;
  touch-action: none;
  cursor: grab;
  flex-shrink: 0;
}
.lb-sheet-drag-handle span {
  display: block;
  width: 40px;
  height: 4px;
  border-radius: 999px;
  background: var(--lb-navy-700);
}
.lb-sheet-drag-handle:active { cursor: grabbing; }
.lb-sheet-drag-handle:active span { background: var(--lb-mist); }
.lb-other-links-sheet .offcanvas-header { border-bottom: 1px solid var(--lb-navy-800); }
.lb-other-links-sheet ~ .offcanvas-backdrop,
.offcanvas-backdrop {
  transition: opacity .32s ease;
  /* Same reinforcement as above — the backdrop must cover the entire
     viewport behind the sheet, full-bleed, regardless of where in the DOM
     it happens to sit or what else is on the page. */
  position: fixed !important;
  inset: 0 !important;
  width: 100% !important;
  height: 100% !important;
  /* Explicit, not Bootstrap's un-overridden default (1040) — that value
     now ties with .lb-bottom-nav (also 1040, see above), which would make
     their paint order coin-flip-dependent on source order instead of
     guaranteed. Pinned clearly between the bottom nav (1040) and the
     sheet itself (1050) so opening the sheet always dims the bottom nav
     too, exactly like a real modal covering every other fixed element on
     the screen. */
  z-index: 1045 !important;
}

/* ---- Mobile fixed "Promoted products" carousel (above the bottom nav) ---
   Replaces the sticky Featured/Promoted aside card on phones. Shows 2
   products side by side per slide, randomized (which products + their
   order/pairing) on every page load/refresh; JS (main.js) rotates to the
   next randomized pair every N seconds. */
.lb-mobile-promo-fixed {
  --lb-mobile-promo-h: 80px;
  position: fixed !important;
  left: 8px; right: 8px;
  bottom: calc(var(--lb-bottomnav-h, 62px) + env(safe-area-inset-bottom) + 6px);
  background: var(--lb-navy-900);
  border: 1px solid var(--lb-navy-700);
  border-radius: var(--lb-radius-md);
  padding: 8px 10px;
  /* Box-shadow's blur removed — a flat, crisp separation line (border
     above) instead of a soft blurred drop-shadow floating over content. */
  box-shadow: none;
  /* Below .lb-bottom-nav (1040) — same as the visit/more buttons. */
  z-index: 1032;
  display: block;
  color: var(--lb-white);
}
.lb-mobile-promo-head { display: flex; align-items: center; justify-content: space-between; font-size: .52rem; line-height: 1; text-transform: uppercase; letter-spacing: .03em; color: var(--lb-mist); margin-bottom: 3px; }
.lb-mobile-promo-dots { display: inline-flex; gap: 3px; }
.lb-mobile-promo-dots span { width: 4px; height: 4px; border-radius: 50%; background: var(--lb-navy-700); display: inline-block; transition: background .2s ease; }
.lb-mobile-promo-dots span.active { background: var(--lb-lime); }
/* Two-column grid: one card per promoted product, shown together per slide. */
.lb-mobile-promo-body { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; min-width: 0; }
.lb-mobile-promo-body.lb-promo-fade { animation: lbPromoSlideIn .32s cubic-bezier(.22,1,.36,1); }
.lb-mobile-promo-card {
  display: flex; align-items: center; gap: 6px; min-width: 0;
  text-decoration: none; color: var(--lb-white);
  padding: 4px 6px; border-radius: 8px;
  border: 1px solid var(--lb-navy-700);
  transition: background-color .15s ease, transform .15s ease, border-color .15s ease;
}
.lb-mobile-promo-card:hover, .lb-mobile-promo-card:active { color: var(--lb-white); background: rgba(255,255,255,.05); border-color: var(--lb-lime); transform: scale(.98); }
.lb-mobile-promo-card img { width: 28px; height: 28px; border-radius: 7px; object-fit: cover; flex-shrink: 0; }
.lb-mobile-promo-card .lb-mobile-promo-name { font-size: .72rem; font-weight: 600; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lb-mobile-promo-card .lb-mobile-promo-tagline { font-size: .62rem; color: var(--lb-mist); display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@keyframes lbPromoSlideIn { from { opacity: 0; transform: translateX(10px); } to { opacity: 1; transform: translateX(0); } }
@media (min-width: 992px) { .lb-mobile-promo-fixed { display: none !important; } }

/* ---- Global scroll-to-top button --------------------------------------- */
.lb-scroll-top {
  position: fixed;
  right: 16px;
  bottom: calc(var(--lb-bottomnav-h, 62px) + env(safe-area-inset-bottom) + 14px);
  width: 44px; height: 44px;
  border-radius: 50%;
  background: var(--lb-navy-900);
  border: 1px solid var(--lb-navy-700);
  color: var(--lb-white);
  display: flex; align-items: center; justify-content: center;
  font-size: 1.1rem;
  box-shadow: 0 6px 18px rgba(0,0,0,.25);
  z-index: 1035;
  opacity: 0;
  visibility: hidden;
  transform: translateY(8px);
  transition: opacity .2s ease, transform .2s ease, visibility .2s;
}
.lb-scroll-top.show { opacity: 1; visibility: visible; transform: translateY(0); }
.lb-scroll-top:hover { color: var(--lb-lime); border-color: var(--lb-lime); }
@media (min-width: 992px) {
  .lb-scroll-top { bottom: 24px; }
}

/* ---- Cookie consent notice (bottom-right) ------------------------------ */
.lb-cookie-notice {
  position: fixed;
  right: 16px;
  left: 16px;
  bottom: calc(var(--lb-bottomnav-h, 62px) + env(safe-area-inset-bottom) + 14px);
  z-index: 1090;
  max-width: 360px;
  margin-left: auto;
  background: var(--lb-navy-900);
  border: 1px solid var(--lb-navy-700);
  border-radius: 14px;
  padding: 16px;
  box-shadow: 0 10px 30px rgba(0,0,0,.35);
  opacity: 0;
  visibility: hidden;
  transform: translateY(12px);
  transition: opacity .25s ease, transform .25s ease, visibility .25s;
}
.lb-cookie-notice.show { opacity: 1; visibility: visible; transform: translateY(0); }
.lb-cookie-notice-body { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 12px; }
.lb-cookie-notice-icon { font-size: 1.1rem; margin-top: 2px; flex-shrink: 0; }
.lb-cookie-notice-actions { display: flex; gap: 8px; }
@media (min-width: 576px) {
  .lb-cookie-notice { left: auto; width: 360px; }
}
@media (min-width: 992px) {
  .lb-cookie-notice { bottom: 24px; }
}

/* ---- Light/dark mode toggle (top bar, right side) -------------------- */
.lb-theme-toggle .lb-theme-icon-light { display: none; }
[data-theme="dark"] .lb-theme-toggle .lb-theme-icon-dark { display: none; }
[data-theme="dark"] .lb-theme-toggle .lb-theme-icon-light { display: inline; }

/* =========================================================================
   RICH-TEXT EDITOR (Quill) — admin blog/page editors, see
   admin/blog.php, admin/pages.php, assets/js/main.js's initRichTextEditor().
   ========================================================================= */
.lb-quill-editor-wrap { background: var(--lb-navy-950); border: 1px solid var(--lb-navy-800); border-radius: 10px; overflow: hidden; }
.lb-quill-editor-wrap .ql-toolbar.ql-snow {
  background: var(--lb-navy-900);
  border: none;
  border-bottom: 1px solid var(--lb-navy-800);
  /* Sticks to the top of its own scroll container once you scroll a long
     post/page past it, instead of scrolling out of view entirely — but
     stops at the site header's height so it never hides underneath it. */
  position: sticky;
  top: var(--lb-topbar-h, 60px);
  z-index: 1020;
}
.lb-quill-editor-wrap .ql-container.ql-snow { border: none; }
.lb-quill-editor-wrap .ql-editor {
  min-height: 260px;
  max-height: 60vh;
  overflow-y: auto;
  color: var(--lb-white);
  font-size: .95rem;
}
.lb-quill-editor-wrap .ql-editor.ql-blank::before { color: var(--lb-mist); font-style: normal; }
.lb-quill-editor-wrap .ql-snow .ql-stroke { stroke: var(--lb-mist); }
.lb-quill-editor-wrap .ql-snow .ql-fill { fill: var(--lb-mist); }
.lb-quill-editor-wrap .ql-snow .ql-picker { color: var(--lb-mist); }
.lb-quill-editor-wrap .ql-toolbar.ql-snow button:hover .ql-stroke,
.lb-quill-editor-wrap .ql-toolbar.ql-snow button.ql-active .ql-stroke { stroke: var(--lb-lime); }
.lb-quill-editor-wrap .ql-toolbar.ql-snow button:hover .ql-fill,
.lb-quill-editor-wrap .ql-toolbar.ql-snow button.ql-active .ql-fill { fill: var(--lb-lime); }
.lb-quill-editor-wrap .ql-snow .ql-picker-options { background: var(--lb-navy-900); border-color: var(--lb-navy-800) !important; }

/* Rendered output of the editor above — public blog posts + static pages. */
.lb-rich-content { color: var(--lb-mist); line-height: 1.7; }
.lb-rich-content h1, .lb-rich-content h2, .lb-rich-content h3 { color: var(--lb-white); margin: 1.4em 0 .6em; }
.lb-rich-content p { margin-bottom: 1em; }
.lb-rich-content img { max-width: 100%; height: auto; border-radius: 10px; margin: .5em 0; }
.lb-rich-content a { color: var(--lb-lime); }
.lb-rich-content blockquote { border-left: 3px solid var(--lb-lime); padding-left: 1em; color: var(--lb-white); margin: 1em 0; }
.lb-rich-content ul, .lb-rich-content ol { padding-left: 1.4em; margin-bottom: 1em; }

/* =========================================================================
   SUBSCRIPTION / PRO PERKS — subscribe.php, dashboard.php, settings.php
   ========================================================================= */
.lb-pro-badge {
  display: inline-flex; align-items: center; gap: .3rem;
  font-size: .68rem; font-weight: 700; letter-spacing: .04em;
  padding: .18rem .55rem; border-radius: 20px;
  color: var(--lb-ink); background: var(--lb-lime);
  text-transform: uppercase;
}
.lb-locked-field { position: relative; }
.lb-locked-field .form-control, .lb-locked-field .form-select { opacity: .55; }
.lb-upsell-note {
  font-size: .8rem; color: var(--lb-mist); background: rgba(199,244,100,.08);
  border: 1px dashed var(--lb-lime); border-radius: 8px; padding: .6rem .9rem; margin-top: .5rem;
}
.lb-analytics-table th, .lb-analytics-table td { white-space: nowrap; padding-top: .55rem; padding-bottom: .55rem; }
.lb-analytics-table th { font-size: .68rem; letter-spacing: .04em; border-bottom-color: var(--lb-navy-800) !important; }
.lb-analytics-table td { border-color: var(--lb-navy-800) !important; vertical-align: middle; }
.lb-analytics-table tbody tr { transition: background .15s ease; }
.lb-analytics-table tbody tr:hover { background: rgba(199,244,100,.05); }
.lb-analytics-table tbody tr:last-child td { border-bottom: none; }

/* =========================================================================
   ADMIN LOGO UPLOAD CARDS — categories, link platforms, tech stack
   (admin/categories.php, admin/platforms.php, admin/techstack.php).
   Small square dropzone reusing the same drag-and-drop pattern as the
   product logo/thumbnail uploader on product/submit.php, just compact
   enough to sit inline in a form row or modal.
   ========================================================================= */
.lb-logo-upload-row { display: flex; align-items: center; gap: .9rem; flex-wrap: wrap; }
.lb-logo-dropzone {
  position: relative;
  width: 84px; height: 84px;
  flex-shrink: 0;
  border: 2px dashed var(--lb-navy-700);
  border-radius: var(--lb-radius-md);
  background: var(--lb-navy-900);
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: border-color .15s ease, background .15s ease;
}
.lb-logo-dropzone:hover, .lb-logo-dropzone:focus-visible { border-color: var(--lb-lime); }
.lb-logo-dropzone.lb-dragover { border-color: var(--lb-lime); background: rgba(199,244,100,.06); }
.lb-logo-dropzone .lb-logo-empty { text-align: center; color: var(--lb-mist); font-size: 1.2rem; }
.lb-logo-dropzone .lb-logo-empty .lb-logo-empty-hint { display: block; font-size: .58rem; margin-top: .15rem; line-height: 1.2; }
.lb-logo-preview-card { position: relative; width: 100%; height: 100%; border-radius: calc(var(--lb-radius-md) - 2px); overflow: hidden; }
.lb-logo-preview-card img { width: 100%; height: 100%; object-fit: cover; display: block; background: var(--lb-navy-800); }
.lb-logo-remove-btn {
  position: absolute; top: 3px; right: 3px;
  width: 20px; height: 20px; border-radius: 50%;
  background: rgba(11,14,31,.85); color: #fff; border: none;
  display: flex; align-items: center; justify-content: center;
  font-size: .62rem; z-index: 2;
}
.lb-logo-remove-btn:hover { background: var(--lb-coral); }
.lb-logo-upload-hint { font-size: .78rem; color: var(--lb-mist); margin-bottom: 0; }

/* =========================================================================
   PRODUCT DETAILS — "Other links" and "Built with" as main-content cards
   (product/view.php) instead of plain sidebar lists.
   ========================================================================= */
.lb-linkcard-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: .75rem;
}
.lb-link-card {
  display: flex; align-items: center; gap: .7rem;
  padding: .85rem 1rem;
  border: 1px solid var(--lb-navy-800);
  border-radius: var(--lb-radius-md);
  background: var(--lb-navy-950, var(--lb-navy-900));
  color: var(--lb-white);
  text-decoration: none;
  transition: border-color .15s ease, background .15s ease, transform .15s ease;
}
.lb-link-card:hover { border-color: var(--lb-lime); background: rgba(199,244,100,.04); transform: translateY(-1px); color: var(--lb-white); }
.lb-link-card-icon {
  flex-shrink: 0;
  width: 36px; height: 36px;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--lb-radius-sm);
  background: rgba(199,244,100,.1);
  color: var(--lb-lime);
  font-size: 1rem;
  overflow: hidden;
}
.lb-link-card-icon img { width: 100%; height: 100%; object-fit: contain; }
.lb-link-card-label { flex-grow: 1; min-width: 0; font-weight: 600; font-size: .88rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lb-link-card-arrow { flex-shrink: 0; color: var(--lb-mist); font-size: .8rem; }
.lb-link-card:hover .lb-link-card-arrow { color: var(--lb-lime); }

/* Inside the mobile "Other links" bottom sheet specifically, the links
   render as a proper mobile-first list — full-width rows, large tap
   targets, hairline dividers between rows — instead of the boxed
   multi-column card grid above (built for the wide desktop main column,
   not a phone-width sheet). */
.lb-other-links-sheet .lb-linkcard-grid {
  display: flex;
  flex-direction: column;
  gap: 0;
}
.lb-other-links-sheet .lb-link-card {
  border: 0;
  border-radius: 0;
  border-bottom: 1px solid var(--lb-navy-800);
  background: transparent;
  padding: .95rem .25rem;
  min-height: 52px;
}
.lb-other-links-sheet .lb-link-card:last-child { border-bottom: 0; }
.lb-other-links-sheet .lb-link-card:hover,
.lb-other-links-sheet .lb-link-card:active { background: var(--lb-navy-900); transform: none; }
.lb-other-links-sheet .lb-link-card-label { font-size: .95rem; }

.lb-techstack-group { margin-bottom: 1rem; }
.lb-techstack-group:last-child { margin-bottom: 0; }
.lb-techstack-group-label {
  font-size: .7rem; text-transform: uppercase; letter-spacing: .06em;
  color: var(--lb-mist); margin-bottom: .6rem; font-weight: 600;
}
.lb-tech-card-grid {
  display: grid;
  /* Mobile-first: 2 columns at the smallest widths, stepping up to more as
     the viewport grows (base rule = phone, then min-width media queries
     widen it) — instead of an auto-fill/auto-fit column count that isn't
     tied to any specific breakpoint. Cards fill their column evenly (1fr)
     rather than being a fixed pixel width. */
  grid-template-columns: repeat(2, 1fr);
  gap: .4rem;
}
@media (min-width: 480px) {
  .lb-tech-card-grid { grid-template-columns: repeat(3, 1fr); }
}
@media (min-width: 768px) {
  .lb-tech-card-grid { grid-template-columns: repeat(4, 1fr); }
}
@media (min-width: 992px) {
  .lb-tech-card-grid { grid-template-columns: repeat(5, 1fr); }
}
.lb-tech-card {
  display: flex; align-items: center; gap: .4rem;
  padding: .4rem .55rem;
  border: 1px solid var(--lb-navy-600);
  border-radius: var(--lb-radius-md);
  background: var(--lb-navy-800);
  color: var(--lb-white);
  text-decoration: none;
  font-size: .75rem; font-weight: 600;
  min-width: 0;
  transition: border-color .15s ease, background .15s ease;
}
.lb-tech-card span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
a.lb-tech-card:hover { border-color: var(--lb-lime); background: rgba(199,244,100,.04); color: var(--lb-white); }
.lb-tech-card-icon {
  flex-shrink: 0; width: 20px; height: 20px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 6px; background: var(--lb-navy-800); color: var(--lb-lime);
  font-size: .7rem; overflow: hidden;
}
.lb-tech-card-icon img { width: 100%; height: 100%; object-fit: contain; }

/* PRICING section chip (product/view.php) — a single, larger badge-style
   row (not a grid like OTHER LINKS/BUILT WITH, since there's only ever
   one pricing model) showing the maker's chosen Free/Freemium/Free
   Trial/Paid/Open Source/Other, or their own custom text when "Other"
   was picked. See pricing_model_icon()/pricing_model_label() in
   includes/functions.php. */
.lb-pricing-chip {
  display: inline-flex; align-items: center; gap: .7rem;
  padding: .7rem 1.1rem;
  border: 1px solid var(--lb-navy-600);
  border-radius: var(--lb-radius-md);
  background: var(--lb-navy-800);
}
.lb-pricing-chip-icon {
  flex-shrink: 0;
  width: 34px; height: 34px;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--lb-radius-sm);
  background: rgba(199,244,100,.1);
  color: var(--lb-lime);
  font-size: 1rem;
}
.lb-pricing-chip-label { font-weight: 700; font-size: .95rem; color: var(--lb-white); }

/* FAQ accordion — plain <details>/<summary> so content is present in the
   DOM (and to search engines / AI crawlers) whether or not it's expanded,
   no JS required. Light theme: solid white card (the shared .lb-panel
   navy-900 var resolves to an off-white #F5F6FA in light mode, which read
   as "not actually white" — pin it to true #FFFFFF here instead). Dark
   theme: overridden further down to match the surrounding dark panel
   again rather than staying white, so it doesn't sit as a bright white
   card against the rest of the dark UI. */
/* Uses the same --lb-navy-800/--lb-navy-600 tokens as .form-control, so it
   automatically matches whichever theme is active instead of a fixed
   light-mode white overridden back to navy for dark mode. */
.lb-faq-item {
  border: 1px solid var(--lb-navy-600);
  border-radius: var(--lb-radius-md);
  background: var(--lb-navy-950); /* true white in light mode, true dark in dark mode */
  padding: .85rem 1.1rem;
}
.lb-faq-item summary {
  cursor: pointer;
  list-style: none;
  color: var(--lb-white);
}
.lb-faq-item summary::-webkit-details-marker { display: none; }
.lb-faq-item summary::before {
  content: "\002B"; /* + */
  display: inline-block;
  width: 1.1em;
  color: var(--lb-lime);
  font-weight: 700;
  transition: transform .2s ease;
}
.lb-faq-item[open] summary::before { content: "\002B"; transform: rotate(45deg); } /* + rotated into an × reads as "collapse" */
/* Answer wrapper the JS accordion measures/animates (see main.js
   initFaqAccordions) — height is animated via the Web Animations API on
   the <details> element itself, this just gives the content a stable box
   to fade in as it grows. */
.lb-faq-content { background: var(--lb-navy-950); animation: lb-faq-fade-in .2s ease; }
.lb-faq-content p { color: var(--lb-mist) !important; }
@keyframes lb-faq-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .lb-faq-content { animation: none; }
  .lb-faq-item summary::before { transition: none; }
}



/* "Show more / Show less" toggle for a long BUILT WITH list — collapsed
   categories live in #lbBuiltWithMore (a Bootstrap .collapse); this button
   just relabels itself and flips its chevron on the collapse's own show/hide
   events (see main.js), matching the pattern used elsewhere for expandable
   panels. */
.lb-showmore-btn {
  display: inline-flex; align-items: center; gap: .4rem;
  margin-top: .25rem;
  padding: .4rem .8rem;
  border: 1px solid var(--lb-navy-600);
  border-radius: 999px;
  background: transparent;
  color: var(--lb-mist);
  font-size: .78rem; font-weight: 600;
  transition: border-color .15s ease, color .15s ease;
}
.lb-showmore-btn:hover, .lb-showmore-btn:focus-visible { border-color: var(--lb-lime); color: var(--lb-lime); }
.lb-showmore-icon { font-size: .7rem; transition: transform .2s ease; }
.lb-showmore-btn[aria-expanded="true"] .lb-showmore-icon { transform: rotate(180deg); }

/* =========================================================================
   PROFILE BANNER — cover photo behind the avatar on profile.php, with a
   matching wide upload dropzone on settings.php.
   ========================================================================= */
.lb-profile-banner {
  position: relative;
  height: 200px;
  border-radius: var(--lb-radius-lg);
  overflow: hidden;
  border: 1px solid var(--lb-navy-800);
  margin-top: -20px;
  margin-bottom: var(--lb-space-4, 1rem);
  /* Layered mesh gradient (two soft radial highlights over the diagonal
     base) instead of a single flat diagonal — gives makers without a
     custom cover image something with a bit of depth rather than a plain
     pastel bar. */
  background:
    radial-gradient(circle at 12% 20%, rgba(199,244,100,.35), transparent 45%),
    radial-gradient(circle at 85% 75%, rgba(255,107,74,.28), transparent 50%),
    linear-gradient(120deg, rgba(199,244,100,.16), rgba(88,166,255,.10) 55%, var(--lb-navy-900));
}
.lb-profile-banner img { width: 100%; height: 100%; object-fit: cover; display: block; }
.lb-profile-banner-overlay {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(11,14,31,0) 40%, rgba(11,14,31,.65) 100%);
}
/* "Edit cover" affordance — only rendered for the profile owner, shown on
   hover/focus so the banner doesn't otherwise look like a button. */
.lb-profile-banner-edit {
  position: absolute; top: .75rem; right: .75rem; z-index: 2;
  display: inline-flex; align-items: center; gap: .35rem;
  font-size: .75rem; font-weight: 600;
  padding: .35rem .75rem;
  border-radius: 999px;
  background: rgba(11,14,31,.55);
  color: #fff;
  border: 1px solid rgba(255,255,255,.25);
  text-decoration: none;
  opacity: 0;
  transition: opacity .15s ease, background .15s ease;
  backdrop-filter: blur(4px);
}
.lb-profile-banner:hover .lb-profile-banner-edit,
.lb-profile-banner-edit:focus-visible {
  opacity: 1;
}
.lb-profile-banner-edit:hover { background: rgba(11,14,31,.75); color: #fff; }
@media (hover: none) {
  /* No hover on touch devices — keep it faintly visible instead of hidden. */
  .lb-profile-banner-edit { opacity: .85; }
}
/* Inline "edit" affordances for the avatar + bio on your own profile — same
   hidden-until-hover/focus pattern as .lb-profile-banner-edit above, just
   sized/positioned for their smaller targets. Both deep-link straight to
   the matching field on settings.php (#lb-avatar-field / #lb-bio-field). */
.lb-profile-avatar-editable { position: relative; }
.lb-inline-edit-btn {
  display: inline-flex; align-items: center; justify-content: center;
  color: #fff;
  background: rgba(11,14,31,.65);
  border: 1px solid rgba(255,255,255,.25);
  text-decoration: none;
  opacity: 0;
  transition: opacity .15s ease, background .15s ease, transform .15s ease;
  backdrop-filter: blur(4px);
}
.lb-inline-edit-btn:hover { background: rgba(11,14,31,.85); color: #fff; transform: scale(1.06); }
/* Avatar variant: a small circular camera badge pinned to the bottom-right
   of the avatar ring. */
.lb-profile-avatar-editable .lb-inline-edit-btn {
  position: absolute; right: 0; bottom: 0; z-index: 2;
  width: 28px; height: 28px;
  border-radius: 50%;
  font-size: .75rem;
}
.lb-profile-avatar-editable:hover .lb-inline-edit-btn,
.lb-profile-avatar-editable .lb-inline-edit-btn:focus-visible {
  opacity: 1;
}
/* Bio variant: a small inline pencil pill sitting right after the bio text,
   revealed on hover/focus of the paragraph rather than the whole card. */
.lb-profile-bio { position: relative; }
.lb-inline-edit-btn-inline {
  width: 22px; height: 22px;
  border-radius: 50%;
  font-size: .65rem;
  margin-left: .4rem;
  vertical-align: middle;
}
.lb-profile-bio-editable:hover .lb-inline-edit-btn-inline,
.lb-inline-edit-btn-inline:focus-visible {
  opacity: 1;
}
@media (hover: none) {
  .lb-inline-edit-btn { opacity: .85; }
}
/* Pulls the avatar/name up to overlap the banner — scoped to just this row
   (inside the col-lg-8 main column) rather than the old negative margin on
   .lb-profile-banner itself, which used to shift the *whole* .row including
   the col-lg-4 aside up with it, making the banner cover the aside's top. */
.lb-profile-header-row {
  display: flex !important;
  flex-direction: row !important;
  flex-wrap: nowrap !important;
  align-items: flex-start !important;
  gap: 1rem;
  position: relative;
  z-index: 1;
  margin-top: -52px;
}
.lb-profile-header-row > .lb-avatar-relative { flex: 0 0 auto; }
.lb-profile-header-row > .lb-profile-header-info {
  flex: 1 1 0%;
  min-width: 0;
  /* Belt-and-suspenders for the mobile wrap trick below: with flex-basis
     0% this column already shrinks to fit next to the avatar rather than
     forcing the row to wrap, but a single unbreakable long word (a long
     username, an unspaced bio, etc.) could still blow out its min-content
     width and force the whole row (avatar included) onto its own line at
     narrow widths. Letting text break anywhere keeps the avatar and
     name/handle on the same line no matter how long that content is. */
  overflow-wrap: anywhere;
  /* The row is pulled up over the banner (see margin-top above) so the
     avatar overlaps it, but without this the name/handle text overlapped
     it too — the top few pixels of the name sat on the banner's dark
     gradient overlay while the rest sat on the page background. In dark
     mode that's still legible (name color is light either way), but in
     light mode the name color flips to dark navy, making it unreadable
     against that dark overlay strip. Nudging the text block down clears
     the banner entirely so it only ever sits on the plain page background. */
  margin-top: 24px;
}
@media (min-width: 992px) {
  /* Desktop only: the row's -52px pull-up (above) is tuned so the AVATAR
     overlaps the banner, but that same pull-up was also dragging the
     name/handle/bio block right up against (and even slightly under) the
     banner's bottom edge instead of clearing it — the base margin-top:24px
     above isn't enough at this breakpoint to compensate for the row's
     larger negative offset. Bumping just this block's own margin-top
     (independent of the avatar, which stays flush per align-items:
     flex-start) gives a clean, deliberate 20px of clearance between the
     banner and the username on desktop specifically; the mobile/tablet
     margin-top:24px above still applies below this breakpoint, where the
     row's pull-up is smaller (-40px, see the max-width:575.98px block). */
  .lb-profile-header-row > .lb-profile-header-info {
    margin-top: 56px;
  }
}
.lb-profile-avatar-ring {
  border: 4px solid var(--lb-bg, var(--lb-navy-950, #0B0E1F));
  border-radius: 50%;
  background: var(--lb-navy-700);
  flex-shrink: 0;
}
/* Compact Twitter/X-style row of website/social links under the bio —
   small icon + text per link, wrapping onto multiple lines on narrow
   screens instead of a boxed "LINKS" card. */
.lb-profile-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .35rem 1rem;
  margin-top: .5rem;
}
.lb-profile-links a {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  font-size: .85rem;
  color: var(--lb-lime);
  text-decoration: none;
  max-width: 100%;
}
.lb-profile-links a:hover { text-decoration: underline; }
.lb-profile-links a i { font-size: .85rem; flex-shrink: 0; }
.lb-profile-links a img { width: 14px; height: 14px; object-fit: contain; flex-shrink: 0; }
.lb-profile-links a span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 220px;
}
@media (max-width: 575.98px) {
  .lb-profile-banner { height: 130px; }
  .lb-profile-header-row { margin-top: -40px; }
  .lb-profile-header-row { flex-wrap: wrap !important; }
  /* Name/handle/bio column stays on the SAME line as the avatar (flex-basis:
     0% above already lets it shrink to fit there instead of forcing a full
     item-wrap), so it reads to the right of the avatar exactly like desktop
     — only the optional Follow/more-options actions block (when viewing
     someone else's profile) gets pushed to its own line below, via the
     classic flex-basis:100% "always start a new line" trick: with a full
     container-width hypothetical size it can never share a line with
     whatever came before it, regardless of how much room they actually
     used. */
  .lb-profile-header-row > .lb-profile-header-actions {
    flex-basis: 100%;
    margin-top: .75rem;
  }
  /* Smaller avatar on narrow screens frees up more room for the name/
     handle to stay comfortably on the same line, rather than only just
     barely fitting. */
  .lb-profile-header-row .lb-profile-avatar-ring {
    width: 64px !important;
    height: 64px !important;
  }
  .lb-profile-header-row .lb-profile-avatar-editable .lb-inline-edit-btn {
    width: 24px; height: 24px; font-size: .68rem;
  }
}

.lb-banner-dropzone {
  position: relative;
  width: 100%; aspect-ratio: 16 / 4.2; min-height: 120px;
  border: 2px dashed var(--lb-navy-700);
  border-radius: var(--lb-radius-lg);
  background: var(--lb-navy-900);
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: border-color .15s ease, background .15s ease;
  overflow: hidden;
}
.lb-banner-dropzone:hover, .lb-banner-dropzone:focus-visible { border-color: var(--lb-lime); }
.lb-banner-dropzone.lb-dragover { border-color: var(--lb-lime); background: rgba(199,244,100,.06); }
.lb-banner-preview-card { position: relative; width: 100%; height: 100%; }
.lb-banner-preview-card img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ---- Seamless, mobile-first micro-animations shared across dashboard/forms
   actions (product edit/submit, gallery add/remove, dashboard product cards,
   buttons). Small, fast, and respect reduced-motion. ---------------------- */
.lb-fade-in-up { animation: lbFadeInUp .28s cubic-bezier(.22,1,.36,1); }
@keyframes lbFadeInUp { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }

.btn, .lb-icon-btn, .lb-upload-remove, .lb-tag, .lb-bottom-nav-item, .lb-upload-dropzone, .lb-upload-mini-card, .lb-upload-card {
  transition: transform .15s ease, background-color .15s ease, border-color .15s ease, box-shadow .15s ease, opacity .15s ease;
}
.btn:active, .lb-fab:active, .lb-icon-btn:active { transform: scale(.96); }
.lb-upload-mini-card:hover { transform: translateY(-2px); }

@media (max-width: 991.98px) {
  /* Tap feedback on touch devices for anything actionable on the dashboard
     "Your products" cards and product forms. */
  .lb-dashboard-product-card, .lb-panel a.btn, .lb-panel button.btn { -webkit-tap-highlight-color: transparent; }
  .lb-dashboard-product-card:active { transform: scale(.985); }
}

@media (prefers-reduced-motion: reduce) {
  .lb-fade-in-up, .lb-promo-fade, .btn, .lb-icon-btn, .lb-upload-remove, .lb-upload-mini-card, .lb-upload-card, .lb-dashboard-product-card {
    animation: none !important;
    transition: none !important;
  }
}

/* =========================================================================
   BLOG LISTING — mobile-first horizontal cards (see blog.php's listing
   view), alternating which side the thumbnail sits on: default order in
   the markup is [body, thumbnail], so the 1st/3rd/5th... card (nth-child
   odd, the default) reads image-on-the-right, and :nth-child(even) below
   flips it to image-on-the-left with `row-reverse` — no PHP index needed,
   pure CSS. Sized mobile-first (small thumbnail, tight type) and scaled
   up at each breakpoint rather than starting desktop-sized and shrinking
   down.
   ========================================================================= */
.lb-blog-card {
  display: flex;
  align-items: stretch;
  gap: .85rem;
  padding: .85rem;
  background: var(--lb-navy-900);
  border: 1px solid var(--lb-navy-800);
  border-radius: var(--lb-radius-lg);
  transition: border-color .15s ease, transform .15s ease;
}
.lb-blog-card:hover, .lb-blog-card:focus-visible { border-color: var(--lb-lime); transform: translateY(-2px); }
.lb-blog-list .lb-blog-card:nth-child(even) { flex-direction: row-reverse; }

.lb-blog-card-body { flex: 1 1 0%; min-width: 0; display: flex; flex-direction: column; }
.lb-blog-card-byline {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .35rem;
  font-size: .74rem;
  color: var(--lb-mist);
  margin-bottom: .3rem;
}
.lb-blog-card-avatar { width: 20px; height: 20px; border-radius: 50%; object-fit: cover; flex-shrink: 0; }
.lb-blog-card-byline-sep { opacity: .7; }
.lb-blog-card-title {
  font-size: .95rem;
  font-weight: 700;
  line-height: 1.3;
  color: var(--lb-white);
  margin: 0 0 .3rem;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.lb-blog-card-excerpt {
  font-size: .8rem;
  line-height: 1.45;
  color: var(--lb-mist);
  margin: 0 0 .5rem;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.lb-blog-card-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .6rem;
  font-size: .74rem;
  color: var(--lb-mist);
  margin-top: auto;
  padding-top: .25rem;
}
.lb-blog-card-readmore { color: var(--lb-lime); font-weight: 600; white-space: nowrap; }

.lb-blog-card-thumb {
  position: relative;
  flex: 0 0 92px;
  width: 92px;
  /* Caps the thumbnail's own height instead of leaving it to the source
     image's natural aspect ratio — without this, a very tall image (e.g.
     a full-page screenshot used as a featured image) has no defined
     height to fall back on here, so the browser sizes this box to the
     image's intrinsic proportions, and align-items:stretch on
     .lb-blog-card then blows the WHOLE row up to match. object-fit:cover
     on the <img> below crops any source image, tall or wide, down to
     this fixed ratio instead. */
  aspect-ratio: 4 / 3;
  align-self: flex-start;
  border-radius: var(--lb-radius-md);
  overflow: hidden;
  background: var(--lb-navy-800);
}
.lb-blog-card-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.lb-blog-card-badge {
  position: absolute;
  top: .4rem;
  right: .4rem;
  display: flex;
  align-items: center;
  gap: .25rem;
  font-size: .6rem;
  font-weight: 700;
  color: #fff;
  background: rgba(11, 14, 31, .68);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  padding: .2rem .4rem;
  border-radius: 999px;
  white-space: nowrap;
}

@media (min-width: 480px) {
  .lb-blog-card-thumb { flex-basis: 120px; width: 120px; }
  .lb-blog-card-title { font-size: 1.02rem; }
}
@media (min-width: 768px) {
  .lb-blog-card { gap: 1.25rem; padding: 1.1rem; }
  .lb-blog-card-thumb { flex-basis: 200px; width: 200px; }
  .lb-blog-card-title { font-size: 1.2rem; }
  .lb-blog-card-excerpt { font-size: .85rem; }
  .lb-blog-card-byline { font-size: .8rem; }
}
@media (min-width: 992px) {
  .lb-blog-card-thumb { flex-basis: 240px; width: 240px; }
}

/* =========================================================================
   BLOG — single post ("digest detail") view.
   Reads like an actual blog post rather than a stack of product panels:
   a short hero banner (not a 420px wall), a proper title/meta/byline
   header, a lead paragraph, an optional stat strip for auto-generated
   digests, and a text-forward ranked list. Each ranked item deliberately
   drops the big per-product hero photo (the images at #1..#N were mostly
   duplicative — the logo already identifies the product) in favor of more
   of the product's own copy, which is both more skimmable and gives
   search/AI crawlers actual sentences to index instead of a picture.
   ========================================================================= */
.lb-post-back { align-items: center; gap: .35rem; }
.lb-post-back:hover { color: var(--lb-lime) !important; }

.lb-post-hero {
  border-radius: var(--lb-radius-lg);
  overflow: hidden;
  background: var(--lb-navy-800);
  max-height: 190px;
}
.lb-post-hero img { width: 100%; height: 100%; max-height: 190px; object-fit: cover; display: block; }
@media (min-width: 768px) {
  .lb-post-hero, .lb-post-hero img { max-height: 280px; }
}

.lb-post-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  row-gap: .5rem;
  column-gap: .9rem;
  font-size: .78rem;
  color: var(--lb-mist);
  margin-bottom: .7rem;
}
.lb-post-meta-item { display: inline-flex; align-items: center; gap: .3rem; }
.lb-post-title {
  font-size: 1.5rem;
  line-height: 1.25;
  margin: 0 0 .75rem;
}
@media (min-width: 768px) { .lb-post-title { font-size: 2rem; } }
.lb-post-byline { display: flex; align-items: center; gap: .5rem; font-size: .85rem; color: var(--lb-mist); }
.lb-post-byline-avatar { width: 26px; height: 26px; border-radius: 50%; object-fit: cover; flex-shrink: 0; }

.lb-post-lead { font-size: 1.02rem; line-height: 1.75; color: var(--lb-mist); }

.lb-post-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: .5rem;
  padding: 1rem;
  background: var(--lb-navy-900);
  border: 1px solid var(--lb-navy-800);
  border-radius: var(--lb-radius-lg);
}
.lb-post-stat { text-align: center; }
.lb-post-stat-num { display: block; font-family: var(--lb-font-mono); font-size: 1.25rem; font-weight: 700; color: var(--lb-lime); }
.lb-post-stat-label { display: block; font-size: .66rem; text-transform: uppercase; letter-spacing: .05em; color: var(--lb-mist); margin-top: .15rem; }

.lb-post-section-title { font-size: 1rem; font-weight: 700; margin: 0 0 .9rem; }

.lb-digest-list { display: flex; flex-direction: column; gap: .75rem; }
.lb-digest-item {
  display: flex;
  align-items: flex-start;
  gap: .75rem;
  padding: 1rem;
  background: var(--lb-navy-900);
  border: 1px solid var(--lb-navy-800);
  border-radius: var(--lb-radius-lg);
  color: inherit;
  text-decoration: none;
  transition: border-color .15s ease, transform .15s ease;
}
.lb-digest-item:hover, .lb-digest-item:focus-visible { border-color: var(--lb-lime); transform: translateY(-2px); color: inherit; }
.lb-digest-rank {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--lb-font-mono);
  font-size: .72rem;
  font-weight: 700;
  color: var(--lb-mist);
  background: var(--lb-navy-800);
  border-radius: 50%;
}
.lb-digest-rank.is-top { color: #0B0E1F; background: var(--lb-lime); }
.lb-digest-logo { width: 42px; height: 42px; border-radius: var(--lb-radius-md); object-fit: cover; background: var(--lb-navy-800); flex-shrink: 0; }
.lb-digest-body { flex: 1 1 0%; min-width: 0; }
.lb-digest-name { font-family: var(--lb-font-display); font-weight: 700; color: var(--lb-white); }
.lb-digest-tagline { font-size: .85rem; color: var(--lb-mist); margin: 0 0 .35rem; }
.lb-digest-desc { font-size: .84rem; line-height: 1.55; color: var(--lb-mist); opacity: .88; margin: 0 0 .6rem; }
.lb-digest-foot { display: flex; align-items: center; flex-wrap: wrap; row-gap: .25rem; column-gap: .85rem; font-size: .76rem; color: var(--lb-mist); }
.lb-digest-readmore { margin-left: auto; color: var(--lb-lime); font-weight: 600; white-space: nowrap; }

.lb-post-outro {
  padding: 1.25rem;
  background: var(--lb-navy-900);
  border: 1px solid var(--lb-navy-800);
  border-left: 3px solid var(--lb-lime);
  border-radius: var(--lb-radius-lg);
}
.lb-post-outro-body { color: var(--lb-mist); line-height: 1.7; }

@media (min-width: 768px) {
  .lb-digest-item { padding: 1.25rem; gap: 1rem; }
  .lb-digest-logo { width: 52px; height: 52px; }
  .lb-digest-rank { width: 32px; height: 32px; font-size: .78rem; }
}

/* =========================================================================
   PRODUCT DESCRIPTION — clamp to ~4 lines with a Show more/less toggle,
   plus the Visit <product> / More links action row directly beneath it
   (see product/view.php "About / Product description" + the toggle logic
   near the bottom of its $extraScripts block).
   ========================================================================= */
.lb-desc-text {
  color: var(--lb-mist);
  line-height: 1.6;
}
.lb-desc-clamped {
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Sits on its own line directly under the clamped paragraph — NOT inside it
   (a button inline at the end of a line-clamped paragraph is clipped away). */
.lb-desc-toggle-btn {
  display: inline-block;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font-weight: 600;
  font-size: .85rem;
  color: var(--lb-lime);
  cursor: pointer;
  white-space: nowrap;
}
.lb-desc-toggle-btn:hover,
.lb-desc-toggle-btn:focus-visible { text-decoration: underline; }
/* Row that holds the toggle (left) + the Visit/More links actions (right).
   Desktop/tablet: single row, toggle flush left, actions pushed flush
   right via margin-left:auto on .lb-desc-actions below — this keeps
   actions right-aligned even when the toggle is d-none (no overflow).
   Mobile: stacks into a column in DOM order, so actions land directly
   under the toggle. */
.lb-desc-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .6rem;
}
.lb-desc-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .6rem;
  margin-left: auto;
}
.lb-desc-visit-btn,
.lb-desc-more-btn { white-space: nowrap; }
@media (max-width: 767.98px) {
  .lb-desc-row {
    flex-direction: column;
    align-items: stretch;
  }
  .lb-desc-row .lb-desc-toggle-btn { align-self: flex-start; }
  .lb-desc-actions { margin-left: 0; }
}

/* =========================================================================
   ADMIN — image upload cards (Site branding & SEO, admin/site-settings.php)
   Reusable card for any single-image upload: preview + upload/replace +
   remove, with drag & drop and an instant local preview handled by the
   script at the bottom of that page. See render_upload_card() there for
   the exact markup this styles.
   ========================================================================= */
.lb-upload-card {
  border: 1px solid var(--lb-navy-800);
  border-radius: var(--lb-radius-md);
  overflow: hidden;
  background: var(--lb-navy-900);
  transition: border-color .15s ease, box-shadow .15s ease;
  height: 100%;
}
.lb-upload-card:hover { border-color: var(--lb-lime); }
.lb-upload-card.lb-upload-dragover {
  border-color: var(--lb-lime);
  box-shadow: 0 0 0 2px rgba(199,244,100,.25);
}
.lb-upload-preview {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 110px;
  padding: 1rem;
}
.lb-upload-preview img { max-width: 100%; max-height: 90px; object-fit: contain; }
.lb-upload-preview.is-empty {
  flex-direction: column;
  gap: .35rem;
  color: #9AA0AE;
  font-size: .72rem;
}
.lb-upload-preview.is-empty i { font-size: 1.6rem; opacity: .55; }
.lb-upload-preview-square { min-height: 84px; padding: .75rem; }
.lb-upload-preview-square img { max-height: 48px; max-width: 48px; }
.lb-upload-preview-banner { min-height: 130px; }
.lb-upload-preview-banner img { max-height: 110px; }
.lb-upload-body {
  padding: .8rem .9rem .9rem;
  border-top: 1px solid var(--lb-navy-800);
}
.lb-upload-title { font-weight: 600; font-size: .85rem; color: var(--lb-white); margin-bottom: .15rem; }
.lb-upload-hint { font-size: .74rem; line-height: 1.4; margin-bottom: .65rem; }
.lb-upload-actions { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.lb-upload-btn {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  padding: .35rem .7rem;
  border-radius: var(--lb-radius-sm);
  border: 1px solid var(--lb-lime);
  color: var(--lb-lime);
  font-size: .78rem;
  font-weight: 600;
  cursor: pointer;
  background: transparent;
  transition: background .15s ease, color .15s ease;
  margin-bottom: 0;
}
.lb-upload-btn:hover { background: var(--lb-lime); color: #0B0E1F; }
.lb-upload-btn input[type="file"] { display: none; }
.lb-upload-remove-btn {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .35rem .6rem;
  border-radius: var(--lb-radius-sm);
  border: 1px solid transparent;
  color: var(--lb-coral);
  font-size: .78rem;
  font-weight: 600;
  cursor: pointer;
  background: transparent;
  transition: background .15s ease, border-color .15s ease;
  margin-bottom: 0;
}
.lb-upload-remove-btn:hover { background: rgba(255,107,74,.1); border-color: var(--lb-coral); }
.lb-upload-remove-btn input[type="checkbox"] { display: none; }
.lb-upload-card.lb-upload-marked-remove .lb-upload-preview { opacity: .35; filter: grayscale(1); }
.lb-upload-card.lb-upload-marked-remove .lb-upload-remove-btn { background: rgba(255,107,74,.15); border-color: var(--lb-coral); }
.lb-upload-filename {
  display: flex;
  align-items: center;
  gap: .3rem;
  font-size: .72rem;
  color: var(--lb-mist);
  margin-top: .55rem;
  word-break: break-all;
}

/* =========================================================================
   NOTIFICATION DEEP-LINK HIGHLIGHT — briefly flashes whatever comment or
   rating a notification link points at (see #comment-N / #rating-N anchors
   in api/comment.php / api/rating.php's notify_user() calls, and the
   hash-handling in assets/js/main.js) so it's obvious which one the visitor
   was sent to look at, once the page has scrolled it into view.
   ========================================================================= */
@keyframes lb-notify-highlight {
  0%   { background-color: rgba(88, 166, 255, 0.28); box-shadow: 0 0 0 1px rgba(88, 166, 255, 0.35); }
  100% { background-color: transparent; box-shadow: 0 0 0 1px rgba(88, 166, 255, 0); }
}
.lb-notify-highlight {
  animation: lb-notify-highlight 2.6s ease-out;
  border-radius: var(--lb-radius-sm);
}
