
/* Palette matches portlincolntilesandpavers.com's own live branding -
   re-verified 2026-08 directly against their current site (their design
   moved on since the original 2026-07 pull, which had drifted: #f2e9e4 ->
   #faf7f2, #22223b -> #18323f). #18838e is their dominant accent
   (buttons/highlights, by far their most-used colour, confirmed by literal
   frequency count across their homepage), #faf7f2 their actual page
   background cream, #18323f their dark navy (by far the single most-used
   colour on their site - body text AND their dark section/header-bar
   background). Per-supplier badge colours used to live here as
   --abi-badge/--italia-badge - now panel-editable per Supplier (see
   .badge below), not part of this shared theme palette any more. */
:root {
  --bg: #faf7f2;
  --card-bg: #ffffff;
  --text: #18323f;
  --text-muted: #6b6b80;
  --border: #e8ddd0;
  --accent: #18838e;
  --accent-bg: #dcefef;
  --overlay: rgba(34, 34, 59, 0.75);
  /* Glassmorphism tokens - frosted teal-tinted panels over the blurred
     blob backdrop set on body below. --glass-bg/-strong feed
     backdrop-filter surfaces (nav/sidebar/cards/modal); --glass-border and
     --glass-shine give the panel edges/hover highlight that sell the glass
     look; --glass-shadow is the soft ambient shadow every glass panel
     shares so they read as one consistent material. */
  --glass-bg: rgba(255, 255, 255, 0.45);
  --glass-bg-strong: rgba(255, 255, 255, 0.65);
  --glass-border: rgba(255, 255, 255, 0.6);
  --glass-shine: rgba(255, 255, 255, 0.8);
  --glass-shadow: 0 8px 32px rgba(19, 100, 110, 0.16);
  --blob-1: rgba(43, 169, 179, 0.30);
  --blob-2: rgba(25, 131, 142, 0.22);
  --blob-3: rgba(216, 97, 60, 0.10);
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #16232b;
    --card-bg: #1f323d;
    --text: #f2e9e4;
    --text-muted: #b8ab9d;
    --border: #2c4048;
    --accent: #2ba9b3;
    --accent-bg: #1e3a3d;
    --overlay: rgba(0, 0, 0, 0.88);
    --glass-bg: rgba(31, 50, 61, 0.45);
    --glass-bg-strong: rgba(31, 50, 61, 0.68);
    --glass-border: rgba(255, 255, 255, 0.09);
    --glass-shine: rgba(255, 255, 255, 0.14);
    --glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
    --blob-1: rgba(43, 169, 179, 0.28);
    --blob-2: rgba(25, 131, 142, 0.24);
    --blob-3: rgba(216, 97, 60, 0.10);
  }
}
/* Explicit choice from the header toggle - set via data-theme, wins over
   the OS preference above regardless of source order (attribute selector
   on :root is more specific than plain :root). */
:root[data-theme="light"] {
  --bg: #faf7f2;
  --card-bg: #ffffff;
  --text: #18323f;
  --text-muted: #6b6b80;
  --border: #e8ddd0;
  --accent: #18838e;
  --accent-bg: #dcefef;
  --overlay: rgba(34, 34, 59, 0.75);
  --glass-bg: rgba(255, 255, 255, 0.45);
  --glass-bg-strong: rgba(255, 255, 255, 0.65);
  --glass-border: rgba(255, 255, 255, 0.6);
  --glass-shine: rgba(255, 255, 255, 0.8);
  --glass-shadow: 0 8px 32px rgba(19, 100, 110, 0.16);
  --blob-1: rgba(43, 169, 179, 0.30);
  --blob-2: rgba(25, 131, 142, 0.22);
  --blob-3: rgba(216, 97, 60, 0.10);
}
:root[data-theme="dark"] {
  --bg: #16232b;
  --card-bg: #1f323d;
  --text: #f2e9e4;
  --text-muted: #b8ab9d;
  --border: #2c4048;
  --accent: #2ba9b3;
  --accent-bg: #1e3a3d;
  --overlay: rgba(0, 0, 0, 0.88);
  --glass-bg: rgba(31, 50, 61, 0.45);
  --glass-bg-strong: rgba(31, 50, 61, 0.68);
  --glass-border: rgba(255, 255, 255, 0.09);
  --glass-shine: rgba(255, 255, 255, 0.14);
  --glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
  --blob-1: rgba(43, 169, 179, 0.28);
  --blob-2: rgba(25, 131, 142, 0.24);
  --blob-3: rgba(216, 97, 60, 0.10);
}
* { box-sizing: border-box; }
html { min-height: 100%; }
body {
  margin: 0;
  min-height: 100vh;
  background-color: var(--bg);
  /* Soft, fixed teal blobs behind every glass panel below - without these
     a backdrop-filter: blur() panel just looks like flat translucent grey,
     since there's nothing colourful behind it to blur. Fixed (not
     scroll-following) so panels keep catching light consistently as the
     page scrolls, the way the reference glass UI does. */
  background-image:
    radial-gradient(circle at 12% 18%, var(--blob-1), transparent 42%),
    radial-gradient(circle at 88% 8%, var(--blob-2), transparent 38%),
    radial-gradient(circle at 82% 78%, var(--blob-3), transparent 45%),
    radial-gradient(circle at 15% 88%, var(--blob-2), transparent 40%);
  background-attachment: fixed;
  background-repeat: no-repeat;
  color: var(--text);
  font-family: "Work Sans", -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
}
/* Matches portlincolntilesandpavers.com's own top announcement strip -
   #18323f is their dark navy (see the palette note above). Deliberately
   NOT theme-reactive (unlike the rest of this page) - it's a fixed brand
   colour block the same way it is on their own site, which has no dark
   mode of its own to react to. */
.brand-strip {
  background: #18323f;
  color: #faf7f2;
  text-align: center;
  font-size: 0.78rem;
  padding: 8px 16px;
  letter-spacing: 0.01em;
}
/* Mirrors portlincolntilesandpavers.com's own header bar structure
   exactly: a flush, full-width cream strip (no rounded corners/shadow/
   margin, unlike the floating glass panels used further down the page)
   sitting directly under the dark announcement strip, logo lockup on the
   left, nav links on the right - the strongest single visual cue that
   this is "part of the same site", not a separate tool. The page's own
   per-section <header> (with the bigger logo/page-title/admin controls)
   sits below this, playing the same "secondary bar" role
   portlincolntilesandpavers.com's own showroom/contact strip plays under
   their nav. Not theme-reactive for the same reason .brand-strip isn't -
   read against the fixed cream page background (var(--bg)), so it stays
   legible in both themes without its own dark-mode variant. */
.site-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px 24px;
  /* 18px top/bottom + the 60px logo below = 96px, matching the real
     site's own header height (measured at 97px, logo 60px tall) - see
     the .site-nav-logo img rule below. */
  padding: 18px 24px;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  font-family: "Work Sans", -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
}
.site-nav-logo {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--accent);
  text-decoration: none;
  font-weight: 600;
  font-size: 1rem;
  line-height: 1.2;
  white-space: nowrap;
  flex-shrink: 0;
}
/* Matches portlincolntilesandpavers.com's own logo height exactly (measured
   2026-08 via getBoundingClientRect on their live header). */
.site-nav-logo img { width: 60px; height: 60px; display: block; }
.site-nav-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 26px;
}
.site-nav-links a {
  color: var(--text);
  text-decoration: none;
  /* Matches their own nav link font-size (measured 15px). */
  font-size: 15px;
  font-weight: 500;
  padding: 4px 2px;
  border-bottom: 2px solid transparent;
}
.site-nav-links a:hover { color: var(--accent); }
.site-nav-links a.site-nav-active {
  color: var(--accent);
  font-weight: 600;
  border-bottom-color: var(--accent);
}
/* View-mode pill/admin-panel-link/logout/theme-toggle - originally lived in
   every page's own <header>, but with the site-wide nav now doubling as
   portlincolntilesandpavers.com's own header bar, these read as "account
   controls" that belong up there too rather than repeated per page (and
   the theme-toggle can only exist once per page - its script binds to a
   single #theme-toggle id). */
.site-nav-controls {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-shrink: 0;
}
/* Hamburger toggle for .site-nav-links - hidden entirely above the
   700px breakpoint, where the links render inline as normal (see
   nav-toggle.js for the open/close behaviour). */
.nav-hamburger {
  display: none;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
  width: 40px;
  height: 40px;
  border: 1px solid var(--glass-border);
  border-radius: 8px;
  background: var(--glass-bg);
  cursor: pointer;
  flex-shrink: 0;
  order: 2;
}
.nav-hamburger span {
  display: block;
  width: 20px;
  height: 2px;
  background: var(--text);
  border-radius: 2px;
  transition: transform 0.2s ease, opacity 0.2s ease;
}
.nav-hamburger.open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-hamburger.open span:nth-child(2) { opacity: 0; }
.nav-hamburger.open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
@media (max-width: 700px) {
  .nav-hamburger { display: flex; }
  .site-nav-logo { order: 1; }
  .site-nav-controls {
    width: 100%;
    flex-wrap: wrap;
    order: 3;
  }
  /* Collapsed by default - .open (toggled by nav-toggle.js) switches it
     back to a full-width column of links on its own row below the logo/
     hamburger/controls row. */
  .site-nav-links {
    display: none;
    width: 100%;
    order: 4;
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
    padding-top: 12px;
    border-top: 1px solid var(--border);
  }
  .site-nav-links.open { display: flex; }
}
.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.site-disclaimer {
  max-width: 900px;
  margin: 32px auto 24px;
  padding: 0 24px;
  font-size: 0.72rem;
  line-height: 1.5;
  color: var(--text-muted);
  text-align: center;
}
/* Solid brand-cream band (var(--bg) is #f2e9e4 in light mode - see the
   palette note above) rather than the glass/blur treatment the rest of
   the page uses - this is the one element meant to read as "the site's
   own header", matching portlincolntilesandpavers.com's flat cream header
   bar, not a floating panel over the background. */
header {
  padding: 20px 24px;
  margin: 16px 24px 6px;
  border-radius: 18px;
  background: var(--bg);
  border: 1px solid var(--glass-border);
  box-shadow: var(--glass-shadow);
  display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 16px;
}
/* Moved here from the filters sidebar (2026-08) - the shortlist toggle is
   used often enough that burying it inside the collapsible mobile filters
   panel made it hard to find; the section header is visible on every
   screen size without opening anything. Specificity (tag+class, not just
   a bare class) is deliberate - .reset-btn's own `flex: 1` rule (further
   down this file) would otherwise win the cascade by source order alone
   and stretch this button to fill the header row edge-to-edge. */
button.shortlist-header-btn { flex: none; width: auto; }
/* A section browse page wraps <header> and the section tabs in one shared
   div (see browse.html), flush and full-width directly under .site-nav
   (2026-08 - previously its own inset, rounded, floating glass card with
   a visible gap above it, reading as a second disconnected bar rather
   than part of the same nav) - same flat background/no radius/no shadow
   treatment as .site-nav itself, so the two read as one continuous top
   block: nav links, then title+shortlist, then category tabs, divided
   only by thin border lines rather than a gap. The nested-override rules
   just below strip header's own standalone panel styling (background/
   border/shadow/radius/margin) only in this context. The landing page's
   <header> has no such wrapper, so it's completely unaffected and keeps
   using the plain `header` rule above as its own standalone panel. */
.section-header-panel {
  margin: 0;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}
.section-header-panel header {
  margin: 0; padding: 20px 24px 16px; border-radius: 0;
  background: none; border: none; box-shadow: none;
  border-top: 1px solid var(--border);
  -webkit-backdrop-filter: none; backdrop-filter: none;
}
header h1 { margin: 0 0 4px; font-size: 1.6rem; font-weight: 600; font-family: "Source Serif 4", Georgia, serif; }
header .subtitle { color: var(--text-muted); font-size: 0.9rem; }
.view-mode-pill {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 5px 12px;
  border-radius: 999px;
  white-space: nowrap;
  background: var(--card-bg);
  color: var(--text-muted);
  border: 1px solid var(--border);
}
.view-mode-pill-admin { background: var(--accent-bg); color: var(--accent); border-color: var(--accent); }
.admin-panel-link {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 5px 12px;
  border-radius: 999px;
  white-space: nowrap;
  background: var(--accent);
  color: #fff;
  border: 1px solid var(--accent);
  text-decoration: none;
}
.admin-panel-link:hover { opacity: 0.85; }
.logout-form { margin: 0; }
.logout-link {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 5px 12px;
  border-radius: 999px;
  white-space: nowrap;
  background: var(--card-bg);
  color: var(--text-muted);
  border: 1px solid var(--border);
  cursor: pointer;
  font-family: inherit;
}
.logout-link:hover { border-color: var(--accent); color: var(--accent); }

.theme-toggle {
  position: relative; width: 54px; height: 20px; border-radius: 999px; flex-shrink: 0;
  border: 1px solid var(--border); background: var(--card-bg); padding: 0; cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease;
}
.theme-toggle:hover { border-color: var(--accent); }
.theme-toggle[aria-checked="true"] { background: var(--accent); border-color: var(--accent); }
.theme-toggle-thumb {
  position: absolute; top: 2px; left: 2px; width: 15px; height: 15px; border-radius: 50%;
  background: #fff; display: flex; align-items: center; justify-content: center;
  font-size: 0.6rem; line-height: 1; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
  transition: transform 0.2s ease;
}
.theme-toggle[aria-checked="true"] .theme-toggle-thumb { transform: translateX(34px); }
.theme-toggle-text {
  position: absolute; top: 50%; transform: translateY(-50%);
  font-size: 0.52rem; font-weight: 700; letter-spacing: 0.01em; color: var(--text-muted);
  transition: opacity 0.15s ease; white-space: nowrap;
}
.theme-toggle-text-off { right: 5px; opacity: 1; }
.theme-toggle-text-on { left: 5px; opacity: 0; color: #fff; }
.theme-toggle[aria-checked="true"] .theme-toggle-text-off { opacity: 0; }
.theme-toggle[aria-checked="true"] .theme-toggle-text-on { opacity: 1; }
/* Root-category tabs across the top of a section browse page - one per
   PRODUCT_TYPES entry (see views.browse()'s section_tabs), so a visitor
   can jump straight between sections without going back to the landing
   page. Kept deliberately prominent (large pill, bold label, glowing
   active state) since this is now the site's primary top-level nav.
   Always nested inside .section-header-panel (see browse.html) rather
   than a standalone glass panel of its own - a border-top divider marks
   where the header ends and the tab row begins within that one shared
   panel. Desktop/tablet: a single horizontally
   scrollable row. Narrow mobile stacks these one per row instead (see the
   max-width:700px override below) - a scroll row that only shows a
   sliver of the next tab reads as cut off rather than a deliberate
   affordance at that width. */
.section-tabs {
  display: flex; gap: 10px; overflow-x: auto; scrollbar-width: none;
  padding: 12px 20px 14px; border-top: 1px solid var(--glass-border);
}
.section-tabs::-webkit-scrollbar { display: none; }
.section-tab {
  display: flex; align-items: center; gap: 9px; flex-shrink: 0;
  padding: 9px 18px; border-radius: 999px; text-decoration: none;
  color: var(--text); font-weight: 600; font-size: 0.92rem;
  transition: background 0.18s ease, transform 0.18s ease, box-shadow 0.18s ease, color 0.18s ease;
}
.section-tab:hover { background: var(--glass-bg-strong); transform: translateY(-1px); }
.section-tab-count {
  font-size: 0.7rem; font-weight: 700; color: var(--text-muted);
  background: var(--glass-bg-strong); padding: 2px 9px; border-radius: 999px;
  transition: background 0.18s ease, color 0.18s ease;
}
.section-tab.active {
  color: #fff;
  background: linear-gradient(135deg, var(--accent), #2ba9b3);
  box-shadow: 0 6px 18px rgba(19, 131, 142, 0.45);
}
.section-tab.active .section-tab-count { background: rgba(255, 255, 255, 0.28); color: #fff; }
@media (min-width: 900px) {
  .section-tabs { justify-content: center; }
}
@media (max-width: 700px) {
  .section-tabs {
    flex-direction: column;
    align-items: stretch;
    overflow-x: visible;
  }
  .section-tab { width: 100%; }
}

/* A short admin-authored PSA banner for a section (added 2026-09,
   SiteSettings.section_notices) - e.g. pointing a visitor from one
   section to another for something that isn't filed where they'd expect
   ("Looking for toilet roll holders? Check Accessories & Hardware").
   Renders only when that section has one set - see browse.html. */
.section-notice {
  margin: 0 0 24px; padding: 18px 24px; border-radius: 14px;
  background: var(--glass-bg-strong); border: 1px solid var(--glass-border);
  border-left: 5px solid var(--accent); color: var(--text); font-size: 1.05rem;
  line-height: 1.5; display: flex; align-items: center; gap: 14px;
}
.section-notice::before { content: "\1F4A1"; font-size: 1.4rem; flex-shrink: 0; }
@media (max-width: 700px) {
  .section-notice { margin: 0 0 20px; padding: 14px 16px; font-size: 0.95rem; }
}

/* My Shortlist cross-section view (added 2026-09, client request - a
   shortlist is one shared list across the whole site, so toggling it
   should show everything on it, not just whatever this page's own section
   happens to hold). catalogue.js toggles `body.shortlist-mode` when the
   toggle is on - see its own shortlistToggleBtn click handler. Hidden by
   default (shown only via that class) so a plain page load never flashes
   it. The per-section Supplier/Colour/Size/Sort/Category/Finish filters
   are hidden while it's active - their option lists only ever cover this
   one section, which would silently misfilter items from every other
   section mixed in here. */
.shortlist-mode-banner {
  display: none; margin: 0 0 24px; padding: 14px 20px; border-radius: 14px;
  background: var(--glass-bg-strong); border: 1px solid var(--glass-border);
  color: var(--text-muted); font-size: 0.92rem; line-height: 1.5;
  align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap;
}
body.shortlist-mode .shortlist-mode-banner { display: flex; }
/* "Copy Shareable Link" (added 2026-09) shares the row via flex above
   rather than getting a full-width line of its own - see catalogue.js's
   shortlistShareBtn handler and catalogue/views.py's
   create_shortlist_share(). Not present at all on the shared-shortlist
   page itself (shared_shortlist.html has no matching button id), only in
   this banner when it's your own My Shortlist view. */
#shortlist-share-btn { flex-shrink: 0; }
body.shortlist-mode .filter,
body.shortlist-mode .filter-fullwidth {
  display: none;
}

/* Two-column layout: a filters sidebar down the left, catalogue grid
   filling the rest of the screen's width. Mobile-first - .layout stacks
   (sidebar above grid, filters collapsible) below the desktop breakpoint,
   where it switches to a sticky left column instead. */
.layout { display: flex; flex-direction: column; gap: 0; padding: 20px 24px 60px; align-items: flex-start; }
.sidebar { width: 100%; }
.main-content { width: 100%; min-width: 0; }

.filters-toggle {
  display: flex; align-items: center; gap: 6px; align-self: flex-start; margin: 12px 0;
  padding: 8px 14px; border-radius: 999px; border: 1px solid var(--glass-border);
  background: var(--glass-bg); color: var(--text); font-size: 0.82rem; cursor: pointer;
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
}
.filters-toggle:hover { border-color: var(--accent); color: var(--accent); }
/* The filters panel is its own frosted glass card - a self-contained
   surface rather than just a border under a row of controls, so it reads
   as the same "material" as the header/tabs/cards. */
.toolbar-filters {
  display: flex; flex-direction: column; gap: 14px; width: 100%;
  padding: 18px; border-radius: 16px; margin-bottom: 8px;
  background: var(--glass-bg);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  backdrop-filter: blur(18px) saturate(160%);
  border: 1px solid var(--glass-border);
  box-shadow: var(--glass-shadow);
}
.toolbar-filters.collapsed { display: none; }
.filter { display: flex; flex-direction: column; gap: 4px; font-size: 0.75rem; color: var(--text-muted); width: 100%; }
.filter select, .search input {
  padding: 7px 10px; border-radius: 8px; border: 1px solid var(--glass-border);
  background: var(--glass-bg-strong); color: var(--text); font-size: 0.85rem; width: 100%;
}
.filter select:focus, .search input:focus { outline: none; border-color: var(--accent); }
.search { display: flex; flex-direction: column; gap: 4px; font-size: 0.75rem; color: var(--text-muted); width: 100%; }
.search input { width: 100%; }
.sidebar-buttons { display: flex; flex-wrap: wrap; gap: 10px; }
.reset-btn {
  padding: 7px 14px; border-radius: 999px; border: 1px solid var(--glass-border);
  background: var(--glass-bg-strong); color: var(--text-muted); font-size: 0.85rem;
  cursor: pointer; white-space: nowrap; flex: 1;
}
.reset-btn:hover { border-color: var(--accent); color: var(--accent); }
.reset-btn.active { border-color: var(--accent); color: var(--accent); background: var(--accent-bg); }
/* Solid-fill accent instead of the plain muted-outline .reset-btn look -
   this one lives in the header now (not tucked inside the filters
   panel), so it needs to actually catch the eye rather than blend in
   next to the section title. */
button.shortlist-header-btn {
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
  font-weight: 600;
}
button.shortlist-header-btn:hover { opacity: 0.85; border-color: var(--accent); color: #fff; }
button.shortlist-header-btn.active {
  background: linear-gradient(135deg, var(--accent), #2ba9b3);
  color: #fff;
  border-color: var(--accent);
}
/* Draws the eye to "My Shortlist" right after a star-add, so a visitor who
   just starred something can actually find where it went - see
   pulseShortlistToggle() in catalogue.js. Scale-based (not color-only) so
   it still reads for a colour-blind visitor, and reduced-motion visitors
   get a plain colour flash instead of the transform. */
@keyframes shortlist-pulse {
  0%, 100% { transform: scale(1); box-shadow: none; }
  30% { transform: scale(1.12); box-shadow: 0 0 0 4px var(--accent-bg); border-color: var(--accent); color: var(--accent); }
  60% { transform: scale(1); }
}
.shortlist-pulse { animation: shortlist-pulse 0.6s ease-in-out 2; }
@media (prefers-reduced-motion: reduce) {
  @keyframes shortlist-pulse {
    0%, 100% { box-shadow: none; }
    30% { box-shadow: 0 0 0 4px var(--accent-bg); border-color: var(--accent); color: var(--accent); }
  }
}
.filter-fullwidth { width: 100%; }
.checkbox-group { display: flex; flex-wrap: wrap; gap: 6px; }
.checkbox-chip {
  display: inline-flex; align-items: center; gap: 5px; padding: 5px 10px;
  border-radius: 14px; border: 1px solid var(--glass-border); background: var(--glass-bg-strong);
  color: var(--text); font-size: 0.78rem; cursor: pointer; user-select: none;
}
.checkbox-chip input { margin: 0; accent-color: var(--accent); }
.checkbox-chip:has(input:checked) { border-color: var(--accent); background: var(--accent-bg); color: var(--accent); }
.chip-count { font-size: 0.7rem; color: var(--text-muted); }
.checkbox-chip:has(input:checked) .chip-count { color: var(--accent); }
.checkbox-chip.zero-count { opacity: 0.4; }
.result-count { font-size: 0.8rem; color: var(--text-muted); padding-bottom: 10px; }

.pager {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 28px 0 12px;
}
/* Two rows: page-number buttons on top, First/Prev/jump-dropdown/Next/Last
   underneath - kept as separate rows (rather than one wrapping row) so a
   wide top row of page numbers doesn't visually run together with the nav
   controls, and so the nav controls always land on a predictable second
   line instead of wherever they happen to wrap to. */
.pager-numbers, .pager-nav-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 6px;
}
.pager-btn {
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--text);
  padding: 7px 12px;
  border-radius: 8px;
  font-size: 0.85rem;
  cursor: pointer;
}
.pager-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.pager-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.pager-btn-active { border-color: var(--accent); background: var(--accent-bg); color: var(--accent); font-weight: 600; }
.pager-nav { font-weight: 500; }
.pager-ellipsis { color: var(--text-muted); padding: 0 4px; }
.pager-select {
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--text);
  padding: 7px 10px;
  border-radius: 8px;
  font-size: 0.85rem;
  cursor: pointer;
  margin-left: 6px;
}
.pager-select:hover { border-color: var(--accent); }
.pager-summary { font-size: 0.8rem; color: var(--text-muted); }

/* Desktop: sidebar becomes a persistent left column instead of a
   collapsible drawer - the catalogue grid fills the remaining width. */
@media (min-width: 900px) {
  .layout { flex-direction: row; gap: 32px; align-items: flex-start; }
  .sidebar {
    /* .section-header-panel is no longer sticky (2026-08 - it used to
       stay pinned under the nav on desktop, but that's now inconsistent
       with mobile, where it was never sticky), so nothing else stays
       pinned above this once scrolled - top/max-height just need to
       clear the viewport edge itself, not some other sticky element's
       height. */
    width: 300px; min-width: 300px; flex-shrink: 0; position: sticky; top: 16px;
    max-height: calc(100vh - 32px); overflow-y: auto; padding-right: 4px;
  }
  .filters-toggle { display: none; }
  .toolbar-filters.collapsed { display: flex; } /* always open on desktop, regardless of the mobile collapse state */

  /* Sticky, compact pager - desktop only (a sticky bottom bar on mobile
     would eat too much of an already-small viewport, and touch scrolling
     makes reaching the real page bottom easy enough without one). Compact
     means smaller everything (padding/font/gap); the numbers row and the
     First/Prev/dropdown/Next/Last row stay two separate lines here too
     (.pager itself stays column - only the "sticky glass bar" treatment
     is desktop-only, not a row-direction merge back into one line). */
  .pager {
    position: sticky; bottom: 0; z-index: 5;
    gap: 6px; padding: 8px 16px;
    background: var(--glass-bg-strong);
    -webkit-backdrop-filter: blur(14px) saturate(160%);
    backdrop-filter: blur(14px) saturate(160%);
    border-top: 1px solid var(--glass-border);
    margin: 20px -24px 0;
  }
  .pager-numbers, .pager-nav-row { gap: 4px; }
  .pager-btn { padding: 5px 9px; font-size: 0.78rem; }
  .pager-select { padding: 5px 8px; font-size: 0.78rem; margin-left: 4px; }
  .pager-summary { white-space: nowrap; }
}

.grid {
  padding: 0 0 60px;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 8px;
}
.card {
  display: flex; flex-direction: column; background: var(--glass-bg-strong);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  backdrop-filter: blur(14px) saturate(160%);
  border: 1px solid var(--glass-border); border-radius: 0; overflow: hidden;
  cursor: pointer; transition: transform 0.12s ease, box-shadow 0.12s ease, border-color 0.12s ease;
  box-shadow: var(--glass-shadow);
}
.card:hover { transform: translateY(-4px); border-color: var(--accent); box-shadow: 0 10px 26px rgba(19, 131, 142, 0.25); }
.card-img { aspect-ratio: 1 / 1; background: var(--accent-bg); overflow: hidden; position: relative; }
.card-img.scrubbable { cursor: ew-resize; }
.card-img img.cf-layer {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block;
  opacity: 0; transition: opacity 0.15s ease;
}
.card-img img.cf-layer.cf-visible { opacity: 1; }
.badge {
  position: absolute; top: 8px; left: 8px; font-size: 0.62rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.03em; padding: 3px 8px; border-radius: 10px;
  /* --supplier-color/--supplier-text-color are set inline per-card by
     catalogue.js's badgeStyle(), sourced from each Supplier's panel-
     editable badge_color/badge_text_color (see catalogue/views.py's
     browse()) - not theme-aware any more (a single admin-picked colour
     pair, same in light and dark mode), unlike the rest of this palette. */
  color: var(--supplier-text-color, #fff); background: var(--supplier-color, #666);
}
.img-count {
  /* Floats just above .card-overlay's title/star row (not bottom:8px any
     more) so the two don't overlap now that the overlay occupies the
     bottom of the card too. */
  position: absolute; bottom: 46px; right: 8px; background: rgba(0,0,0,0.6);
  color: #fff; font-size: 0.7rem; padding: 2px 7px; border-radius: 10px; z-index: 2;
}
.scrub-indicator {
  /* z-index above .card-overlay so the segments stay visible painted over
     the gradient (they're already a plain white bar, reads fine over a
     dark scrim), instead of being hidden underneath it. */
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 3; display: flex; gap: 2px;
  padding: 0 6px 5px; opacity: 0; transition: opacity 0.15s ease; pointer-events: none;
}
.card:hover .scrub-indicator { opacity: 1; }
.scrub-seg {
  flex: 1; height: 2px; border-radius: 1px; background: rgba(255,255,255,0.35);
  transition: background 0.12s ease;
}
.scrub-seg.active { background: rgba(255,255,255,0.95); }
.swipe-hint-wrap {
  position: absolute; inset: 0; display: none; align-items: center; justify-content: center;
  pointer-events: none;
}
.swipe-hint {
  display: inline-flex; align-items: center; gap: 6px; padding: 8px 16px; border-radius: 18px;
  background: rgba(0, 0, 0, 0.6); color: #fff; font-size: 0.88rem; font-weight: 600;
  opacity: 0; animation: swipe-hint-cue 3.2s ease-in-out 1;
}
@keyframes swipe-hint-cue {
  0%, 100% { opacity: 0; transform: translateX(0); }
  10%, 80% { opacity: 1; }
  25% { transform: translateX(-8px); }
  45% { transform: translateX(8px); }
  65% { transform: translateX(-4px); }
}
/* Only touch-primary devices get the hint - a mouse user already sees the
   ew-resize cursor and the crossfade on hover, so they'd never need it and
   it'd just be visual noise. */
@media (hover: none) and (pointer: coarse) {
  .swipe-hint-wrap { display: flex; }
}
@media (prefers-reduced-motion: reduce) {
  .swipe-hint { animation: none; opacity: 0; }
}
.empty-state { text-align: center; padding: 60px 20px; color: var(--text-muted); display: none; }

/* Full-bleed card: the photo fills the whole card (see .card-img above),
   title + shortlist star float on top of it in this bottom strip instead
   of a separate body section - see cardHtml() in catalogue.js. The
   gradient is deliberately NOT theme-aware (always a dark-to-transparent
   scrim, in both light and dark mode) since it has to stay legible over
   an arbitrary product photo, not the page background - a light-mode
   scrim would disappear against a light photo. The title's own text-
   shadow is a second line of defence for the part of a photo the gradient
   hasn't fully darkened yet (a bright top-of-frame area right at the
   gradient's own fade edge, for instance). */
.card-overlay {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  display: flex; align-items: flex-end; justify-content: space-between; gap: 8px;
  padding: 28px 12px 10px;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.85) 0%, rgba(0, 0, 0, 0.55) 45%, transparent 100%);
  pointer-events: none;
}
.card-overlay h3 {
  margin: 0; font-size: 0.92rem; font-weight: 600; line-height: 1.3; color: #fff;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.7);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.shortlist-star {
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  width: 30px; height: 30px; border-radius: 50%;
  background: rgba(0, 0, 0, 0.45); border: 1px solid rgba(255, 255, 255, 0.3);
  cursor: pointer; font-size: 1.05rem; line-height: 1; color: #fff; pointer-events: auto;
  transition: background 0.15s ease, transform 0.12s ease;
}
.shortlist-star:hover { background: rgba(0, 0, 0, 0.65); transform: scale(1.08); }
.shortlist-star.active { color: var(--accent); background: rgba(255, 255, 255, 0.9); }

/* Desktop-only hover preview - a single floating popup shared by every
   card (positioned via JS in catalogue.js, not one per card) showing that
   product's description plus a button that opens the same full modal a
   click already would. Hidden entirely on touch devices via the media
   query below (hover doesn't really exist there, and a tap already opens
   the modal directly) - catalogue.js also gates attaching the hover
   listeners in the first place, this is just a belt-and-suspenders CSS
   backstop. */
.hover-popup {
  position: fixed; z-index: 150; width: 260px;
  padding: 14px; border-radius: 14px;
  background: var(--glass-bg-strong);
  -webkit-backdrop-filter: blur(20px) saturate(160%);
  backdrop-filter: blur(20px) saturate(160%);
  border: 1px solid var(--glass-border);
  box-shadow: var(--glass-shadow);
  opacity: 0; transform: translateY(4px);
  pointer-events: none;
  transition: opacity 0.15s ease, transform 0.15s ease;
}
.hover-popup.open { opacity: 1; transform: translateY(0); pointer-events: auto; }
.hover-popup-desc {
  font-size: 0.82rem; line-height: 1.45; color: var(--text); margin: 0 0 10px;
  display: -webkit-box; -webkit-line-clamp: 5; -webkit-box-orient: vertical; overflow: hidden;
}
.hover-popup-btn {
  display: block; width: 100%; font-size: 0.78rem; font-weight: 600;
  padding: 7px 12px; border-radius: 999px; border: 1px solid var(--accent);
  background: var(--accent-bg); color: var(--accent); cursor: pointer; font-family: inherit;
}
.hover-popup-btn:hover { background: var(--accent); color: #fff; }
@media (hover: none), (pointer: coarse) {
  .hover-popup { display: none !important; }
}

/* Kept always in the flex-rendered state (not display:none/flex) so
   opacity/transform can actually transition - visibility's own transition
   is delayed on close only, so the overlay stops intercepting clicks
   immediately on close but still fades out visually, and becomes
   interactive the instant it opens. */
.modal-overlay {
  display: flex; position: fixed; inset: 0; background: var(--overlay);
  z-index: 100; align-items: flex-start; justify-content: center; overflow-y: auto; padding: 40px 16px;
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity 0.2s ease, visibility 0s linear 0.2s;
}
.modal-overlay.open {
  opacity: 1; visibility: visible; pointer-events: auto;
  transition: opacity 0.2s ease, visibility 0s linear 0s;
}
.modal {
  background: var(--glass-bg-strong);
  -webkit-backdrop-filter: blur(24px) saturate(160%);
  backdrop-filter: blur(24px) saturate(160%);
  border: 1px solid var(--glass-border); box-shadow: var(--glass-shadow);
  border-radius: 20px; max-width: 960px; width: 100%; padding: 20px; position: relative;
  transform: scale(0.96) translateY(10px); transition: transform 0.2s ease;
}
.modal-overlay.open .modal { transform: scale(1) translateY(0); }
.modal-close {
  position: absolute; top: 14px; right: 14px; z-index: 5;
  display: flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: 50%;
  background: var(--glass-bg-strong); border: 1px solid var(--glass-border);
  color: var(--text); font-size: 1.3rem; line-height: 1; cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, transform 0.12s ease;
}
.modal-close:hover { background: var(--accent); color: #fff; border-color: var(--accent); transform: scale(1.08); }

/* Two-column product-detail layout on desktop (large hero + thumbnail
   strip on the left, sticky while the info column scrolls independently
   if it's taller than the viewport); stacks to one column on mobile. */
.modal-layout { display: flex; flex-direction: column; gap: 20px; }
@media (min-width: 760px) {
  .modal-layout { flex-direction: row; align-items: flex-start; }
  /* Same 80vh budget as .modal-info (not a fixed pixel cap on the
     thumbnail grid specifically) - a product with a short description/few
     specs leaves plenty of room here, so the thumbnail grid grows to
     actually use it instead of stopping at an arbitrary 2 rows with a lot
     of visibly empty column below it; a product with 20-30+ photos still
     gets capped at the same 80vh the info column already respects, with
     its own scrollbar only kicking in past that point. */
  .modal-media {
    width: 42%; flex-shrink: 0; position: sticky; top: 0; max-height: 80vh;
    display: flex; flex-direction: column;
  }
  .modal-info { flex: 1; min-width: 0; max-height: 80vh; overflow-y: auto; padding-right: 6px; }
}
/* Shows the photo at its own real proportions (added 2026-09, client
   request) rather than force-cropping every product to the same 4:3 box
   via object-fit:cover - a portrait or square shot no longer gets pieces
   cut off. .modal-hero has no fixed aspect-ratio any more: it just wraps
   whatever height the image naturally renders at, capped by max-height
   the same as before, with the modal's own max-width (see .modal above)
   untouched either way since the column width cap (max-width:100%) is
   what actually bounds it. */
.modal-hero {
  max-height: 46vh; flex-shrink: 0; border-radius: 14px; overflow: hidden; cursor: zoom-in;
  background: var(--accent-bg); border: 1px solid var(--glass-border);
  display: flex; align-items: center; justify-content: center;
}
.modal-hero img { max-width: 100%; max-height: 46vh; width: auto; height: auto; object-fit: contain; display: block; }
/* Fills whatever's left of .modal-media's 80vh budget below the hero -
   see the comment above - only scrolling internally (min-height:0 is what
   lets a flex child actually shrink to fit instead of overflowing its
   parent) if a product has enough photos to still exceed that. */
.modal-thumbs {
  display: grid; grid-template-columns: repeat(auto-fill, 54px); align-content: start; justify-content: start;
  gap: 6px; margin-top: 8px; min-height: 0; overflow-y: auto; padding-right: 2px;
}
.modal-thumbs img {
  width: 54px; height: 54px; object-fit: cover; border-radius: 8px; cursor: pointer;
  border: 2px solid transparent; opacity: 0.6; transition: opacity 0.12s ease, border-color 0.12s ease;
}
.modal-thumbs img:hover { opacity: 1; }
.modal-thumbs img.active { opacity: 1; border-color: var(--accent); }

.modal .badge { position: static; }
.modal-badges { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 10px; }
.modal-category-badge {
  font-size: 0.72rem; font-weight: 600; padding: 4px 10px; border-radius: 999px;
  color: var(--text-muted); border: 1px solid var(--glass-border); background: var(--glass-bg);
}
.modal-heading { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 10px; }
.modal-heading h2 { margin: 0; font-size: 1.4rem; font-weight: 700; line-height: 1.25; }
.modal-shortlist-star {
  display: inline-flex; align-items: center; gap: 6px; flex-shrink: 0;
  background: var(--glass-bg-strong); border: 1px solid var(--glass-border); border-radius: 999px;
  cursor: pointer; font-size: 0.78rem; font-weight: 600; padding: 6px 14px 6px 12px; color: var(--text-muted);
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.modal-shortlist-star-icon { font-size: 1rem; line-height: 1; }
.modal-shortlist-star:hover { border-color: var(--accent); color: var(--accent); }
.modal-shortlist-star.active { color: var(--accent); border-color: var(--accent); background: var(--accent-bg); }
.modal-desc { color: var(--text-muted); font-size: 0.9rem; line-height: 1.6; margin: 10px 0 16px; }
.modal-swatches { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; }
.modal-swatch {
  display: flex; align-items: center; font-size: 0.78rem;
  padding: 5px 10px; border-radius: 999px; border: 1px solid var(--glass-border); background: var(--glass-bg);
}
.lightbox-overlay {
  display: none; position: fixed; inset: 0; background: rgba(10, 10, 12, 0.94);
  z-index: 200; align-items: center; justify-content: center;
}
.lightbox-overlay.open { display: flex; }
.lightbox-overlay img { max-width: 90vw; max-height: 88vh; object-fit: contain; border-radius: 4px; }
.lightbox-close {
  position: absolute; top: 16px; right: 20px; background: none; border: none;
  color: #fff; font-size: 2.2rem; cursor: pointer; line-height: 1; padding: 4px;
}
.lightbox-nav {
  position: absolute; top: 50%; transform: translateY(-50%); background: rgba(255, 255, 255, 0.12);
  border: none; color: #fff; font-size: 1.8rem; width: 48px; height: 48px; border-radius: 50%;
  cursor: pointer; align-items: center; justify-content: center; line-height: 1;
}
.lightbox-nav:hover { background: rgba(255, 255, 255, 0.25); }
.lightbox-prev { left: 16px; }
.lightbox-next { right: 16px; }
.lightbox-counter {
  position: absolute; bottom: 18px; left: 50%; transform: translateX(-50%);
  color: #fff; font-size: 0.82rem; background: rgba(255, 255, 255, 0.12);
  padding: 4px 12px; border-radius: 10px;
}
/* A long single-column mobile grid (or a many-hundred-product desktop
   one) makes manually scrolling back to the nav/filters a real chore -
   shown at every screen size (2026-08, previously mobile-only). Hidden
   until scrolled past .visible's threshold (see catalogue.js's
   handleScrollForBackToTop) rather than always shown, so it doesn't sit
   in the way right after landing on the page. */
.back-to-top {
  display: block;
  position: fixed;
  bottom: 18px;
  left: 50%;
  transform: translateX(-50%) translateY(20px);
  z-index: 15;
  padding: 10px 18px;
  border-radius: 999px;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: #fff;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 6px 18px rgba(19, 131, 142, 0.4);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.back-to-top.visible {
  opacity: 1;
  pointer-events: auto;
  transform: translateX(-50%) translateY(0);
}
@media (min-width: 900px) {
  /* .pager is its own sticky bar at the bottom of the viewport on desktop
     (see its own min-width:900px rule) - lifted above it here so the two
     don't overlap. */
  .back-to-top { bottom: 68px; }
}
@media (prefers-reduced-motion: reduce) {
  .back-to-top { transition: opacity 0.2s ease; }
}
.spec-list {
  display: flex; flex-direction: column; gap: 2px; font-size: 0.85rem; margin-bottom: 18px;
  background: var(--glass-bg); border: 1px solid var(--glass-border); border-radius: 12px; padding: 4px 12px;
}
.spec-list .spec { display: flex; justify-content: space-between; gap: 8px; border-bottom: 1px solid var(--glass-border); padding: 8px 0; }
.spec-list .spec:last-child { border-bottom: none; }
.spec-list .spec-label { color: var(--text-muted); }
.variant-table {
  width: 100%; border-collapse: collapse; font-size: 0.82rem; margin-bottom: 18px;
  background: var(--glass-bg); border: 1px solid var(--glass-border); border-radius: 12px; overflow: hidden;
}
.variant-table th, .variant-table td { text-align: left; padding: 8px 12px; border-bottom: 1px solid var(--glass-border); }
.variant-table tr:last-child td { border-bottom: none; }
.variant-table th { color: var(--text-muted); font-weight: 600; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.03em; }
.modal-top-links { display: flex; gap: 8px; flex-wrap: wrap; margin: 2px 0 14px; }
/* Client-requested (2026-09) - makes clear every product shown is ordered
   through PLTP itself, not directly from the supplier, right above the
   Enquire/Call actions that are exactly how a visitor would act on it. */
.modal-order-note {
  font-size: 0.82rem; color: var(--text-muted); font-style: italic;
  margin: 0 0 12px; padding: 8px 12px; border-radius: 10px;
  background: var(--accent-bg); border: 1px solid var(--glass-border);
}
.modal-links { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
/* Two deliberately distinct treatments: solid accent buttons for the
   actions that actually drive a lead (enquire/call, kept down with the
   rest of the action row at the bottom), and an outlined-accent button
   for everything else that's still a real button but not the primary
   ask - the brochure/site links (moved up under the title so they're
   visible without scrolling) and the copy-link utility action. Each has
   its own class rather than a shared `.modal-links a` catch-all, since
   that shared selector's higher specificity would otherwise silently
   override .modal-cta's colours (which is exactly what happened before
   this was split out - .modal-links a beat .modal-cta on specificity
   regardless of source order). */
.modal-cta {
  font-size: 0.82rem; padding: 8px 14px; border-radius: 999px; border: 1px solid var(--accent);
  color: #fff; text-decoration: none; background: linear-gradient(135deg, var(--accent), #2ba9b3);
  font-weight: 600; box-shadow: 0 4px 14px rgba(19, 131, 142, 0.35);
}
.modal-cta:hover { opacity: 0.88; }
.modal-btn-outline {
  font-size: 0.82rem; padding: 8px 14px; border-radius: 999px; border: 1px solid var(--glass-border);
  color: var(--accent); background: var(--glass-bg-strong); cursor: pointer; font-family: inherit;
  text-decoration: none; font-weight: 600;
}
.modal-btn-outline:hover { background: var(--accent); color: #fff; border-color: var(--accent); }
/* Staff-only, deliberately separated (own row, own colour) from the rest
   of .modal-links so it doesn't read as just another action alongside
   Enquire/Call - this one removes the product from the whole catalogue,
   not a lead-generating action like its siblings. See catalogue.js's
   excludeProduct(). */
.modal-exclude-btn {
  font-size: 0.78rem; padding: 7px 14px;
  border-radius: 999px; border: 1px solid #c0392b; color: #c0392b;
  background: transparent; cursor: pointer; font-family: inherit; font-weight: 600;
}
.modal-exclude-btn:hover:not(:disabled) { background: #c0392b; color: #fff; }
.modal-exclude-btn:disabled { opacity: 0.6; cursor: not-allowed; }
/* Staff-only "Move to section" (added 2026-09) - shares the row with
   Exclude from Catalogue rather than each getting its own full-width row,
   since neither is a lead-generating action like the rest of .modal-links
   above it (see catalogue.js's setProductType()). */
.modal-staff-tools {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 14px;
}
.modal-type-select-label {
  display: flex; align-items: center; gap: 8px; font-size: 0.78rem; font-weight: 600; color: var(--text-muted);
}
.modal-type-select {
  font-size: 0.82rem; font-weight: 600; padding: 6px 10px; border-radius: 8px;
  border: 1px solid var(--glass-border); background: var(--glass-bg-strong); color: var(--text);
  font-family: inherit; cursor: pointer;
}
.modal-type-select:disabled { opacity: 0.6; cursor: not-allowed; }

/* Section-chooser landing page - large clickable image tiles, one per
   PRODUCT_TYPES entry. Reuses the same design tokens as everything else
   (--accent-bg as the no-photo fallback background, --overlay for the
   gradient so light/dark theme both stay legible). A thin glass border +
   ambient teal shadow ties them to the same material as the rest of the
   glassmorphism aesthetic, without needing to blur the photo itself. */
.section-tiles {
  max-width: 1400px; margin: 0 auto; padding: 8px 24px 48px;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 20px;
}
.section-tile {
  position: relative; display: block; aspect-ratio: 4 / 3; border-radius: 18px; overflow: hidden;
  background-color: var(--accent-bg); background-size: cover; background-position: center;
  text-decoration: none; border: 1px solid var(--glass-border); box-shadow: var(--glass-shadow);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.section-tile:hover { transform: translateY(-4px); box-shadow: 0 10px 28px rgba(19, 131, 142, 0.3); }
.section-tile-overlay {
  position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: flex-end;
  padding: 20px; background: linear-gradient(to top, var(--overlay) 0%, rgba(0, 0, 0, 0) 65%);
}
.section-tile-name { color: #fff; font-size: 1.3rem; font-weight: 600; font-family: "Source Serif 4", Georgia, serif; }
.section-tile-count { color: rgba(255, 255, 255, 0.85); font-size: 0.85rem; margin-top: 2px; }

/* Login page (templates/admin/login.html - overrides Django admin's own
   login template so it matches the rest of the site instead of admin's
   default chrome). Reuses the same header/glass-card/theme-toggle
   language as landing.html rather than inventing a new visual style.
   .brand/.brand-logo have no other rule anywhere else in this file - the
   template's own img tag has no width/height either, so without this the
   logo rendered at its native pixel size (huge) instead of matching the
   .site-nav-logo's own 60px treatment. */
.brand { display: flex; align-items: center; gap: 14px; }
.brand-logo { width: 64px; height: 64px; object-fit: contain; flex-shrink: 0; }
.login-page {
  display: flex; justify-content: center; padding: 40px 24px 80px;
}
.login-card {
  width: 100%; max-width: 380px; padding: 28px 26px;
  border-radius: 18px; background: var(--glass-bg);
  -webkit-backdrop-filter: blur(18px) saturate(160%); backdrop-filter: blur(18px) saturate(160%);
  border: 1px solid var(--glass-border); box-shadow: var(--glass-shadow);
}
.login-card h2 { margin: 0 0 18px; font-size: 1.2rem; font-weight: 600; }
.login-error {
  background: rgba(200, 60, 60, 0.15); border: 1px solid rgba(200, 60, 60, 0.4);
  color: var(--text); border-radius: 10px; padding: 10px 12px; font-size: 0.85rem; margin: 0 0 16px;
}
.login-field { display: flex; flex-direction: column; gap: 4px; font-size: 0.8rem; color: var(--text-muted); margin-bottom: 14px; }
.login-field label { font-weight: 600; }
.login-field input {
  padding: 9px 12px; border-radius: 8px; border: 1px solid var(--glass-border);
  background: var(--glass-bg-strong); color: var(--text); font-size: 0.95rem; width: 100%;
}
.login-field input:focus { outline: none; border-color: var(--accent); }
.login-submit {
  width: 100%; margin-top: 6px; font-size: 0.9rem; font-weight: 600; padding: 10px 14px;
  border-radius: 999px; border: 1px solid var(--accent); color: #fff; cursor: pointer;
  background: linear-gradient(135deg, var(--accent), #2ba9b3); box-shadow: 0 4px 14px rgba(19, 131, 142, 0.35);
}
.login-submit:hover { opacity: 0.88; }
